逃逸分析
逃逸分析(Escape Analysis)指的是将变量的内存分配在合适的地方(堆或者栈)。
在函数中申请内存有2种情况:
- 如果内存分配在栈(stack)上,当函数退出的时候,这部分内存会自然的回收,不需要垃圾回收(GC,Garbage Collection)
- 如果内存分配在堆(heap)上,函数的执行会交给GC(Garbage Collection)来处理。
此外,Golang与闭包密切相关。
Golang的逃逸分析
Golang的逃逸分析的基本原则是:如果函数返回了变量的引用,那么这个变量就会逃逸。
编译器通过分析代码,决定变量分配的地方:
- 如果变量在函数外没有被引用,那么优先分配在栈(stack)上。
- 如果变量在函数外被引用,那么优先分配在堆(heap)上。
需要注意的是,没有在函数外被引用的变量,也有可能被分配在堆(heap)上。例如,这个变量需要的内存太大,超出了栈的容量,(目前,一个Goroutine的栈的最大容量,在64位系统是1GB,在32位系统是250MB)。栈内存的分配和回收是非常快速的,只需要2条CPU指令,PUSH
和 RELEASE
。而堆内存,分配需要找到合适大小的内存块,回收则是通过GC。
因此,通过内存的逃逸分析,可以尝试将不必要分配在堆上的变量分配在栈上,减少分配堆内存的开销和GC的压力。下面看一下一些逃逸的例子。
指针逃逸
看下面的代码cat.go
。
1 | package main |
进行逃逸分析
1 | $ go build -gcflags="-m" cat.go |
可以看到,./cat.go:10:10: new(Cat) escapes to heap
, 有变量的内存逃逸。
动态类型逃逸
1 | package main |
编译代码
1 | $ go build -gcflags="-m -l" dynamic.go |
变量s
产生了内存逃逸,正如前一个例子。
这里要注意的是,/dynamic.go:11:14: *x escapes to heap
也发生了内存逃逸,这是因为fmt.Println(a ...interface{})
,fmt
接受的参数是interface{}
,这是类型不确定的。
编译期间不能确定参数的具体的类型,逃逸就会产生。
slice,map和channel的指针引用
1 | package main |
编译代码
1 | go run -gcflags "-m -l" main.go |
在这里,变量b
,d
,f
的内存都被移动到堆上,因为,Golang中,slice,map,channel引用指针的变量,一定会逃逸。
Golang中,slice,map,channel对指针的引用会比之保留变量的slice,map,channel性能低,这里是根本原因。
闭包
1 | func Fibonacci() func() int { |
编译
1 | $ go build -gcflags="-m" fib.go |
在Fibonacci()
函数中,a,b是一个本地的变量,因为被闭包引用,所以被分配在了堆上。
栈容量不足
看下面的代码:
1 | func BigSlice() { |
BigSlice()
分配一个长度为1000的int数组,变量s是否会内存逃逸,取决与栈容量的大小是否足够。
1 | $ go build -gcflags="-m" big.go |
这里,没有产生内存逃逸,如果将slice的长度增长10倍,就会产生逃逸。
1 | $ go build -gcflags="-m" big.go |
我的公众号:lyp_share