翻译自:http://www.agardner.me/golang/garbage/collection/gc/escape/analysis/2015/10/18/go-escape-analysis.html
垃圾回收是Go的一个很方便的特性--其自动的内存管理使代码更整洁,同时减少内存泄漏的可能性。但是,由于垃圾回收需要周期性的停止程序从而去收集不用的对象,不可避免的会增加额外开销。Go编译器是智能的,它会自动决定一个变量是应该分配在堆上从而在将来便于回收,还是直接分配到函数的栈空间。对于分配到栈上的变量,其与分配到堆上的变量不同之处在于:随着函数的返回,栈空间会被销毁,从而栈上的变量被直接销毁,不需要额外的垃圾回收开销。
Go的逃逸分析相对于Java虚拟机的HotSpot来说更为基础。基本规则就是,如果一个变量的引用从声明它的函数中返出去了,则发生“逃逸”,因为它有可能在函数外被别的内容使用,所以必须分配到堆上。如下几种情况会比较复杂:
- 函数调用其他函数
- 引用作为结构体的成员变量
- 切片和映射
- Cgo指向变量的指针
-gcflags '-m'
package main type S struct {} func main() { var x S _ = identity(x) } func identity(x S) S { return x }
go run -gcflags '-m -l'identitymainxidentity
package main type S struct {} func main() { var x S y := &x _ = *identity(y) } func identity(z *S) *S { return z }
其对应的输出是:
./escape.go:11: leaking param: z to result ~r1 ./escape.go:7: main &x does not escape
zidentityzmainxxmain
package main type S struct {} func main() { var x S _ = *ref(x) } func ref(z S) *S { return &z }
其输出为:
./escape.go:10: moved to heap: z ./escape.go:11: &z escapes to heap
zxrefzzrefrefmainmainref
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 }
其输出为:
./escape.go:12: moved to heap: y ./escape.go:13: &y escapes to heap
refStructy
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 }
其输出为:
./escape.go:12: leaking param: y to result z ./escape.go:9: main &i does not escape
mainirefStructimainrefStructi
再来看一个有点弯弯绕的例子:
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 }
其输出为:
./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
y
slicereflectmap[10000]int
如果你剖析过你的程序堆使用情况(https://blog.golang.org/pprof
),并且想减少垃圾回收的消耗,可以将频繁分配到堆上的变量移到栈上,可能会有较好的效果。进一步研究HotSpot JVM是如何进行逃逸分析的会是一个不错的话题,可以参考这个链接,这个里面主要讲解了栈分配,以及有关何时可以消除同步的检测。