golang garbage collection gc

http://ruizeng.net/golang-gc-internals/

 

Summary

In the process of actually using the go language, I encountered some seemingly strange memory usage phenomena, so I decided to do some research on the garbage collection model of the go language. This paper summarizes the results of the study.

What is garbage collection?

Once upon a time, memory management was a major problem for programmers developing applications. In traditional system-level programming languages ​​(mainly C/C++), programmers must carefully manage memory and control the application and release of memory. A little carelessness may cause memory leaks, which are difficult to find and locate, and have always been a nightmare for developers. How to solve this headache problem? Two approaches have generally been used in the past:

  • Memory leak detection tool. The principle of this tool is generally static code scanning, which detects code segments that may have memory leaks by scanning programs. However, the detection tools inevitably have omissions and deficiencies, and can only play an auxiliary role.
  • Smart pointers. This is an automatic memory management method introduced in C++. The object is referenced through a pointer object with automatic memory management function, so that the programmer does not need to pay too much attention to the release of memory, and achieves the purpose of automatic memory release. This method is the most widely used method, but it has a certain learning cost for programmers (not native support at the language level), and once there is a scene of forgetting to use, it still cannot avoid memory leaks.

In order to solve this problem, almost all new languages ​​developed later (java, python, php, etc.) have introduced automatic memory management at the language level - that is, language users only need to pay attention to memory application and do not have to care about memory release. , memory release is automatically managed by the virtual machine or runtime. This behavior of automatically reclaiming memory resources that are no longer in use is called garbage collection.

Common Garbage Collection Methods

reference counting

This is the simplest garbage collection algorithm, and it is similar to the smart pointer mentioned earlier. A reference count is maintained for each object. When the object referencing the object is destroyed or updated, the reference count of the referenced object is automatically decremented by one, and the reference count of the referenced object is automatically increased by one when the referenced object is created or assigned to other objects. When the reference count reaches 0, the object is reclaimed immediately.

The advantage of this method is that it is simple to implement and the memory is recovered in a timely manner. This algorithm is widely used in systems with tight memory and high real-time performance, such as ios cocoa framework, php, python, etc. Simple reference counting algorithms also have significant disadvantages:

  • Frequently updating reference counts reduces performance. A simple solution is for the compiler to combine adjacent reference count update operations into one update; another approach is to not count frequently occurring temporary variable references, but to scan the stack when the reference reaches 0. There are also temporary object references to decide whether to release. And so on, there are many other methods, you can refer to here for details.
  • Circular reference problem. When a circular reference occurs between objects, none of the objects in the reference chain can be released. The most obvious solution is to avoid generating circular references. For example, cocoa introduces two pointer types: strong pointer and weak pointer. Or the system detects circular references and actively breaks the circular chain. Of course this also increases the complexity of garbage collection.

mark and sweep

This method is divided into two steps. Marking starts from the root variable to traverse all referenced objects, and marks all objects that can be accessed through application traversal as "referenced"; The memory is reclaimed (reclamation may be accompanied by a defragmentation operation). This method solves the insufficiency of reference counts, but also has obvious problems: every time garbage collection is started, all current normal code execution will be suspended, and the collection will greatly reduce the system responsiveness! Of course, many variants of the mark&sweep algorithm (such as the three-color marking method) have also appeared in the follow-up to optimize this problem.

Generation Collection

After a lot of practical observation, in object-oriented programming languages, the life cycle of most objects is very short. The basic idea of ​​generational collection is to divide the heap into two or more spaces called generations. Newly created objects are stored in what is called the young generation (generally, the size of the young generation will be much smaller than the old generation), and objects with longer lifetimes will be promoted as garbage collection is repeated ( promotion) into the old age. Therefore, two different garbage collection methods, the young generation garbage collection and the old generation garbage collection, emerge as the times require, which are used to perform garbage collection on objects in their respective spaces. The new generation garbage collection is very fast, several orders of magnitude faster than the old generation. Even if the new generation garbage collection frequency is higher, the execution efficiency is still stronger than the old generation garbage collection. This is because the life cycle of most objects is very short. , there is no need to upgrade to the old age at all.

GO's garbage collector

