Let's talk about analyzing golang's escape

藏色散人
Release: 2021-07-01 14:56:15
forward
2049 people have browsed it

Translated from: http://www.agardner.me/golang/garbage/collection/gc/escape/analysis/ 2015/10/18/go-escape-analysis.html

Garbage collection is a very convenient feature of Go-its automatic memory management makes the code cleaner, Also reduces the possibility of memory leaks. However, since garbage collection requires periodic stopping of the program to collect unused objects, additional overhead will inevitably be added. The Go compiler is smart and automatically determines whether a variable should be allocated on the heap for easy collection in the future, or allocated directly to the function's stack space. For variables allocated on the stack, the difference from variables allocated on the heap is that as the function returns, the stack space will be destroyed, so the variables on the stack are directly destroyed without additional garbage collection overhead.

Go's escape analysis is more basic than HotSpot of the Java virtual machine. The basic rule is that if a reference to a variable is returned from the function in which it is declared, an "escape" occurs. Because it may be used by other content outside the function, it must be allocated on the heap. The following situations will be more complicated:

  • Functions calling other functions
  • Referencing member variables as structures
  • Slicing and mapping
  • Cgo Pointer to variable

In order to implement escape analysis, Go will construct a function call graph during the compilation phase, while tracking the process of input parameters and return values. If a function only references a parameter, but the reference does not return the function, the variable will not escape. If a function returns a reference, but the reference is released by another function on the stack or does not return the reference, there is no escape. In order to demonstrate several examples, you can add the -gcflags '-m' parameter during compilation. This parameter will print the detailed information of escape analysis:

package main

type S struct {}

func main() {
    var x S
    _ = identity(x)
}

func identity(x S) S {
    return x
}
Copy after login

You can execute go run -gcflags '-m -l' (Note: the go code file name is omitted in the original text) to compile this code. The -l parameter prevents function identity from being inlined (we will discuss inlining another time) this topic). You will see no output! Go uses value transfer, so the x variable in the main function will always be copied to the stack space of the function identity. Usually code that does not use references allocates memory through stack space. So no escape analysis is involved. Let's try the more difficult one:

package main

type S struct {}

func main() {
    var x S
    y := &x
    _ = *identity(y)
}

func identity(z *S) *S {
    return z
}
Copy after login

The corresponding output is:

./escape.go:11: leaking param: z to result ~r1
./escape.go:7: main &x does not escape
Copy after login

The first line shows the "flow" of the variable z: the input parameter is directly used as The return value is returned. However, function identity did not take away the reference z, so no variable escape occurred. After the main function returns, no reference to x exists, so the x variable can be allocated memory in the stack space of the main function.
The third experiment:

package main

type S struct {}

func main() {
  var x S
  _ = *ref(x)
}

func ref(z S) *S {
  return &z
}
Copy after login

The output is:

./escape.go:10: moved to heap: z
./escape.go:11: &z escapes to heap
Copy after login

Now there is an escape. Remember that Go is pass-by-value, so z is a copy of the variable x. Function ref returns a reference to z, so z cannot be allocated on the stack, otherwise when function ref returns, the reference will Where does it point? So it escaped into the heap. In fact, after executing ref and returning to the main function, the main function discards the reference instead of dereferencing it, but Go's escape analysis is not smart enough to identify it. This situation.
It's worth noting that in this case, if we don't stop the reference, the compiler will inline ref.
What happens if the structure members are defined as references?

package main

type S struct {
  M *int
}

func main() {
  var i int
  refStruct(i)
}

func refStruct(y int) (z S) {
  z.M = &y
  return z
}
Copy after login

The output is:

./escape.go:12: moved to heap: y
./escape.go:13: &y escapes to heap
Copy after login

In this case, even though the reference is a member of the structure, Go still tracks the flow of the reference. Since function refStruct accepts a reference and returns it, y must escape. Compare the following example:

package main

type S struct {
  M *int
}

func main() {
  var i int
  refStruct(&i)
}

func refStruct(y *int) (z S) {
  z.M = y
  return z
}
Copy after login

The output is:

./escape.go:12: leaking param: y to result z
./escape.go:9: main &i does not escape
Copy after login

Although the i variable is referenced in the main function and passed In function refStruct, but the scope of this reference does not exceed the stack space where it is declared. This is slightly different in semantics from the previous program, and this one will be more efficient: in the previous program, the variable i must be allocated on the stack of the main function and then used as a parameter Copy it to function refStruct, and allocate the copied copy on the heap. In this example, i is assigned only once and then the reference is passed around.

Let’s look at a somewhat convoluted example:

package main

type S struct {
  M *int
}

func main() {
  var x S
  var i int
  ref(&i, &x)
}

func ref(y *int, z *S) {
  z.M = y
}
Copy after login

The output is:

./escape.go:13: leaking param: y
./escape.go:13: ref z does not escape
./escape.go:9: moved to heap: i
./escape.go:10: &i escapes to heap
./escape.go:10: main &x does not escape
Copy after login

问题在于,y被赋值给了一个入参结构体的成员。Go并不能追溯这种关系(go只能追溯输入直接流向输出),所以逃逸分析失败了,所以变量只能分配到堆上。由于Go的逃逸分析的局限性,许多变量会被分配到堆上,请参考此链接,这里面记录了许多案例(从Go1.5开始)。

最后,来看下映射和切片是怎样的呢?请记住,切片和映射实际上只是具有指向堆内存的指针的Go结构:slice结构是暴露在reflect包中(SliceHeader
)。map结构就更隐蔽了:存在于hmap。如果这些结构体不逃逸,将会被分配到栈上,但是其底层的数组或者哈希桶中的实际数据会被分配到堆上去。避免这种情况的唯一方法是分配一个固定大小的数组(例如[10000]int)。

如果你剖析过你的程序堆使用情况(https://blog.golang.org/pprof
),并且想减少垃圾回收的消耗,可以将频繁分配到堆上的变量移到栈上,可能会有较好的效果。进一步研究HotSpot JVM是如何进行逃逸分析的会是一个不错的话题,可以参考这个链接,这个里面主要讲解了栈分配,以及有关何时可以消除同步的检测。

更多golang相关技术文章,请访问golang教程栏目!

The above is the detailed content of Let's talk about analyzing golang's escape. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:segmentfault.com
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact [email protected]
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!