go語言逃逸分析
任何時(shí)候,一個(gè)值被分享到函數(shù)棧幀范圍之外,它都會(huì)在堆上被重新分配赎懦,說道這個(gè)問題了,我們就談?wù)剮吔纾@個(gè)名詞的作用是在函數(shù)執(zhí)行的時(shí)候
為了函數(shù)的上下文所設(shè)置的一個(gè)邊界叽唱,它存在在棧中,棧在 Go 語言中是非常重要的微宝,因?yàn)樗鼮榉峙浣o每個(gè)函數(shù)的幀邊界提供了物理內(nèi)存空間棺亭,所有又有一個(gè)名詞叫棧幀
然后有個(gè)問題就是在棧的執(zhí)行過程中,只能執(zhí)行在這個(gè)棧以上的部分蟋软,舉個(gè)例子abc三個(gè)函數(shù)镶摘,a調(diào)用b b調(diào)用c 那么痛調(diào)用機(jī)制來說最高的就是a,最低的就是c
舉個(gè)例子:
棧的就像一個(gè)筒子的存錢的東西,你每次需要從下面往上塞硬幣,然后用的時(shí)候也是從下面往外扣喇勋。很形象了哈凶伙。
然后在go語言執(zhí)行的過程中,如果傳遞的是值就不發(fā)生堆的問題了窑眯,因?yàn)椴粫?huì)指向棧幀邊界的下面的問題,因?yàn)橹当粡?fù)制到了,為了運(yùn)行b函數(shù)而臨時(shí)在a中開辟的內(nèi)存空間里了拒逮。不存在和下面的b函數(shù)的瓜葛了。
當(dāng)你使用的是指針臀规,那么復(fù)制過去的也是指針滩援,然后你想要使用這個(gè)值就要去取棧幀邊界下面的東西,但是又不能去得到塔嬉,怎么辦呢玩徊,這個(gè)時(shí)候就需要將下面的這個(gè)指針指向的變量存放到堆里,這不就解決了嘛邑遏,當(dāng)然你免不了要gc啊佣赖。所以又是資源時(shí)間的浪費(fèi),如何掂量你自己看著辦吧记盒。
package main
type user struct {
name string
email string
}
func main() {
a1 := createUser1()
a2 := createUser2()
fmt.Println("a1", &a1, "a2", &a2)
}
func createUser1() user {
u := user{
name: "Bill",
email: "@.com",
}
fmt.Println("V1", &u)
return u
}
//go:noinline
func createUser2() *user {
u := user{// 發(fā)生了逃逸分析
name: "Bill",
email: "bill@ardanlabs.com",
}
fmt.Println("V2", &u)
return &u
}
指針指向了棧下的無效地址空間憎蛤。當(dāng) main 函數(shù)調(diào)用下一個(gè)函數(shù),指向的內(nèi)存將重新映射并將被重新初始化,這就是逃逸分析將開始保持完整性的地方
編譯器將檢查到俩檬,在 createUserV2 的函數(shù)棧中構(gòu)造user值是不安全的萎胰,因此,替代地棚辽,會(huì)在堆中構(gòu)造相應(yīng)的值
值只有在編譯器編譯時(shí)知道其大小才會(huì)將它分配到棧中技竟。這是因?yàn)槊總€(gè)函數(shù)的棧幀大小是在編譯時(shí)計(jì)算的。如果編譯器不知道其大小屈藐,就只會(huì)在堆中分配榔组。
例如有一個(gè)數(shù)組你沒有給它分配大小,那么它就沒辦法放到棧里就會(huì)放到堆里联逻。
總結(jié):
當(dāng)函數(shù)執(zhí)行的時(shí)候無法執(zhí)行幀邊界下面的部分就會(huì)發(fā)生將數(shù)據(jù)推入堆的逃逸分析
-
當(dāng)分配的數(shù)據(jù)搓扯,程序無法知道具體的大小的時(shí)候就會(huì)放到堆里
例如一個(gè)不知道大小的map或者一個(gè)不知道大小的slice。
逃逸分析的可怕之處就是造成大量的gc包归,因?yàn)槎训臄?shù)據(jù)垃圾回收是go通過gc來回收的锨推。