The go language garbage collection generally adopts the classic mark and sweep algorithm.

  • Before version 1.3, golang's garbage collection algorithm was very simple, and its performance was widely criticized: go runtime suspends the execution of all tasks under certain conditions (memory exceeds the threshold or periodically such as 2min), performs mark&sweep operations, and after the operation is completed Start the execution of all tasks. In scenarios with high memory usage, the go program will experience a very obvious stagnation phenomenon (Stop The World) during garbage collection. In a background service process that requires high response speed, this kind of delay is simply unbearable! During this period, many teams at home and abroad who practiced the go language in the production environment have stepped on the pit of gc more or less. At that time, the common method to solve this problem was to control the amount of memory that was automatically allocated memory as soon as possible to reduce the gc load, and at the same time to use the method of manual memory management to deal with scenarios that required a large amount of and high-frequency allocation of memory.
  • Since version 1.3, the go team has started to continuously improve and optimize gc performance. When each new version of go is released, gc improvement has become a point that everyone pays attention to. In version 1.3, go runtime separates mark and sweep operations. As before, it also suspends the execution of all tasks and starts the mark. After the mark is completed, the suspended task is restarted. Instead, the sweep task and the ordinary coroutine task are used. The same is executed in parallel with other tasks. If running on a multi-core processor, go will try to run the gc task on a separate core without affecting the execution of the business code. The go team's own claim is to reduce the time-out by 50%-70%.
  • Version 1.4 (the current latest stable version) does not have many performance changes to gc. In version 1.4, a lot of runtime code has replaced the native c language implementation and adopted the go language implementation. A major change brought about by gc is that it can implement accurate gc. The C language implementation cannot obtain the object information of the memory during gc, so it cannot accurately distinguish between ordinary variables and pointers, and can only use ordinary variables as pointers. If it happens that the space pointed to by this ordinary variable has other objects, then this object will not be used. Recycle. The go language implementation is fully aware of the type information of the object, and only traverses the object pointed to by the pointer when marking, thus avoiding the waste of heap memory in the C implementation (solving about 10-30%).
  • In version 1.5, the go team has made a relatively large improvement to gc (foreshadowing has been laid in 1.4, such as the introduction of write barrier), and the official main goal is to reduce the delay. The garbage collector that go 1.5 is implementing is a "non-generational, non-moving, concurrent, three-color mark-and-sweep garbage collector". The generational algorithm has been mentioned above. It is a good garbage collection management strategy, but it is not considered in version 1.5; I guess the reason is that the steps cannot be taken too much, and it must be gradually improved. Go official also said that it will be in the Considered in the gc optimization of version 1.6. At the same time, the three-color marking method introduced above is introduced. The mark operation of this method can be performed gradually without scanning the entire memory space every time, which can reduce the time to stop the world. It can be seen from this that the garbage collection performance of go has been improving all the way up to version 1.5, but with relatively mature garbage collection systems (such as java jvm and javascript v8), the path that go needs to be optimized is still very long (but I believe that The future must be bright~).

Experience

The team also encounters the most and the most difficult problems when practicing the go language, which is also the memory problem (mainly gc).

The problem of large memory usage of go program

This problem is found when we stress test the background service. We simulate a large number of user requests to access the background service. At this time, each service module can observe a significant increase in memory usage. However, when the stress test was stopped, the memory usage did not decrease significantly. It took a long time to locate the problem, using various methods such as gprof, but still did not find the cause. In the end, I found that it was normal at this time... There are two main reasons,

First, the garbage collection of go has a trigger threshold, which will gradually increase as each memory usage becomes larger (for example, the initial threshold is 10MB, the next time is 20MB, and the next time it becomes 40MB...). If the time does not trigger gc go, it will actively trigger once (2min). After the memory usage increases at the peak, unless you continue to apply for memory, it is basically impossible to trigger gc by the threshold. Instead, you need to wait for at most 2 minutes to start active gc before triggering gc.

The second reason is that when the go language returns the memory to the system, it just tells the system that the memory does not need to be used and can be recycled; at the same time, the operating system will adopt a "procrastination" strategy, not immediately recycling, but waiting until the system memory is tight. Reclamation will start so that the program can get extremely fast allocations when it re-allocates memory.

long gc time problem

For backend programs that require users to respond to events, the stop the world part-time job of golang gc is a nightmare. According to the above introduction, the 1.5 version of go should improve the performance of gc a lot after completing the above improvements, but all garbage-collected languages ​​will inevitably face performance degradation during gc. For this, we should try to avoid frequent creation of temporary heaps Objects (such as &abc{}, new, make, etc.) to reduce the scanning time during garbage collection. For temporary objects that need to be used frequently, consider reusing them directly through the array cache; many people use the cgo method to manage their own memory and bypass garbage collection. , this method is not recommended unless it is absolutely necessary (it is easy to cause unpredictable problems), of course, it can still be considered in the case of a last resort, the effect of this trick is still very obvious~

