文章目錄
- 前言
- 1 逃逸分析是什么?
- 2 逃逸分析的基本思想是什么?
- 3 逃逸分析的分配原則是什么?
- 4 如何進行逃逸分析?
- 5 逃逸分析案例
- 5.1 變量在函數外存在引用
- 5.2 引用類型的逃逸
- 5.3 閉包捕獲變量
- 5.4 變量占用內存較大
- 6 變量會逃逸到堆上的原因有哪些?
- 7 如何避免逃逸?
- 8 逃逸分析的作用有哪些?
- 9 學習交流
前言
在正式學習逃逸分析時,我們需要提前補充棧、堆、變量的聲明周期的概念。
- 棧(stack):在GO語言中,棧是調用棧(call stack) 的簡稱。在GO程序運行時,每一個Goroutine單獨維護一個自己的棧區,僅允許自己使用不能被其他Goroutine使用。一個棧通常包含許多棧幀(stack frame),它描述的是函數之間的調用關系。棧的內存是由編譯器自動進行分配和釋放的。棧區主要存儲函數參數、局部變量、調用函數幀,它們隨函數的創建而分配,隨函數的退出而銷毀。
- 堆(heap):與棧(stack)不同的是,堆區的內存是由編譯器和工程師共同負責管理分配,交給Runtime GC來釋放。在堆上分配內存時,必須找到一塊足夠大的內存來存放新的變量數據。在堆上釋放內存時,垃圾回收器會掃描內存空間中不被使用的對象并釋放其內存。在我們開發過程中,其實考慮內存管理,主要是考慮堆內存的管理。
變量的聲明周期與變量作用域的關系
- 全局變量:它的生命周期與程序的生命周期一致
- 局部變量:它的生命周期是動態的,從變量創建開始,到變量不再使用結束。
- 形參和函數的返回值:它們都是屬于局部變量,在函數被調用時創建,調用結束時被銷毀。
1 逃逸分析是什么?
逃逸分析(Escape Analysis)是一種重要的編譯時優化技術,決定將變量分配到 堆(heap)上 還是 棧(stack)上。
通過逃逸分析,編譯器可以判斷變量的生命周期和作用范圍,從而選擇最合適的內存分配方式,以提高程序的性能和減少內存開銷。
2 逃逸分析的基本思想是什么?
[!warning]- 思考: 如何知道GO變量的生命周期是完全可知的?
- 判斷變量是值類型還是引用類型,值類型是確定的完全可知的,引用類型是不可知的,不知道是否該變量被其他函數使用。
- 檢查變量的生命周期是否是完全可知的,如果是,則在棧上分配內存。
- 如何檢查不是完全可知的,也就是我們說的逃逸,必須在堆上分配內存。
3 逃逸分析的分配原則是什么?
[!warning]- 如何確定參數類型是不確定的?
- 變量的數據類型采用
interface{}
,編譯期無法確定其具體的參數類型,所以分配到堆中。- 什么樣的數據類型是確定的? 比如:聲明了一個確定數據類型int的變量`var num int
- GO的逃逸分析是在編譯期間完成的,編譯期間無法確定的參數類型是放在堆中的。
- 變量在函數外存在引用,則必定放在堆中
- 變量占用內存較大,則優先放在堆中
- 變量在函數外部沒有引用,則優先放在棧中。
4 如何進行逃逸分析?
逃逸分析我們可以通過命令查看結果,
-gcflags
選項用于向 Go 編譯器傳遞編譯標志。這些標志可以用來啟用或禁用特定的編譯器功能,包括逃逸分析
- 查看基本的逃逸分析和內聯信息,適用于一般情況
go build -gcflags="-m" main.go
[!warning]+ 命令解釋說明
-m
:表示輸出有關內聯(inlining) 和逃逸分析的信息
- 查看更詳細的優化信息和逃逸分析結果,并禁用內聯優化,適用于需要深入調試和分析的情況。
go build -gcflags '-m -m -l' main.go
[!warning]+ 命令解釋說明
-m -m
:表示多次使用-m
標志,增加詳細程度,會輸出更多的優化信息,包括逃逸分析和內聯優化的詳細信息。-l
:表示禁用內聯優化。
5 逃逸分析案例
5.1 變量在函數外存在引用
package mainimport "fmt"func createPointer() *int {var x intreturn &x // x 逃逸到堆上
}func main() {p := createPointer()fmt.Println(*p)
}
[!note]+ 代碼解析說明
- 函數
createPointer
返回了局部變量x
的地址,這意味著x
在函數返回后仍然需要存在。- 因此,編譯器將
x
分配到堆上,并在逃逸分析的輸出中提示&x escapes to heap
。
5.2 引用類型的逃逸
- 例如:切片、映射、接口等引用類型的變量,如果它們的底層數據逃逸,則這些變量也會逃逸。
func createSlice() []int {s := make([]int, 10) return s // s 逃逸到堆上
}
5.3 閉包捕獲變量
如果閉包捕獲了外部變量,該變量會逃逸到堆上。
func createClosure() func() { var x int return func() { x++ // x 逃逸到堆上 }
}
5.4 變量占用內存較大
func createManySlice() []int { var s []int for i := 0; i < 1000; i++ { s = make([]int, 10) } return s // s 逃逸到堆上
}
6 變量會逃逸到堆上的原因有哪些?
- 函數返回值:如果返回一個局部變量的指針或引用,該變量會逃逸到堆上。
- 閉包捕獲:如果閉包捕獲了外部變量,該變量會逃逸到堆上。
- 長生命周期:如果變量的生命周期超出了其作用域,如通過指針或引用傳遞給其他函數或存儲在全局變量中。
7 如何避免逃逸?
避免逃逸,也就是說減少不必要的堆分配。
- 避免返回局部變量的指針或引用
- 盡量減少閉包捕獲的外部變量
- 使用值傳遞而不是指針傳遞
8 逃逸分析的作用有哪些?
- 提升內存分配效率:棧上分配比在堆上分配效率更高效,棧上的內存可以自動回收,而堆上的內存需要垃圾回收器管理。
- 減少垃圾回收開銷:減少不必要的堆分配,可以降低垃圾回收的頻率和開銷。
- 提高程序性能:優化內存分配,提升程序運行效率。
9 學習交流
為了方便大家一起學習一起進步,我創建了一個學習交流的平臺
感興趣的朋友們可以加我微信:LH913582934,備注:CSDN。