Go 中的分段棧和連續(xù)棧的區(qū)別?
# 分段棧
在 Go 1.3 版本之前 ,使用的棧結(jié)構(gòu)是分段棧,隨著goroutine 調(diào)用的函數(shù)層級的深入或者局部變量需要的越來越多時,運行時會調(diào)用 runtime.morestack 和 runtime.newstack創(chuàng)建一個新的??臻g,這些??臻g是不連續(xù)的,但是當(dāng)前 goroutine 的多個棧空間會以雙向鏈表的形式串聯(lián)起來,運行時會通過指針找到連續(xù)的棧片段。
分段棧雖然能夠按需為當(dāng)前 goroutine 分配內(nèi)存并且及時減少內(nèi)存的占用,但是它也存在一個比較大的問題:
如果當(dāng)前 goroutine 的棧幾乎充滿,那么任意的函數(shù)調(diào)用都會觸發(fā)棧的擴容,當(dāng)函數(shù)返回后又會觸發(fā)棧的收縮,如果在一個循環(huán)中調(diào)用函數(shù),棧的分配和釋放就會造成巨大的額外開銷,這被稱為熱分裂問題(Hot split)。
為了解決這個問題,Go 在 1.2 版本的時候不得不將棧的初始化內(nèi)存從 4KB 增大到了 8KB。后來把采用連續(xù)棧結(jié)構(gòu)后,又把初始棧大小減小到了 2KB。
# 連續(xù)棧
連續(xù)??梢越鉀Q分段棧中存在的兩個問題,其核心原理就是每當(dāng)程序的??臻g不足時,初始化一片比舊棧大兩倍的新棧并將原棧中的所有值都遷移到新的棧中,新的局部變量或者函數(shù)調(diào)用就有了充足的內(nèi)存空間。使用連續(xù)棧機制時,??臻g不足導(dǎo)致的擴容會經(jīng)歷以下幾個步驟:
- 調(diào)用用 runtime.newstack 在內(nèi)存空間中分配更大的棧內(nèi)存空間;
- 使用 runtime.copystack 將舊棧中的所有內(nèi)容復(fù)制到新的棧中;
- 將指向舊棧對應(yīng)變量的指針重新指向新棧;
- 調(diào)用 runtime.stackfree銷毀并回收舊棧的內(nèi)存空間;
copystack會把舊棧里的所有內(nèi)容拷貝到新棧里然后調(diào)整所有指向舊棧的變量的指針指向到新棧, 我們可以用下面這個程序驗證下,棧擴容后同一個變量的內(nèi)存地址會發(fā)生變化。
- package main
- func main() {
- var x [10]int
- println(&x)
- a(x)
- println(&x)
- }
- //go:noinline
- func a(x [10]int) {
- println(`func a`)
- var y [100]int
- b(y)
- }
- //go:noinline
- func b(x [100]int) {
- println(`func b`)
- var y [1000]int
- c(y)
- }
- //go:noinline
- func c(x [1000]int) {
- println(`func c`)
- }
程序的輸出可以看到在棧擴容前后,變量x的內(nèi)存地址的變化:
- 0xc000030738
- ...
- ...
- 0xc000081f38
是不是很簡單呢?
本文轉(zhuǎn)載自微信公眾號「Go編程時光」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系Go編程時光公眾號。























