Golang’s garbage collection mechanism allows automatic memory management to make our code cleaner and less likely to leak memory. However, the GC periodically stops and collects unused objects, so it still adds overhead to the program. The Go compiler is smart enough to decide, for example, whether a variable needs to be allocated on the heap or the stack, and unlike an allocation on the heap, a variable on the stack is reclaimed at the end of the function that declared it. Then, for GC purposes, variables allocated on the stack do not incur additional overhead, and the entire call stack of the function is destroyed after the function returns.
How does Golang decide whether a variable should be allocated on the heap or the stack? This brings us to Golang’s Escape Analysis.
The basic rule for determining escape is that if the return value of a function is a reference to a variable declared within the function, then the variable is said to have escaped from the function. As the return value of this function, it can also be modified by other programs outside the function, so it must be allocated on the heap and not on the stack of that function.
Therefore, if we can analyze the escape of variables during the compilation process, we can improve the performance of our program. First of all, the biggest benefit is to reduce the pressure of garbage collection, no escaped variables are allocated on the stack, and the function can directly recover resources when it returns; secondly, after the escape analysis, we can determine which variables can actually be allocated on the stack, which is faster than the heap and has better performance; there is also the possibility of synchronization elimination, if the function that defines the variables has a synchronization lock, but only one thread accesses it at runtime, the At this point the machine code after escaping the analysis will run with the synchronous lock removed.
Turn on Go compile-time escape analysis log
At compile time, add the
-gcflags '-m' parameter to see a detailed escape analysis log of the go compilation process. However, to prevent Go from automatically inlining functions at compile time, the
-l parameter is added, ending up with
-gcflags '-m -l'.
You can see that there is no output. We know that Go uses pass-by-value when calling functions, so the
x declared in the
main function is copied to the stack of the
identity() function. Usually, code without references always uses stack allocation, so there is no output from the escape analysis log.
So if you change the code a bit.
The first line is that the
z variable is meant to flow through a function, only as input to the function, and is returned directly, and no reference to
z is used in
identity(), so the variable is not escaped.
In the second line,
x is declared in the
main() function, so it is on the stack of the
main() function, and there is no escape.
You can see that an escape has occurred. The argument
ref() is passed by value, so
z is a copy of the value
x in the
main() function, and
ref() returns a reference to
z cannot be placed on the stack of
ref(), but is actually assigned to the heap.
In fact, we find that the
main() function does not use the reference returned by
ref() directly, in which case
z can actually be assigned to the stack of
ref(), but Go’s escape analysis is not sophisticated enough to identify this case; it only looks at the flow of input and returned variables. It’s worth noting that if we don’t add the
ref() will actually be used by the compiler inline with
What if the reference is assigned to a member of a structure?
It can be found that Go’s escape analysis can track references even if the reference is a member of a structure. When the structure
refStruct is returned,
y must have escaped from
Compare this to the following example.
The reason this
y doesn’t escape is that
refStruct() with a reference to
i and returns directly, never exceeding the call stack of the
main() function, for the same reason as Example 1.
Another note: Example 4 is more efficient than Example 3.
In Example 3,
i has to request a stack space on the stack of
main(), and after
y has to request another space on the heap; in Example 4, only
i actually requests a space once, and then its reference goes through
A more complex example.
It is understandable that
z are not escaped, but the problem is that
y is also assigned to a member of the input
z of the function
ref(), and Go’s escape analysis cannot track the relationship between variables and does not know that
i becomes a member of
x, and the analysis result says that
i is escaped, but essentially
i is not escaped. This is a problem.
Here are some more examples of perversions that have been assigned to the heap because of Go’s lack of escape analysis: https://docs.google.com/document/d/1CxgUBPlx9iJzkz9JWkb6tIpTe5q32QDmz8l0BouG0Cw/preview
In fact, all this is to show that if you want to reduce garbage collection time and improve program performance, you should avoid allocating space on the heap as much as possible, so you can think about this aspect more when writing your program. The following is an example of how to reduce garbage collection time and improve performance.)