The problem of goroutine leaks

One of our services needs to handle a lot of persistent connection requests. During implementation, a read and write coroutine is opened for each persistent connection request, and the endless for loop is used to continuously process and send and receive data. When the connection is closed by the remote end, if these two coroutines are not processed, they will continue to run, and the occupied channel will not be released. The dependent channel closes and ensures its exit by judging whether the channel is closed in the coroutine.

 

http://wangzhezhe.github.io/blog/2016/04/30/golang-gc/

 

Golang-gc basics

APR 30TH, 2016 8:02 PM | COMMENTS

This part mainly introduces some related knowledge about the introduction of golang gc. Since the content of gc is more involved, it will be sorted out bit by bit.

Background on Golang GC

  • golang is a language based on garbage collection, which is its design principle.
  • As a language with a garbage collector, the efficiency of gc's interaction with the program will affect the efficiency of the entire program.
  • Usually the memory management of the program itself will affect the efficiency between the gc and the program, and even cause performance bottlenecks.

Golang GC related issues

The main reference is this:

http://morsmachine.dk/machine-gc

It was written in 2014. It is estimated that the gc mechanism at that time was relatively simple. The changes to gc in the new version of golang should be relatively large.

There is also the relevant part about golang gc in the go language reading notes

About memory leaks

The term "Memory Leak" seems familiar to me, but I have never seen its precise meaning.

Memory leak is explained from the perspective of the operating system. The metaphor is that "the storage space (virtual memory space) that the operating system can provide to all processes is being drained by a process", the reason is that the program is running At times, the storage space that will be continuously and dynamically opened up is not released in time after the operation ends. After an application allocates a certain segment of memory, due to a design error, the program will lose control of the segment of memory, resulting in a waste of memory space.

If the program applies for a piece of memory in the memory space, after the program runs, the memory space is not released, and the corresponding program does not have a good gc mechanism to reclaim the space applied by the program, which will lead to Memory leak.

From the user's point of view, the memory leak itself is not harmful, because it is not an impact on user functions, but "memory leak" if the

For languages ​​such as C and C++ without Garbage Collection, we are mainly concerned with two types of memory leaks:

  • Heap leaks. For memory, it refers to a piece of memory allocated from the heap through malloc, realloc new, etc. as needed during program operation, and must be deleted by calling the corresponding free or delete after completion. If the design error of the program causes this part of the memory to not be released, then this memory will not be used after that, and a Heap Leak will be generated.

  • System resource leak (Resource Leak). Mainly refers to the program using the system allocated resources such as Bitmap, handle, SOCKET, etc. without using the corresponding function to release, resulting in a waste of system resources, which can seriously reduce system performance and unstable system operation.

There are still many related issues involved in memory leaks, which will not be discussed here for the time being.

Common GC Patterns

The specific advantages and disadvantages can refer to this , here is just a general introduction.

  • Reference counting (reference counting) Each object maintains a reference counter. When the object referencing the object is destroyed or updated, the reference counter of the referenced object is automatically decremented by 1. When the applied object is created, or assigned to other objects When the reference is +1, it is recycled when the reference is 0. The idea is simple, but frequently updating the reference counter reduces performance, and there is a cycle to reference (used by php, Python)

  • Mark and sweep (mark and sweep) is what golang uses. It traverses all referenced objects from the root variable, performs a cleanup operation after marking, and recycles unmarked objects. Disadvantage: every garbage collection will suspend all normal operations. Running code, the responsiveness of the system will be greatly reduced, various mark&swamp variants (three-color marking method), alleviate performance problems.

  • The generational collection (generation) jvm uses the idea of ​​generational recycling. In object-oriented programming languages, the vast majority of objects have very short lifetimes. The basic idea of ​​generational collection is to divide the heap into two or more spaces called generations. Newly created objects are stored in what is called the young generation (generally, the size of the young generation will be much smaller than the old generation), and objects with longer lifetimes will be promoted as garbage collection is repeated ( promotion) to the old age (a classification idea is used here, which is also a basic idea of ​​scientific thinking).

