在Go里面定义了一个变量,到底是分配在堆上还是栈上,Go官方文档告诉我们,不需要管,他们会分析,其实这个分析就是逃逸分析
通俗来讲,当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了逃逸。
Golang内存分配的基本原则如果函数外部没有引用,则优先放到栈中;
如果函数外部存在引用,则必定放到堆中;
如果一个变量过大,则有可能分配在堆上
逃逸分析变量在栈或是堆上分配内存,是由编译器决定的
在 build 的时候,通过添加 -gcflags "-m" 编译参数就可以查看编译过程中的逃逸分析
为什么要进行逃逸分析提到逃逸分析,不得不提的是堆分配和栈分配的差异。
堆适合不可预知的大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。堆分配带来的另一大问题是gc,gc会消费cpu的时间。减少内存逃逸则直接降低了gc处理的时间。
栈内存分配则会非常快,栈分配内存只需要两个CPU指令:“PUSH”和“RELEASE”分配和释放;而堆分配内存首先需要去找到一块大小合适的内存块。之后要通过垃圾回收才能释放。
案例指针逃逸
代码
1 package main
2
3 func main() {
4 test()
5 }
6
7 func test() *int {
8 i := 1
9 return &i
10 }
编译
% go build -gcflags "-m"
./main.go:7:6: can inline test
./main.go:3:6: can inline main
./main.go:4:9: inlining call to test
./main.go:8:5: moved to heap: i
解释
虽然i是test函数声明的一个局部变量,但golang支持变量i的作用域扩展到函数外部,其方式就是通过内存逃逸,将该变量在heap上分配。
栈空间不足导致的逃逸
代码
func main() {
stack()
}
func stack() {
s := make([]int, 100000, 100000)
s[0] = 1
}
编译
% go build -gcflags "-m"
./main.go:7:6: can inline stack
./main.go:3:6: can inline main
./main.go:4:10: inlining call to stack
./main.go:4:10: make([]int, 100000, 100000) escapes to heap
./main.go:8:14: make([]int, 100000, 100000) escapes to heap
解释
栈的大小是有限制的,如果变量过大,则直接分配到堆上。
如果将上述case中slice的cap设置为10,使用同样的方法分析,可以不会发生内存逃逸。
使用new的内存分配方式同make是一致的。
动态类型逃逸
代码
3 func main() {
4 dynamic()
5 }
6
7 func dynamic() interface{} {
8 i := 0
9 return i
10 }
编译
% go build -gcflags "-m"
./main.go:7:6: can inline dynamic
./main.go:3:6: can inline main
./main.go:4:12: inlining call to dynamic
./main.go:4:12: i does not escape
./main.go:9:5: i escapes to heap
解释
我们可以看到dynamic函数被inline处理掉了,所以变量i声明的地方由代码中的第8行被标识到第4行。因为dynamic函数的返回值interface{}是一个动态类型,在编译期不同确定动态类型的实际使用空间,因此i被逃逸分配到heap上了。
闭包逃逸
代码
3 func main() {
4 f := fibonacci()
5 for i := 0; i < 10; i++ {
6 f()
7 }
8 }
9
10 func fibonacci() func() int {
11 a, b := 0, 1
12 return func() int {
13 a, b = b, a+b
14 return a
15 }
16 }
编译
% go build -gcflags "-m"
./main.go:12:12: can inline fibonacci.func1
./main.go:11:5: moved to heap: a
./main.go:11:8: moved to heap: b
./main.go:12:12: func literal escapes to heap
解释
不仅变量a、b发生了逃逸,返回值函数对象也被分配到堆上了
参考文献https://www.jianshu.com/p/dcd87431a8af
https://segmentfault.com/a/1190000020086727?utm_source=sf-similar-article
https://zhuanlan.zhihu.com/p/113643434