讓我們一起分析 Go 語言逃逸
1.介紹
熟悉 C / C++ 的讀者朋友們應(yīng)該都知道一個進(jìn)程(應(yīng)用程序)的虛擬內(nèi)存空間劃分為棧內(nèi)存區(qū)和堆內(nèi)存區(qū)。
棧內(nèi)存區(qū)上對象的內(nèi)存空間是自動分配和銷毀的,使用者無需關(guān)心。但是,堆內(nèi)存區(qū)上對象的內(nèi)存空間是需要使用者自己管理,無形中增加了使用者的心智負(fù)擔(dān)。
因此,一些高級語言會支持垃圾回收(GC),降低使用者內(nèi)存管理的心智負(fù)擔(dān)。支持垃圾回收的語言可以自動管理堆內(nèi)存區(qū)上對象的內(nèi)存空間。
Go 語言編譯器負(fù)責(zé)決定把對象分配到棧上或堆上,比如一個對象在函數(shù)退出后就不可達(dá)(沒有其他對象引用該對象)時,那就將該對象分配到棧上,反之,則分配到堆上。
如果一個對象被分配到堆上,就需要 Go 的垃圾回收管理該對象的內(nèi)存空間。但是,垃圾回收是有代價的,它會占用系統(tǒng)開銷。
所以,為了更大限度地降低垃圾回收占用的系統(tǒng)資源,提升應(yīng)用程序本身可使用的系統(tǒng)資源,使用者就需要盡量減少堆內(nèi)存分配,盡量多地使應(yīng)用程序使用棧內(nèi)存分配,盡量避免 Go 編譯器通過逃逸分析優(yōu)化后被分配到棧內(nèi)存的對象逃逸到堆內(nèi)存。
2.查看對象是否發(fā)生逃逸
Go 語言工具鏈提供了查看對象是否逃逸的方法,我們在執(zhí)行 go build 時,配合使用參數(shù) -gcflags 開啟編譯器支持的額外功能,例如:
go build -gcflasg '-m -m -l' main.go
- -m 用于輸出編譯器的執(zhí)行細(xì)節(jié),包括逃逸分析的執(zhí)行。
- -l 用于禁用內(nèi)聯(lián)優(yōu)化。
我們通過使用 Go 語言工具鏈對一段簡單的示例代碼進(jìn)行查看對象是否發(fā)生逃逸。
func main() {
sum(1, 2)
}
func sum(a, b int) *int {
res := a + b
return &res
}
輸出結(jié)果:
go build -gcflags '-m -m -l' main.go
# command-line-arguments
./main.go:8:2: res escapes to heap:
./main.go:8:2: flow: ~r0 = &res:
./main.go:8:2: from &res (address-of) at ./main.go:9:9
./main.go:8:2: from return &res (return) at ./main.go:9:2
./main.go:8:2: moved to heap: res
閱讀上面這段代碼,我們發(fā)現(xiàn) sum 函數(shù)中的變量 res 逃逸到堆,也就是說 Go 編譯器通過逃逸分析,決定將變量 res 分配到堆空間。
3.逃逸分析的作用
Go 語言編譯器通過逃逸分析優(yōu)化,將對象合理分配到??臻g和堆空間。
因為棧內(nèi)存分配比堆內(nèi)存分配更快,所以 Go 語言在編譯時通過逃逸分析優(yōu)化將不會發(fā)生逃逸的對象優(yōu)先分配到??臻g。
因此,不僅降低堆空間內(nèi)存分配的開銷,同時,也可以降低垃圾回收占用的系統(tǒng)資源。
4.總結(jié)
本文我們介紹 Go 語言逃逸分析,它可以幫助使用者合理分配對象的內(nèi)存空間。
我們知道分配到堆內(nèi)存空間的對象,會導(dǎo)致 Go 執(zhí)行垃圾回收,而垃圾回收會占用系統(tǒng)資源,降低應(yīng)用程序本身可使用的系統(tǒng)資源。
所以,我們在實際項目開發(fā)中,可以借助 Go 工具鏈分析對象是否會發(fā)生逃逸,盡量避免一些不必要的對象逃逸。