引言
????????內存管理的靈活性是讓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被分配到堆上。
??臻g不足逃逸
當對象大小超過的棧幀大小時(詳見go內存分配),變量對象發生逃逸被分配到堆上。
當s的容量足夠大時,s逃逸到堆上。t容量較小分配到棧上
閉包引用逃逸
Fibonacci()函數返回一個函數變量賦值給f,f就成了一個閉包。閉包f保存了a b的地址引用,所以每次調用f()后ab的值發生變化。ab發生逃逸。
但如果直接調用Fibonacci(),則ab都是獨立的局部變量。
動態類型逃逸
當對象不確定大小或者被作為不確定大小的參數時發生逃逸。
t的大小是個變量所以會逃逸到堆上。size作為interface{}參數逃逸到堆上。
切片或map賦值
在給切片或者map賦值對象指針(與對象共享內存地址時),對象會逃逸到堆上。但賦值對象值或者返回對象值切片是不會發生逃逸的。
????變量逃逸情況還有很多,暫時學習整理這些。程序性能優化是一個很重要的方向,對于現在還在完善的go編譯器,我們需要不斷總結現有缺陷,盡量在編碼時注意潛在的問題,不要把優化都留給編譯器(也不可能都留給它,因為我也不知道要優化什么 0-0 )。
總結
????逃逸分析是編譯器在靜態編譯的時候,分析對象的生命周期及引用情況來決定對象內存分配到堆上還是棧上,由于棧內存分配較堆快且棧內存回收較快(無需gc),編譯器以此來優化程序性能。
參考
更多精彩內容詳見作者公眾號:? i技術之路