Go 語言內存逃逸案例
01 介紹
在「Go 語言逃逸分析」中,我們瞭解到內存分配的相關知識,棧空間分配開銷小,堆空間分配開銷大。
Go 語言編譯器可以通過逃逸分析決定內存分配到棧空間或堆空間。但是,分配到棧空間的對象在某些情況中會逃逸到堆空間。我們可以使用 Go 工具鏈查看對象是否發生內存逃逸。
爲了提升 Go 應用程序的性能,我們應該避免 Go 應用程序中出現內存逃逸的現象,本文我們介紹 Go 語言內存逃逸的幾種典型案例。
02 內存逃逸案例
指針逃逸
示例代碼:
func main() {
pointerEscape(1, 2)
}
// pointerEscape 指針逃逸
func pointerEscape(a, b int) *int {
sum := a + b
return &sum
}
輸出結果:
go build --gcflags '-m -m -l' main.go
# command-line-arguments
./main.go:9:2: sum escapes to heap:
./main.go:9:2: flow: ~r0 = &sum:
./main.go:9:2: from &sum (address-of) at ./main.go:10:9
./main.go:9:2: from return &sum (return) at ./main.go:10:2
./main.go:9:2: moved to heap: sum
閱讀上面這段代碼,我們創建一個函數 pointerEscape
,函數內部創建一個局部變量 sum
,返回結果是該變量的指針。
通過執行 go build --gcflags '-m -m -l' main.go
的輸出結果,我們發現函數中定義的局部變量 sum 逃逸到堆空間,這就是所謂的指針逃逸。
函數 pointerEscape
的局部變量 sum
本來應該在函數結束時被回收,但是在 main 函數中會繼續使用 sum
變量的內存地址,導致變量 sum
被逃逸到堆上。
如果想要避免示例函數的返回結果出現內存逃逸,可以使用值類型的返回結果,這樣就帶來另外一個問題。
如果返回結果是一個比較大的變量,比如返回結果是較大的結構體類型的變量,我們使用值類型將會造成比較大的內存佔用。
所以,我們在實際項目開發中,需要根據實際情況,合理使用返回結果的類型。
動態類型逃逸
示例代碼:
func main() {
fmt.Println("hello world")
}
輸出結果:
go run -gcflags '-m -m -l' main.go
# command-line-arguments
./main.go:6:14: "hello world" escapes to heap:
./main.go:6:14: flow: {storage for ... argument} = &{storage for "hello world"}:
./main.go:6:14: from "hello world" (spill) at ./main.go:6:14
./main.go:6:14: from ... argument (slice-literal-element) at ./main.go:6:13
./main.go:6:14: flow: {heap} = {storage for ... argument}:
./main.go:6:14: from ... argument (spill) at ./main.go:6:13
./main.go:6:14: from fmt.Println(... argument...) (call parameter) at ./main.go:6:13
./main.go:6:13: ... argument does not escape
./main.go:6:14: "hello world" escapes to heap
閱讀上面這段代碼,我們在 main 函數中,使用 fmt.Println()
打印字符串 hello world
。
通過執行 go run -gcflags '-m -m -l' main.go
的輸出結果,我們發現使用 fmt.Println()
打印的字符串 hello world
逃逸到堆上,這就是所謂的動態類型逃逸。
因爲 fmt.Println()
接收的參數是空接口類型,Go 編譯器無法確定入參變量的具體類型,所以此類情況變量也會逃逸到堆上。
03 總結
本文我們介紹兩個典型的內存逃逸的案例,除此之外,以下幾種情況,也會發生內存逃逸。
-
發送指針或帶有指針的值到 channel 中。
-
在一個切片上存儲指針或帶指針的值。
-
slice 的底層數組被重新分配(append 超出其容量時)。
感興趣的讀者朋友們,可以自行編寫上述幾種情況的示例代碼,驗證是否會發生內存逃逸。
參考資料:
-
http://npat-efault.github.io/programming/2016/10/10/escape-analysis-and-interfaces.html
-
http://www.wingtecher.com/themes/WingTecherResearch/assets/papers/ICSE20.pdf
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/iACaQ8vsxEvUVsDJ5QO6UA