逃逸分析的概念
这并不是特定语言特有的概念。在编译原理中,分析指针动态范围的方法被称为 逃逸分析 。 什么意思呢?当一个对象的指针被多个方法或线程引用时,则称这个指针发生了 逃逸 。
再细致地举个例子: 为什么会有这样一个问题?以编写C/C++代码为例,为了减少构造函数的开销,在函数中不返回值,而是返回指向这个值的指针。
但是涉及到指针的问题往往可能存在陷阱。
比如,如果是在栈上进行的静态内存分配,函数执行完毕之后,相应的内存空间就会被销毁,那之后再操作这个值会扰乱程序的运行甚至导致程序崩溃;再比如如果在函数中通过new的方式在堆中动态分配内存,虽然函数结束时内存不会被销毁,但也引发了一个新问题:即它何时才会被销毁?如果一直不能销毁,那就是内存泄露的问题了。
在C++中,上述提到的在函数中动态分配内存法并返回指针的方法会存在问题,但是在go中不会。 这要归功于go编译器的逃逸分析。
func foo()*int{
t:=new(int)
*t=3
return t
}
go编译器如何进行逃逸分析及优化
思路
- 若变量在函数外没有被引用,优先放到栈上 如果超过了栈的存储能力,一样会放到堆里的
- 如果存在引用,必定放到堆上
也就是说就算写代码的时候指定了静态内存分配/动态内存分配。编译器也不一定采用指定的方式,而是根据分析得出的最优情况。
具体指令
go build -gcflags="-m -l" example/main.go
其中-m用于输出编译器的优化细节
也可用反汇编指令查看:
go tool compile -S main.go
此处说明t被分配到了堆上
如何理解Go中的堆和栈
操作系统层面的 栈 被Go语言的runtime全部消耗了,用于调度器、垃圾回收、系统调用等;而用户态所看到的栈,其实是从操作系统申请来的堆内存虚拟出来的。所以这个“栈”有堆的碎片化的特性。但为了避免这种情况,runtime可能会将整个栈进行深拷贝,复制到另一块内存区域。 因此,也有另外一个问题:GO指针的算术运算不再能奏效。因为不能保证指针所指向的地址内容是否在runtime期间被移动。
参考文献
- 《go程序员笔试面试宝典》
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情