一、什么是逃逸分析
在函数内部定义了一个局部变量,然后返回这个局部变量的地址(指针)。由于这些局部变量是在栈上分配的(静态内存分配),一旦函数执行完毕,变量占据的内存被销毁,任何对这个返回值的动作(如解引用),都将扰乱程序的运行,甚至导致程序直接崩溃。比如下面的这段代码:
int *foo ( void)
{
int t = 3;
return &t;
}
解决办法:在函数内部使用new函数构造一个变量(动态内存分配),然后返回此变量的地址。因为变量是在堆上创建的,所以函数退出时不会被销毁。但是,这样就行了吗?new出来的对象该在何时何处地delete呢?调用者可能忘记delete或者直接拿掉返回值传给其他函数,之后就再也不能delete它了,也就是发生可内存泄露。
而这一切在Go语言中就大不相同了。像上面示例的C++代码放到Go里,没有任何问题。
通俗来讲,当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了逃逸。
更简单的来说,逃逸分析决定了一个变量是分配在堆上还是分配在栈上。
二、为什么需要逃逸分析
逃逸分析这种操作把变量合理的分配到了它该去的地方,”找准自己的位置“。即使你是用new申请的内存,如果我发现你竟然在退出函数后没有用了,那么就把你丢到栈上,毕竟栈上的内存分配比堆上快很多;反之,即使你表面上只是一个普通的变量,但是经过逃逸分析后发现退出函数之后还有其他地方在引用,那我就把你分配到堆上。真正的按需分配,提前实现共产主义
如果变量都分配到堆上,堆不像栈可以自动清理。它会引起go频繁的进行垃圾回收,二垃圾回收会占用比较大的系统开销(占用cpu容量的25%)。
通过逃逸分析,可以尽量把那些不需要分配到堆上的变量直接分配到栈上,堆上的变量少了,会减轻分配堆内存的开销,同时减轻gc的压力,提高程序的原型速度。
三、Go中的逃逸准则
Go中逃逸分析最基本的原则是:如果一个函数返回对一个变量的引用,那么它就发生逃逸。
简单来说,编译器会根据变量是否被外部引用来决定是否逃逸:
1、如果函数外部没有引用,则优先放到栈中;
2、如果函数外部存在引用,则必定放到堆中;
如何查看是否发生逃逸
olang提供了编译的参数让我们可以直观地看到变量是否发生了逃逸,只需要在go build时指定
go build -gcflags '-m -l' main.go
除了使用编译参数之外,我们还可以使用一种更底层的,更硬核,也更准确的方式来判断一个对象是否逃逸,那就是:直接看汇编!
go tool compile -S