go escape analysis

1 Preface

The so-called Escape analysis means that the compiler determines the location of memory allocation, which does not need to be specified by the programmer.
Function to request a new object

  • If the allocation is in the stack, the memory can be recycled automatically after the function is executed;
  • If the allocation is in the heap, the function execution can be handed over to GC (garbage collection) for processing;

With escape analysis, local variables of return function will become possible. In addition, escape analysis is also closely related to closures. It is very important to understand which scenes objects will escape.

2 Escape strategy

Whenever a new object is requested in a function, the compiler will decide whether to escape according to whether the object is externally referenced by the function:

  1. If there is no reference outside the function, it will be put on the stack first;
  2. If there is a reference outside the function, it must be placed in the heap;

Note that objects that are not referenced outside the function may also be placed in the heap. For example, the memory is too large and exceeds the storage capacity of the stack.

3 escape scene

3.1 pointer escape

We know that Go can return local variable pointers, which is actually a typical variable escape case. The example code is as follows:

package main

type Student struct {
    Name string
    Age  int
}

func StudentRegister(name string, age int) *Student {
    s := new(Student) //The local variable s escapes to the heap

    s.Name = name
    s.Age = age

    return s
}

func main() {
    StudentRegister("Jim", 18)
}

s in the function StudentRegister() is a local variable, and its value is returned through the return value of the function. s itself is a pointer, and the memory address it points to is not a stack but a heap. This is a typical escape case.

The escape analysis during compilation can be viewed through the compilation parameter - gcflag=-m:

D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:8: can inline StudentRegister
.\main.go:17: can inline main
.\main.go:18: inlining call to StudentRegister
.\main.go:8: leaking param: name
.\main.go:9: new(Student) escapes to heap
.\main.go:18: main new(Student) does not escape

It can be seen that in the StudentRegister() function, that is, line 9 of the code displays "escapes to heap", which indicates that the memory allocation in this line has escaped.

3.2 escape due to insufficient stack space

Look at the following code. Will escape occur?

package main

func Slice() {
    s := make([]int, 1000, 1000)

    for index, _ := range s {
        s[index] = index
    }
}

func main() {
    Slice()
}

A slice of 1000 lengths is allocated in the Slice() function of the above code. Whether it escapes depends on whether the stack space is large enough.
View the compilation tips directly as follows:

D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:4: Slice make([]int, 1000, 1000) does not escape

We found no escape here. So what about expanding the slice length by 10 times, that is, 10000?

D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:4: make([]int, 10000, 10000) escapes to heap

We found that when the slice length was expanded to 10000, it would escape.

In fact, when the stack space is insufficient to store the current object or the current slice length cannot be determined, the object will be allocated to the heap.

3.3 dynamic type escape

Many function parameters are of interface type, such as FMT Println (a... Interface {}), it is difficult to determine the specific type of its parameters during compilation, and escape will also occur.
The following code is shown:

package main

import "fmt"

func main() {
    s := "Escape"
    fmt.Println(s)
}

The s variable in the above code is just a string variable. Call FMT Escape occurs when println():

D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:7: s escapes to heap
.\main.go:7: main ... argument does not escape

3.4 closure reference object escape

A famous open source framework implements a function that returns Fibonacci sequence:

func Fibonacci() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}

The function returns a closure, which refers to the local variables A and b of the function. When used, the closure is obtained through the function, and then the Fibonacci sequence will be output each time the closure is executed.
The complete sample program is as follows:

package main

import "fmt"

func Fibonacci() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}

func main() {
    f := Fibonacci()

    for i := 0; i < 10; i++ {
        fmt.Printf("Fibonacci: %d\n", f())
    }
}

The above code obtains a closure through Fibonacci(), and prints a Fibonacci value every time the closure is executed. The output is as follows:

D:\SourceCode\GoExpert\src>src.exe
Fibonacci: 1
Fibonacci: 1
Fibonacci: 2
Fibonacci: 3
Fibonacci: 5
Fibonacci: 8
Fibonacci: 13
Fibonacci: 21
Fibonacci: 34
Fibonacci: 55

In Fibonacci() function, a and b, which originally belonged to local variables, had to be put on the heap due to the reference of closures, resulting in escape:

D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:7: can inline Fibonacci.func1
.\main.go:7: func literal escapes to heap
.\main.go:7: func literal escapes to heap
.\main.go:8: &a escapes to heap
.\main.go:6: moved to heap: a
.\main.go:8: &b escapes to heap
.\main.go:6: moved to heap: b
.\main.go:17: f() escapes to heap
.\main.go:17: main ... argument does not escape

4 escape summary

  • Allocating memory on the stack is more efficient than allocating memory in the heap
  • Memory allocated on the stack does not require GC processing
  • When the memory allocated on the heap is used up, it will be handed over to GC for processing
  • The purpose of escape analysis is to determine whether the internal allocation address is stack or heap
  • Escape analysis is completed in the compilation phase

5 programming Tips

Consider this question: is it really more efficient for functions to pass pointers than values?
We know that passing the pointer can reduce the copy of the underlying value and improve the efficiency. However, if the amount of copied data is small, the pointer passing will cause escape, which may use the heap or increase the burden of GC. Therefore, passing the pointer is not necessarily efficient.

Original text https://www.topgoer.cn/docs/gozhuanjia/chapter044.3-escape_analysis

Added by phpsir on Wed, 19 Jan 2022 22:56:01 +0200