【Go学习】语言基础(一)逃逸分析

121 阅读2分钟

逃逸分析的概念

这并不是特定语言特有的概念。在编译原理中,分析指针动态范围的方法被称为 逃逸分析 。 什么意思呢?当一个对象的指针被多个方法或线程引用时,则称这个指针发生了 逃逸 。

再细致地举个例子: 为什么会有这样一个问题?以编写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用于输出编译器的优化细节

image.png

也可用反汇编指令查看: go tool compile -S main.go

image.png

此处说明t被分配到了堆上

如何理解Go中的堆和栈

操作系统层面的 栈 被Go语言的runtime全部消耗了,用于调度器、垃圾回收、系统调用等;而用户态所看到的栈,其实是从操作系统申请来的堆内存虚拟出来的。所以这个“栈”有堆的碎片化的特性。但为了避免这种情况,runtime可能会将整个栈进行深拷贝,复制到另一块内存区域。 因此,也有另外一个问题:GO指针的算术运算不再能奏效。因为不能保证指针所指向的地址内容是否在runtime期间被移动。

参考文献

  • 《go程序员笔试面试宝典》

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情