Therefore, two different garbage collection methods, the new generation garbage collection and the old generation garbage collection, came into being (classify first, and then prescribe the right medicine), which are respectively used to perform garbage collection on objects in their respective spaces. The new generation garbage collection is very fast, several orders of magnitude faster than the old generation. Even if the new generation garbage collection frequency is higher, the execution efficiency is still stronger than the old generation garbage collection. This is because the life cycle of most objects is very short. , there is no need to upgrade to the old age at all.

How gc in golang generally works

The gc in golang is basically the idea of ​​mark removal:

In the memory heap (called heap memory because the heap data structure is sometimes used to manage memory pages), a series of objects are stored, and these objects may be related to other objects (references between these objects) A tracing garbage collector will stop the originally running program at a certain point in time, and then it will scan the already known set of objects that the runtime already knows, usually they are global variables that exist in the stack and various object. GC will mark these objects, mark the state of these objects as reachable, find out all the references of objects in other places that can be reached from these current objects, and mark these objects as reachable objects, This step is called the mark phase, which is the marking phase , and the main purpose of this step is to obtain the state information of these objects.

Once all these objects are scanned, gc will get all unreachable objects (objects whose status is unreachable) and recycle them. This step is called sweep phase, which is the cleaning phase .

gc only collects objects that are not marked as reachable. If gc does not recognize a reference, it may eventually recycle an object that is still in use, causing a program error.

You can see the main three steps: scanning, recycling, cleaning.

It feels that the garbage collection model in golang is relatively simple compared to other languages.

problem in gc

The introduction of gc can be said to solve the problem of memory recycling. When using newly developed languages ​​(java, python, php, etc.), users do not need to care about the release of memory objects, but only need to care about the application of objects. By performing related operations in runtime or in vm, To achieve the effect of automatically managing memory space, this behavior of automatically reclaiming memory resources that are no longer in use is called garbage collection.

According to the previous statement, whether a reference can be recognized normally is the basis for gc to work normally, so the first question is how should gc recognize a reference?

The biggest problem: it is difficult to identify the reference, and it is difficult for the machine code to know what constitutes a reference. If a reference is missed by mistake, it will cause the memory that was not ready to be freed to be freed by mistake, so the strategy is to prefer more than less.

One strategy is to treat all memory spaces as possible references (pointer values). This is called a conservative garbage collector. This is how the Boehm garbage collector in C works. That is to say, treat ordinary variables in memory as pointers, and try to cover all pointers. If it happens that there are other objects in the space pointed to by this ordinary variable value, then this object will not be recycled. The go language implementation is fully aware of the type information of the object, and only traverses the object pointed to by the pointer when marking, thus avoiding the waste of heap memory in the C implementation (solving about 10-30%).

three-color marker

2014/6 1.3 Introduce concurrent cleanup (garbage collection and concurrent execution of user logic?)

2015/8 1.5 Introduced three-color marking method

Regarding the introduction of concurrent cleanup, refer to here in version 1.3, go runtime separates the operations of mark and sweep. As before, it also suspends the execution of all tasks and starts the mark (the mark part still needs to stop the original program. ), the suspended task will be restarted immediately after the mark is completed, and the sweep task will be executed in parallel with other tasks like ordinary coroutine tasks. If running on a multi-core processor, go will try to run the gc task on a separate core without affecting the execution of business code as much as possible. The go team's own statement is to reduce the pause time by 50%-70%.

The basic algorithm is the cleaning + recycling mentioned above. The core of Golang gc optimization is to try to make STW (Stop The World) shorter and shorter.

How to measure GC

So much has been said before, how to measure the star efficiency of gc and judge whether it has an impact on the operation of the program? The first way is to set the environment variable of godebug. For details, please refer to this article, which is really a very good article: link , such as running GODEBUG=gctrace=1 ./myserver, if you want to understand the output result, you need to go further to the principle of gc The advantage of this article is that it is clear which factors determine the gc time of golang, so you can also take different ways to improve the gc time in a targeted manner:

According to the previous analysis, it can also be known that the gc in golang uses the clear marking method, so the total time of gc is:

Tgc = Tseq + Tmark + Tsweep(T means time)

  • Tseq represents the time it takes to stop the user's goroutine and do some preparatory activities (usually small)
  • Tmark is the heap mark time, the mark occurs when all user goroutines are stopped, so can significantly affect the latency of processing
  • Tsweep is the heap cleanup time, the cleanup usually happens concurrently with normal program operation, so it is less critical for latency

