以前在 C++编程中要自己识别对象是在堆上还是栈上,如果把栈上地址(指针)返回了可能导致严重问题;同样,如果用 new/malloc 在堆上申请空间,可能造成内存泄漏问题。 Go 编译器实现了自动逃逸分析,变量对象的具体申请空间是根据分析情况决定的,这样省去了我们需要主动识别的烦恼,但同时带来了潜在的性能问题,需要注意。
-
优点:
- 对开发人员消除了堆和栈的区别,降低了复杂度
- 自动分析,将不逃逸的创建在栈上——速度快;将逃逸的放在堆上,更灵活;
-
编译时开启逃逸分析日志:
go build -gcflags '-m -l'
基础知识
-
堆的特点:
- 分配空间大、无需提前知道容量大小
- 分配速度慢,是栈上的 100 倍
-
栈的特点:
- 分配速度快
- 分配空间相对小(Go 有优化)、需要在编译期就预测出容量
Go 逃逸分析知识点
- 编译器会分析代码的特征和代码生命周期,Go 中的变量只有在编译器可以证明在函数返回后不会再被引用的,才分配到栈上,其他情况下都是分配到堆上。
- 例如
fmt.Println(...)
函数,由于其中用到了反射导致编译器很难判定,所以变量就会分配到堆上
- 例如
- 关键词
new
并不能保证在堆上分配,仍然受编译器逃逸分析的控制 - 函数的入参在 C++等语言中被作为函数的局部变量在栈上申请,但是在 Go 中同样由编译器逃逸分析决定
- 有些时候,虽然分析结果为”没有逃逸”,但是由于对象容量过大、影响总体效率,还是会被分配在堆上
- 不要盲目的使用结构体对象指针作为函数参数,虽然可以减少复制,但是在栈上复制传递的开销要远比在堆上创建的开销要小,还要根据具体情况而定
- 在写代码时,尽量少写逃逸的变量,可以提高程序运行效率