詳解Go語言中的逃逸分析
什么是逃逸
一句話,逃逸分析是編譯器用于決定將變量分配到棧上還是堆上的一種行為。
眾所周知,函數(shù)的運行都在操作系統(tǒng)內(nèi)存空間中的??臻g內(nèi)。我們在棧上聲明臨時變量,分配內(nèi)存,函數(shù)運行完畢后,回收內(nèi)存。每個函數(shù)的??臻g都是獨立的,其他函數(shù)沒有權(quán)限訪問。但在某些情況下,我們需要在函數(shù)結(jié)束以后訪問棧上面的某些數(shù)據(jù),這就涉及到內(nèi)存逃逸了。
如果變量從棧上逃逸,那么他會逃到哪兒去呢?他會跑到堆上。由于棧上的變量是在函數(shù)結(jié)束的時候自動進行回收,回收代價比較??;而堆空間分配內(nèi)存,則首先需要找到一塊大小合適的內(nèi)存,之后通過GC回收才能釋放。對于這種情況,頻繁使用垃圾回收會占用比較大的開銷,所以要盡量分配內(nèi)存到棧上,減少GC的壓力。
逃逸分析基本過程
Go語言的逃逸分析最基本的原則:如果一個函數(shù)返回一個對變量的引用,那么他就會發(fā)生逃逸。
在任何情況下,如果一個值被分配到了??臻g以外的地方,那么它一定是被分配到了堆上。簡言之:編譯器會分析代碼的特征和生命周期,Go中的變量只有在編譯器可以證明在函數(shù)返回后不會再被引用的情況下,才會被分配到棧上,否則會被分配到堆上。
不同于C++中的new,Go語言中的new關(guān)鍵字不一定會將內(nèi)存分配到堆空間上,在Go語言中,沒有關(guān)鍵字或者函數(shù)可以直接將變量分配到堆上,而是通過編譯器來分析代碼決定將變量分配到何處。
一句話:
編譯器會根據(jù)變量是否被外部引用來決定是否逃逸。
如果函數(shù)外部沒有引用,則優(yōu)先放到棧中;
如果函數(shù)外部存在引用,則必定放到堆中;
常見逃逸情況
指針逃逸
我們知道傳遞指針可以減少底層值的拷貝,提高效率。但是如果拷貝的數(shù)據(jù)量小,指針傳遞會產(chǎn)生逃逸,可能會使用堆空間,增加GC負擔(dān),所以傳遞指針不一定是高效的。
比如:
package main
type Student struct {
Name string
Age int
}
func StudentRegister(name string, age int) *Student {
s := new(Student) // 局部變量s逃逸到堆
s.Name = name
s.Age = age
return s
}
func main() {
StudentRegister("similar", 18)
}雖然函數(shù)StudentRegister內(nèi)部s為局部變量,但是由于返回了指針,其指向的內(nèi)存地址不會是棧而是堆,這是典型的逃逸案例。
使用命令 go build -gcflags '-m -l' main.go,得到:
# command-line-arguments
./escape.go:8:22: leaking param: name
./escape.go:9:10: new(Student) escapes to heap # 表示該行內(nèi)存發(fā)生了逃逸現(xiàn)象
??臻g不足
如果分配太大容量的slice在棧上,當(dāng)??臻g不足存放當(dāng)前對象或者無法判斷當(dāng)前切片長度時就會將對象分配到堆中。
package main
func MakeSlice() {
s := make([]int, 10000, 10000)
for index := range(s){
s[index] = index
}
}
func main() {
MakeSlice()
}同樣使用命令go build -gcflags '-m -l' main.go:
# command-line-arguments
./escape_1.go:4:11: make([]int, 10000) escapes to heap
動態(tài)類型逃逸
很多函數(shù)的參數(shù)為interface類型,比如:
func Printf(format string, a ...interface{}) (n int, err error)
func Scanf(format string, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)在編譯時很難確定其參數(shù)的具體類型,也能產(chǎn)生逃逸。
變量大小不確定
在創(chuàng)建切片,初始化切片容量的時候,有時會傳入一個變量指定其大小,由于變量的值不能被編譯器確定,所以不能確定其占用空間大小,從而編譯器可能會直接將變量分配到堆上。
package main
func MakeSlice() {
length := 1
a := make([]int, length, length)
for i := 0; i < length; i++ {
a[i] = i
}
}
func main() {
MakeSlice()
}編譯結(jié)果:
# command-line-arguments
./escape_1.go:5:11: make([]int, length, length) escapes to heap
常見的逃逸情況總結(jié)
指針逃逸:函數(shù)內(nèi)部返回一個局部變量指針
分配大對象:導(dǎo)致棧空間不足,不得不分配到堆上
調(diào)用接口類型的方法,接口類型的方法調(diào)用是動態(tài)調(diào)度 - 實際使用的具體實現(xiàn)只能在運行時確定。
盡管能夠符合分配到棧的場景,但是其大小不能在編譯的時候確定,也會分配到堆上。
如何避免
Go中的接口類型的方法調(diào)用是動態(tài)調(diào)度,因此不能夠在編譯階段確定,所有類型結(jié)構(gòu)轉(zhuǎn)換成接口的過程會涉及到內(nèi)存逃逸的情況發(fā)生。如果對于性能要求比較高且訪問頻次比較高的函數(shù)調(diào)用,應(yīng)該盡量避免使用接口類型。
由于切片一般都是使用在函數(shù)傳遞的場景下,而且切片在append的時候可能會涉及到重新分配內(nèi)存,如果切片在編譯期間的大小不能夠確認或者大小超出棧的限制,多數(shù)情況下都會被分配到堆上。
總結(jié)
堆上分配內(nèi)存比棧上分配內(nèi)存,開銷大很多。
變量分配在棧上需要能夠在編譯期確定他的作用域,否則會分配到堆上。
Go語言編譯器會通過變量是否被外部引用類決定是否逃逸
通過go build -gcflags '-m'命令可以觀察變量是否逃逸
不能盲目使用變量的指針作為函數(shù)參數(shù),雖然會減少復(fù)制操作,但是當(dāng)參數(shù)為變量自身的時候,復(fù)制是在棧上完成的操作,開銷遠比變量逃逸后動態(tài)地在堆上分配內(nèi)存少得多。
到此這篇關(guān)于詳解Go語言中的逃逸分析的文章就介紹到這了,更多相關(guān)Go逃逸分析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang中字符串(string)與字節(jié)數(shù)組([]byte)一行代碼互轉(zhuǎn)實例
golang語言本身就是c的工具集,開發(fā)c的程序用到的大部分結(jié)構(gòu)體,內(nèi)存管理,攜程等,golang基本都有,下面這篇文章主要給大家介紹了關(guān)于Golang中字符串(string)與字節(jié)數(shù)組([]byte)一行代碼互轉(zhuǎn)的相關(guān)資料,需要的朋友可以參考下2022-09-09
Golang使用協(xié)程實現(xiàn)批量獲取數(shù)據(jù)
服務(wù)端經(jīng)常需要返回一個列表,里面包含很多用戶數(shù)據(jù),常規(guī)做法當(dāng)然是遍歷然后讀緩存。使用Go語言后,可以并發(fā)獲取,極大提升效率,本文就來聊聊具體的實現(xiàn)方法,希望對大家有所幫助2023-02-02
Go語言實現(xiàn)優(yōu)雅關(guān)機和重啟的示例詳解
優(yōu)雅的關(guān)機是指在關(guān)閉服務(wù)之前,先讓服務(wù)處理完當(dāng)前正在處理的請求,然后再關(guān)閉服務(wù),本文主要為大家詳細介紹了如何使用Go語言實現(xiàn)優(yōu)雅關(guān)機和重啟,感興趣的小伙伴可以參考一下2025-04-04
10個現(xiàn)代網(wǎng)站開發(fā)必備的Go軟件包工具盤點
這篇文章主要為大家介紹了10個現(xiàn)代網(wǎng)站開發(fā)必備的Go軟件包,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10
深入探索Go語言中的高效數(shù)據(jù)結(jié)構(gòu)堆
堆,作為一種基本的數(shù)據(jù)結(jié)構(gòu),以其在優(yōu)先隊列和排序算法中提供高效解決方案的能力而聞名。在本文中,我們將深入探討堆的內(nèi)部工作原理,包括其特性、實現(xiàn)細節(jié)以及在現(xiàn)代編程中的應(yīng)用2008-06-06
詳解Go語言中如何通過Goroutine實現(xiàn)高并發(fā)
在Go語言中,并發(fā)編程是一個核心且強大的特性,Go語言通過goroutine和channel等機制,使得并發(fā)編程變得更加簡單和直觀,本文給大家介紹了Go語言中如何通過Goroutine快速實現(xiàn)高并發(fā),感興趣的小伙伴跟著小編一起來看看吧2024-10-10

