前提
-
栈区(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:不让参数内联
得到结果
r 从函数中逃逸了
也可以使用反汇编命令也可以看出变量是否发生逃逸。
go tool compile -S main.go
总结
-
通过逃逸分析,可以尽量把不需要分配到堆上的变量分配到栈上。堆上的变量少了,会减轻分配堆内存的开销,也会减少gc的压力,提高程序的运行速度。
-
不要盲目使用变量的指针作为函数参数,虽然它会减少复制操作。但其实当参数为变量自身时,复制是在栈上完成的,开销远比变量逃逸后动态地在堆上分配内存少的多。
-
尽量写出少一些逃逸的代码,提升程序的运行效率。