Go 基于區(qū)域的內(nèi)存管理,再戰(zhàn)手動(dòng)管理!
大家好,我是煎魚(yú)。
最近 Go 核心團(tuán)隊(duì)成員 @Michael Knyszek 發(fā)起針對(duì) memory regions 的社區(qū)討論。
圖片
試圖引入新的基于區(qū)域的內(nèi)存管理(Region-based memory management),并再次之前提到的 Arena 實(shí)驗(yàn)給撈一下。
“基于區(qū)域的內(nèi)存管理” 是什么
在計(jì)算機(jī)科學(xué)中,基于區(qū)域的內(nèi)存管理(Region-Based Memory Management)是一種內(nèi)存管理方式,其中每個(gè)被分配的對(duì)象都會(huì)被歸屬到一個(gè)特定的區(qū)域(下也稱(chēng):region)。
區(qū)域也被稱(chēng)為區(qū)段、場(chǎng)地、空間或內(nèi)存上下文。一個(gè)區(qū)域是多個(gè)已分配對(duì)象的集合,這些對(duì)象可以通過(guò)一次性高效的操作被整體重新分配或釋放。
在優(yōu)勢(shì)上,區(qū)域分配在內(nèi)存的分配和釋放上具有較低的開(kāi)銷(xiāo)!
業(yè)務(wù)背景
煎魚(yú)注:為什么這次叫 “業(yè)務(wù)背景”。因?yàn)槲腋惺艿搅怂麄?Google 想強(qiáng)烈把這個(gè)輪子放進(jìn) Go 里的沖動(dòng)。所以就不是單純的 “背景” 了。
之前有過(guò) Arena 實(shí)驗(yàn)庫(kù)。該類(lèi)型允許直接將數(shù)據(jù)結(jié)構(gòu)分配到其中,并允許批量提前釋放 Arena 內(nèi)存,首次提供了手動(dòng)管理內(nèi)存的方式。一度在圈內(nèi)鬧的很大。
但,作者很無(wú)語(yǔ)的表示:“很不幸的是,由于 Arean 與語(yǔ)言和標(biāo)準(zhǔn)庫(kù)的兼容性較差,將 Arean 添加到標(biāo)準(zhǔn)庫(kù)的提議被無(wú)限期擱置!”
圖片
這次再出現(xiàn),想必就是 Google 團(tuán)隊(duì)里的 Go 同學(xué)還是想再試試。
新提案
提案背景
在原有的 Arean 提案設(shè)計(jì)中,應(yīng)用的 API 要使用 arean,必須接受一個(gè)額外的參數(shù):要分配到哪個(gè) arean。和 context 類(lèi)似。
有太多的應(yīng)用程序 API 需要更新才能很好地與 Go 語(yǔ)言的編寫(xiě)方式集成,而且這會(huì)讓這些應(yīng)用程序接口變得更糟糕。這也是最終被很多人反對(duì)的原因之一。
因此本次新提案提出了一種可組合的方法,即以用戶定義的 goroutine-local 內(nèi)存區(qū)域的形式來(lái)替代原先 arena 的方式。
具體設(shè)計(jì)
本次新提案提出的是新的庫(kù),造一個(gè)新輪子去覆蓋老的輪子(:doge
region 庫(kù)的函數(shù)簽名如下:
package region
func Do(f func())
func Ignore(g func())
一共包含兩個(gè)方法。看起來(lái)很少,但有一定的 “學(xué)問(wèn)” 在。
以下具體講講兩個(gè)方法的作用和使用方向。
1、Do 方法
函數(shù)作用:該方法創(chuàng)建一個(gè)新的 region,并在該 region 中調(diào)用參數(shù) f(閉包函數(shù))。當(dāng) Do 返回時(shí),該 region 會(huì)被銷(xiāo)毀。
核心特性如下:
- 隱式內(nèi)存綁定:在 f 及其調(diào)用鏈中分配的內(nèi)存可能會(huì)被隱式綁定到當(dāng)前的 region。
- 自動(dòng)解綁:內(nèi)存在特定場(chǎng)景會(huì)自動(dòng)從 region 中解綁,例如:
該內(nèi)存被其他 region 引用。
被其他 goroutine 或調(diào)用方(包括 Do 的調(diào)用者及其上層調(diào)用者)引用;或被其他未綁定到此 region 的內(nèi)存引用。
- 資源回收:如果 region 被銷(xiāo)毀時(shí)仍有內(nèi)存綁定到它,這些內(nèi)存將由 runtime 主動(dòng)回收。
- 性能優(yōu)化:
- 正確使用時(shí),可通過(guò)內(nèi)存復(fù)用降低資源成本,減輕垃圾回收(GC)的壓力。
- 錯(cuò)誤使用可能增加資源成本,因?yàn)閺?region 中解綁內(nèi)存也有代價(jià)。
- 局部性:
- region 僅對(duì)創(chuàng)建它的 goroutine 有效,不能傳播到新創(chuàng)建的 goroutine。
- 異常處理:
- 當(dāng) f 的執(zhí)行因?yàn)?panic 或調(diào)用 runtime.Goexit 終止時(shí),region 會(huì)像正常返回一樣銷(xiāo)毀。
2、Ignore 方法
函數(shù)作用:該方法讓 g 及其調(diào)用鏈忽略當(dāng)前 goroutine 上已激活使用的 region。用于排除已知生命周期長(zhǎng)于 region 的內(nèi)存,從而更高效地利用 region。
性能上,使用 Ignore 主動(dòng)排除內(nèi)存比自動(dòng)解綁更高效。作為兜底邏輯,在沒(méi)有激活 region 的情況下調(diào)用 Ignore 方法不會(huì)有任何效果。
使用例子
官方給出的最簡(jiǎn)單的基本例子。
代碼如下:
var keep *int
region.Do(func() {
w := new(int)
x := new(MyStruct)
y := make([]int, 10)
z := make(map[string]string)
*w = use(x, y, z)
keep = w // w 從 region 中解除綁定
}) // x、y 和 z 的內(nèi)存會(huì)被緊急清理,而 w 則不會(huì)。
這個(gè)例子想表述的是:所有主要的內(nèi)置函數(shù)都適用于 region 功能。而且從 region 中泄漏的指針會(huì)導(dǎo)致其指向的內(nèi)存從 region 中解除綁定。
嵌套的使用例子。代碼如下:
region.Do(func() {
z := new(MyStruct)
var y *MyStruct
region.Do(func() {
x := new(MyStruct)
use(x, z) // z 可在該內(nèi)部 region 內(nèi)自由使用
y = x // x 不受任何 region 的約束
})
use(y)
})
這個(gè)例子主要演示 region 嵌套 region 的使用。
總結(jié)
這次 Go 核心團(tuán)隊(duì)想要引入支持手動(dòng)做內(nèi)存管理的決心感覺(jué)非常大。畢竟 arean 被 ban 了后又沉淀了一段時(shí)間,馬上又推出了 region 的新提案。
Region 本次在討論階段,相信很快就會(huì)進(jìn)入下個(gè)階段。我們通過(guò)本文先進(jìn)行快速了解。可以繼續(xù)保持關(guān)注和期待!