Golang memory escape analysis

Keywords: Go Back-end


A variable is defined in Go. Whether it is allocated on the heap or on the stack. The official Go document tells us that it doesn't need to be controlled. They will analyze it. In fact, this analysis is escape analysis

Generally speaking, when the pointer of an object is referenced by multiple methods or threads, we call the pointer an escape.

Basic principles of Golang memory allocation

If there is no reference outside the function, it will be put on the stack first;

If there is a reference outside the function, it must be placed in the heap;

If a variable is too large, it may be allocated on the heap

escape analysis

Variables allocate memory on the stack or heap, which is determined by the compiler

When build ing, you can view the escape analysis in the compilation process by adding the - gcflags "-m" compilation parameter

Why escape analysis

When it comes to escape analysis, we have to mention the difference between heap allocation and stack allocation.

Heap is suitable for memory allocation of unpredictable size. However, the price paid for this is that the distribution speed is slow and will form Memory fragmentation . Another big problem with heap allocation is gc, which consumes cpu time. Reducing memory escape directly reduces the gc processing time.

Stack memory allocation will be very fast. Stack memory allocation requires only two CPU instructions: "PUSH" and "RELEASE" allocation and RELEASE; For heap memory allocation, you first need to find a memory block of appropriate size. Then it can be released through garbage collection.


Pointer escape


  1 package main
  3 func main() {
  4     test()
  5 }
  7 func test() *int {
  8     i := 1
  9     return &i
10 }


% go build -gcflags "-m"
./main.go:7:6: can inline test
./main.go:3:6: can inline main
./main.go:4:9: inlining call to test
./main.go:8:5: moved to heap: i


Although i is a local variable declared by the test function, golang supports the extension of the scope of variable i outside the function by allocating the variable on heap through memory escape.

Escape caused by insufficient stack space


func main() {

func stack() {
    s := make([]int, 100000, 100000)
    s[0] = 1


% go build -gcflags "-m"
./main.go:7:6: can inline stack
./main.go:3:6: can inline main
./main.go:4:10: inlining call to stack
./main.go:4:10: make([]int, 100000, 100000) escapes to heap
./main.go:8:14: make([]int, 100000, 100000) escapes to heap


The stack size is limited. If the variable is too large, it will be allocated directly to the heap.

If the cap of slice in the above case is set to 10 and analyzed in the same way, memory escape will not occur.

The memory allocation method using new is the same as that of make.

Dynamic type escape


  3 func main() {
  4     dynamic()
  5 }
  7 func dynamic() interface{} {
  8     i := 0
  9     return i
 10 }


% go build -gcflags "-m"
./main.go:7:6: can inline dynamic
./main.go:3:6: can inline main
./main.go:4:12: inlining call to dynamic
./main.go:4:12: i does not escape
./main.go:9:5: i escapes to heap


We can see that the dynamic function is processed by inline, so the declaration of variable i is identified from line 8 to line 4 in the code. Because the return value interface {} of the dynamic function is a dynamic type, the actual use space of the dynamic type is determined at different compile times, so i is allocated to heap.

Closure escape


  3 func main() {
  4     f := fibonacci()
  5     for i := 0; i < 10; i++ {
  6         f()
  7     }
  8 }
 10 func fibonacci() func() int {
 11     a, b := 0, 1
 12     return func() int {
 13         a, b = b, a+b
 14         return a
 15     }
 16 }


% go build -gcflags "-m"
./main.go:12:12: can inline fibonacci.func1
./main.go:11:5: moved to heap: a
./main.go:11:8: moved to heap: b
./main.go:12:12: func literal escapes to heap


Not only the variables a and b escape, but also the return value function object is allocated to the heap





Posted by jamessw on Fri, 03 Dec 2021 11:56:30 -0800