本贴转自https://www.cnblogs.com/itbsl/p/10476674.html
如侵权,请联系QQ359152155删除。
Go变量逃逸分析
逃逸分析
决定一个变量是分配在堆上还是栈上。
逃逸分析
这种操作把变量合理地分配到它该去的地方,即使你是用new申请到的内存,如果我发现你竟然在退出函数后就没有用了,那么就把你丢到栈上,毕竟栈上的内存分配比堆上快得多;反之,即使你表面上只是一个普通的变量,但是经过逃逸分析后发现在退出函数之后还有其他地方在引用,那我就把你分配到堆上。
如果变量都分配到堆上,堆不像栈可以自动清理,它会引起Go频繁地进行垃圾回收,而垃圾回收会占用比较大的系统开销。
通过逃逸分析,可以尽量把哪些不需要分配到对上的变量直接分配到栈上,堆上的变量少了,会减轻分配堆内存的开销,同时也会减少GC的压力,提高程序的运行速度。
逃逸分析是怎么完成的
Go逃逸分析的最基本原则是:如果一个函数返回堆一个变量的引用,那么它就会发生逃逸。
编译器会分析代码的特征和代码生命周期,Go中的变量只有在编译器可以证明在函数返回后不会再被引用,才会分配到栈上,其他情况下都是分配到堆上。
逃逸分析实例
Go提供了相关的命令,可以查看变量是否发生逃逸。
用下面这个例子:
package main
import "fmt"
func foo() *int {
t := 3
return &t;
}
func main() {
x := foo()
fmt.Println(*x)
}
foo函数返回一个局部变量的指针,main函数里变量x接收它。执行如下命令:
go build -gcflags="-m -l" main.go
加-l
是为了不让foo函数被内联,得到如下输出:
# command-line-arguments
.\hello.go:6:5: moved to heap: t
.\hello.go:12:16: ... argument does not escape
.\hello.go:12:17: *x escapes to heap
foo函数里的变量t逃逸了,和我们预想的一致。让我们不解的是为什么main函数里的*x也逃逸了?这是因为fmt.Println()函数的参数是interface类型,编译期间很难确定其参数的具体类型,也会发生逃逸。
总结
堆上动态分配内存比栈上静态分配内存,开销大很多。
Go编译器会在编译器分析变量的生命周期,编译器会根据变量是否在函数返回后仍旧被引用来决定是否逃逸。对于Go程序原来说,编译器的这些逃逸分析规则不需要掌握,我们只需通过`go build -gcflags="-m"命令来观察变量逃逸情况就行了。
不要盲目使用变量的指针作为函数参数,虽然它会减少复制操作,但其实当参数为变量自身的时候,复制是在站上完成的操作,开销远比变量逃逸后动态地在堆上分配内存少的多。
最后,尽量写出少一些逃逸的代码,提升程序的运行效率。