Go面试题——逃逸分析
Go面试题——逃逸分析
一个变量是在堆分配,还是在栈上分配,是经过编译器的逃逸分析之后得出的 "结论"。
一、逃逸分析是什么?
在 C 语言中,可以使用malloc
和free
手动在堆上分配和回收内存。
在 Go 语言中,堆内存是通过垃圾回收机制自动管理的,无需开发者指定。那么,Go 编译器怎么知道某个变量需要分配在栈上,还是堆上呢?编译器决定内存分配位置的方式,就称之为逃逸分析(escape analysis)。逃逸分析由编译器完成,作用于编译阶段。
二、逃逸分析有什么作用?
在 Go 语言中,逃逸分析把变量合理地分配到它该去的地方,“找准自己的位置”。即使是用 new 函数申请到的内存,
如果编译器发现这块内存退出函数后就没有使用了,那就分配到栈上,毕竟栈上的内存分配比堆快很多。
即使表面上只有一个普通的变量,但是经过编译器的逃逸分析后发现,在函数之外,还有其他的地方在引用,那就分配到堆上。
如果变量都分配到堆上,堆不像栈可以自动清理。就会引起 Go 频繁地进行垃圾回收,而垃圾回收会占用比较大的系统开销。
堆和栈相比,堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片;栈内存分配则会非常的快,栈分配内存值需要通过 PUSH 指令,并且会被自动释放;而堆内存首先需要找到一个大小合适的内存块,之后需要通过垃圾回收才能释放。
通过逃逸分析,可以尽量把哪些不需要分配到堆上的变量直接分配到栈上,堆上的变量少了,会减轻堆内存分配的开销,同时会减少垃圾回收(Garbage Collection,GC)的压力,提高程序的运行速度。
三、逃逸分析是怎么完成的?
在 Go 语言中,逃逸分析最基本的原则是:如果一个函数返回对一个变量的引用,那么这个变量就会发生逃逸。
编译器会分析代码的特征和代码的生命周期 ,Go 中的变量只有在编译器可以证明在函数返回后不会再被引用的,才分配到栈上,其他情况下都是分配的堆上。
例如:对于一个变量取地址,可能会被分配到堆上。但是编译器进行逃逸分析后,如果考虑到在函数返回后,此变量不会被用,那么还是会分配到栈上。简单地来说,编译器会根据变量是否被外部引用来决定是否逃逸:
1)如果变量在函数外部没有引用,则优先放到栈上。
2)针对第一条,定义一个很大的数组,需要申请内存过大,超过栈的存储能力,一般还是要放在堆上面。
3)如果变量在函数外部存在引用,则必定放到堆上。
四、如何确定是否发生逃逸?