Go 逃逸分析

72 阅读2分钟

前提

  • 栈区(stack):

    • 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。
    • 操作方式类似于数据结构中的栈。
    • 内存分配比堆上快很多。
  • 堆区(heap):

    • 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。
    • 它与数据结构中的堆是两回事。
    • 适合不可预知大小的内存分配。
    • 会引起Go频繁地进行垃圾回收,而垃圾回收会占用比较大的系统开销。
    • 会形成内存碎片。
  • 我们通过 New() 函数获得的内存并非一定在堆上分配,而是由 Go 编译器先进行逃逸分析再进行分配

    • 如果该变量退出函数后没有用了,就将其分配到栈上
    • 反之,即使你表面上只是一个普通的变量,但是经过逃逸分析后发现在退出函数之后还有其他地方在引用,则分配到堆上。

实例

package main

func main(){
	x := f()
	print(x)
}

func f() *int{
	r := 1
	return &r
}

执行

go build -gcflags '-m -l' main.go
  • -gcflags:垃圾回收参数
  • -l:不让参数内联

得到结果
image.png r 从函数中逃逸了

也可以使用反汇编命令也可以看出变量是否发生逃逸。

go tool compile -S main.go

image.png

总结

  • 通过逃逸分析,可以尽量把不需要分配到堆上的变量分配到栈上。堆上的变量少了,会减轻分配堆内存的开销,也会减少gc的压力,提高程序的运行速度。

  • 不要盲目使用变量的指针作为函数参数,虽然它会减少复制操作。但其实当参数为变量自身时,复制是在栈上完成的,开销远比变量逃逸后动态地在堆上分配内存少的多。

  • 尽量写出少一些逃逸的代码,提升程序的运行效率。