After the granularity is further subdivided, the specific concepts are still somewhat unclear:

  • Related to Tmark: 1 The number of live objects in the heap during garbage collection, 2 The total amount of memory occupied by live objects with pointers, and 3 The number of pointers in live objects.
  • Related to Tsweep: 1 The total amount of heap memory 2 The total amount of garbage in the heap

How to do gc tuning (gopher conference Danny)

Hard parameters

When it comes to problems with algorithms, there will always be some parameters. The GOGC parameter mainly controls the memory usage when the next gc starts .

For example, the current program uses 4M of memory (here is the heap memory ), which means that the current reachable memory of the program is 4M, when the memory occupied by the program reaches reachable*(1+GOGC/100)=8M, gc will be triggered and start related gc operations.

How to set the parameters of GOGC should be determined according to the actual scene in the production situation, such as increasing the parameters of GOGC to reduce the frequency of GC.

small tips

If you want to have in-depth insights, it is essential to use gdb. This article organizes some introductory skills for using gdb.

Reducing object allocation  The so-called reduction of object allocation is actually the reuse of objects as much as possible. For example, two function definitions like the following:

1
2
func(r*Reader)Read()([]byte,error)
func(r*Reader)Read(buf[]byte)(int,error)

The first function has no formal parameters, and returns a []byte each time it is called. The second function returns an object of type buf []byte each time it is called, and then returns the number of bytes read. .

The first function allocates a space each time it is called, which puts extra pressure on the gc. The second function reuses the parameter declaration each time it is called.

It is a commonplace to talk about string and []byte conversion. Converting  between stirng and []byte will put pressure on gc. Through gdb, you can first compare the data structures of the two:

1
2
3
type = struct []uint8 {    uint8 *array;    int len;    int cap;}

type = struct string {    uint8 *str;    int len;}

When the two are converted, the underlying data structure will be copied, resulting in lower gc efficiency. In terms of solution strategy, one way is to always use []byte, especially in terms of data transmission, []byte also contains many effective operations that are commonly used in strings. The other is to use a lower-level operation to directly convert to avoid copying. You can refer to the first part of performance optimization in WeChat "Rain Mark School", mainly using unsafe.Pointer for direct conversion.

For the use of unsafe, I feel that I can organize an article separately. First, list the relevant information here  http://studygolang.com/articles/685  Intuitively, unsafe.Pointer can be understood as void* in C++ , in golang, it is equivalent to a bridge for conversion of various types of pointers.

The underlying type of uintptr is int, which can hold the value of the address pointed to by the pointer. It can be converted to and from unsafe.Pointer. The main difference is that uintptr can participate in pointer arithmetic, while unsafe.Pointer can only perform pointer conversion and cannot perform pointer arithmetic. If you want to do pointer arithmetic with golang, you can refer to this . When the specific pointer operation is performed, it must be converted to the type of uintptr before further calculation, such as how much the offset is.

A small amount of use of + to connect strings  will generate new objects and reduce the efficiency of gc due to the use of + to connect strings. The best way is to use the append function.

But there is another drawback, such as referring to the following code:

1
b := make([]int, 1024)   b = append(b, 99)   fmt.Println("len:", len(b), "cap:", cap(b))

After the append operation is used, the space of the array increases from 1024 to 1312, so if the length of the array can be known in advance, it is best to do space planning when the space is initially allocated, which will increase some code management costs, and at the same time It will also reduce the pressure on gc and improve the efficiency of the code.

References

https://talks.golang.org/2015/go-gc.pdf

https://www.zhihu.com/question/21615032

https://blog.golang.org/go15gc

Introduction to golang gc Chinese (the summary is more comprehensive, including the comparison of golang gc in different versions)  http://www.open-open.com/lib/view/open1435846881544.html ( original )

Other Garbage Collection Related Articles

This introduced gc is more systematic:  http://newhtml.net/v8-garbage-collection/

Garbage collector for version 1.5  http://ruizeng.net/go-15-release-notes/

Memory leak referencehttp  ://blog.csdn.net/na_he/article/details/7429171

Go1.5 source code analysis  https://github.com/qyuhen/book

An example of manually managing golang gc (more in-depth content)  http://my.oschina.net/lubia/blog/175154

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325787093&siteId=291194637