引言
内存管理的灵活性是让C/C++程序猿们又爱又恨的东西,比如malloc或new一块内存我可以整个进程使用。但是,如果这块内存在某个函数中new了,但是暂时不能释放那就是悲剧开始了。鬼知道何时释放合适及是不是我还记得我new过它。所以后来很多语言都限制了内存管理或者优化了内存管理机制,添加gc机制来“辅助”程序猿们编程。变量分配在堆上还是栈上不是由是否new/malloc决定,而是通过编译器的“逃逸分析”来决定。
什么是逃逸分析
在编译程序优化理论中,逃逸分析是一种确定指针动态范围的方法——分析在程序的哪些地方可以访问到指针。也是就是说逃逸分析是解决指针作用范围的编译优化方法。编程中常见的两种逃逸情景:
1,函数中局部对象指针被返回(不确定被谁访问)
2,对象指针被多个子程序(如线程 协程)共享使用
为什么要做逃逸分析
开始我们提到go语言中对象内存的分配不是由语言运算符或函数决定,而是通过逃逸分析来决定。为什么要这么干呢?其实说到底还是为了优化程序。函数中生成一个新对象:
1,如果分配到栈上,待函数返回资源就被回收了
2,如果分配到堆上,函数返回后交给gc来管理该对象资源
栈资源的分配及回收速度比堆要快,所以逃逸分析最大的好处应该是减少了GC的压力。
逃逸分析原理
逃逸分析的场景
指针逃逸
典型的逃逸case,函数返回局部变量的指针。
运行:go build -gcflags "-m -l" escap01.go
-m 可以用多个来打印更详细的信息,-l去掉inline信息。局部变量a被分配到堆上。
栈空间不足逃逸
当对象大小超过的栈帧大小时(详见go内存分配),变量对象发生逃逸被分配到堆上。
当s的容量足够大时,s逃逸到堆上。t容量较小分配到栈上
闭包引用逃逸
Fibonacci()函数返回一个函数变量赋值给f,f就成了一个闭包。闭包f保存了a b的地址引用,所以每次调用f()后ab的值发生变化。ab发生逃逸。
但如果直接调用Fibonacci(),则ab都是独立的局部变量。
动态类型逃逸
当对象不确定大小或者被作为不确定大小的参数时发生逃逸。
t的大小是个变量所以会逃逸到堆上。size作为interface{}参数逃逸到堆上。
切片或map赋值
在给切片或者map赋值对象指针(与对象共享内存地址时),对象会逃逸到堆上。但赋值对象值或者返回对象值切片是不会发生逃逸的。
变量逃逸情况还有很多,暂时学习整理这些。程序性能优化是一个很重要的方向,对于现在还在完善的go编译器,我们需要不断总结现有缺陷,尽量在编码时注意潜在的问题,不要把优化都留给编译器(也不可能都留给它,因为我也不知道要优化什么 0-0 )。
总结
逃逸分析是编译器在静态编译的时候,分析对象的生命周期及引用情况来决定对象内存分配到堆上还是栈上,由于栈内存分配较堆快且栈内存回收较快(无需gc),编译器以此来优化程序性能。
参考
有疑问加站长微信联系(非本文作者)