浅谈Go语言之逃逸分析 | 青训营笔记

102 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天。

众所周知,在使用Go语言的new函数分配内存时,不用像C/C++语言时那样担心内存泄漏的问题,这是因为Go语言的内建函数new在分配内存时得到的内存并不指定是在堆或者是栈(因为在Go语言中没有很明确的堆栈分界,可以说堆栈的区别被模糊化了),变量在堆上分配内存还是在栈上分配内存,都是要经过编译器的逃逸分析才能决定的。

那么,到底什么是逃逸分析呢?

在编译原理中,分析指针动态范围的方法被称为逃逸分析。即:当一个对象的指针被多个方法或者线程引用时,则这个指针发生了逃逸,逃逸分析可以决定变量是在堆上还是在栈上分配。

那么,为什么需要逃逸分析呢?

相信各位朋友都学过C/C++语言。在写代码时,常常将返回值改成指针返回,因为C/C++语言都是静态内存分配,即局部变量都是在栈上分配的。那么这里就有一个问题:我们在函数内部定义的一个局部变量,当我们函数执行完会返回这个局部变量的指针(即变量的地址),变量占用的内存空间会被销毁,而这个时候如果再对返回值操作,有可能会导致程序panic。(相当于你对一个已经不存在了的人拨打电话说想找他,这是一件多么可怕的事情)。

在Go语言里,逃逸分析会把变量合理的分配到它特定的地方(堆或者栈)。如果编译器发现某块内存在退出它所属的函数后就不再使用了,那么这个变量就会分配到栈上,因为栈上会比堆上快。如果说这个变量在它所属的函数外部还会被使用,那么就会被分配到堆上。(说到这里,我再简单扯一下Go语言的gc。在Go中栈上内存由编译器负责管理回收,而堆上的内存由编译器和垃圾收集器负责管理回收。)以new为例,在C++中是分配堆上内存,但是在Go里面就不一定了,正如前面所说,得益于Go语言的逃逸分析。因为在堆上分配的内存,会频繁的引起Go的gc进行垃圾回收,而且gc会占用很多的系统开销;而且堆内存分配速度比栈内存分配慢很多。那么有了Go语言的逃逸分析,就可以尽量把不用分配到堆上的内存分配到栈上,会提升程序性能。

逃逸分析的规则

编译器会根据变量是否被外部引用来决定是否逃逸:

1.如果变量在函数外部没有被引用,优先分配到栈上(不必定,因为如果此时需要分配的内存超过了栈的存储内存,那么就会被分配到堆上。)

2.如果变量在函数外部有引用,那么必定分配到堆上。