[译] Go: 理解 Sync.Pool 的设计
- 原文地址:medium.com/@blanchon.v…
- 原文作者:Vincent Blanchon
- 譯文地址:github.com/watermelo/d…
- 譯者:咔嘰咔嘰
- 譯者水平有限,如有翻譯或理解謬誤,煩請(qǐng)幫忙指出
??本文基于 Go 1.12 和 1.13 版本,并解釋了這兩個(gè)版本之間 sync/pool.go 的演變。
sync 包提供了一個(gè)強(qiáng)大且可復(fù)用的實(shí)例池,以減少 GC 壓力。在使用該包之前,我們需要在使用池之前和之后對(duì)應(yīng)用程序進(jìn)行基準(zhǔn)測(cè)試。這非常重要,因?yàn)槿绻涣私馑鼉?nèi)部的工作原理,可能會(huì)影響性能。
池的限制
我們來(lái)看一個(gè)例子以了解它如何在一個(gè)非常簡(jiǎn)單的上下文中分配 10k 次:
type Small struct {a int }var pool = sync.Pool{New: func() interface{} { return new(Small) }, }//go:noinline func inc(s *Small) { s.a++ }func BenchmarkWithoutPool(b *testing.B) {var s *Smallfor i := 0; i < b.N; i++ {for j := 0; j < 10000; j++ {s = &Small{ a: 1, }b.StopTimer(); inc(s); b.StartTimer()}} }func BenchmarkWithPool(b *testing.B) {var s *Smallfor i := 0; i < b.N; i++ {for j := 0; j < 10000; j++ {s = pool.Get().(*Small)s.a = 1b.StopTimer(); inc(s); b.StartTimer()pool.Put(s)}} } 復(fù)制代碼上面有兩個(gè)基準(zhǔn)測(cè)試,一個(gè)沒(méi)有使用 sync.Pool,另一個(gè)使用了:
name time/op alloc/op allocs/op WithoutPool-8 3.02ms ± 1% 160kB ± 0% 1.05kB ± 1% WithPool-8 1.36ms ± 6% 1.05kB ± 0% 3.00 ± 0% 復(fù)制代碼由于循環(huán)有 10k 次迭代,因此不使用池的基準(zhǔn)測(cè)試在堆上需要 10k 次內(nèi)存分配,而使用了池的基準(zhǔn)測(cè)試僅進(jìn)行了 3 次分配。 這 3 次分配由池產(chǎn)生的,但卻只分配了一個(gè)結(jié)構(gòu)實(shí)例。目前看起來(lái)還不錯(cuò);使用 sync.Pool 更快,消耗更少的內(nèi)存。
但是,在一個(gè)真實(shí)的應(yīng)用程序中,你的實(shí)例可能會(huì)被用于處理繁重的任務(wù),并會(huì)做很多頭部?jī)?nèi)存分配。在這種情況下,當(dāng)內(nèi)存增加時(shí),將會(huì)觸發(fā) GC。我們還可以使用命令 runtime.GC() 來(lái)強(qiáng)制執(zhí)行基準(zhǔn)測(cè)試中的 GC 來(lái)模擬此行為:(譯者注:在 Benchmark 的每次迭代中添加runtime.GC())
name time/op alloc/op allocs/op WithoutPool-8 993ms ± 1% 249kB ± 2% 10.9k ± 0% WithPool-8 1.03s ± 4% 10.6MB ± 0% 31.0k ± 0% 復(fù)制代碼我們現(xiàn)在可以看到,在 GC 的情況下池的性能較低,分配數(shù)和內(nèi)存使用也更高。我們繼續(xù)更深入地了解原因。
池的內(nèi)部工作流程
深入了解 sync/pool.go 包的初始化,可以幫助我們之前的問(wèn)題的答案:
func init() {runtime_registerPoolCleanup(poolCleanup) } 復(fù)制代碼他將注冊(cè)一個(gè)在運(yùn)行時(shí)清理 pool 對(duì)象的方法。GC 在文件 runtime/mgc.go 中將觸發(fā)這個(gè)方法:
func gcStart(trigger gcTrigger) {[...]// 在開(kāi)始 GC 前調(diào)用 clearpoolsclearpools() 復(fù)制代碼這就解釋了為什么在調(diào)用 GC 時(shí)性能較低。因?yàn)槊看?GC 運(yùn)行時(shí)都會(huì)清理 pool 對(duì)象(譯者注:pool 對(duì)象的生存時(shí)間介于兩次 GC 之間)。文檔也告知我們:
存儲(chǔ)在池中的任何內(nèi)容都可以在不被通知的情況下隨時(shí)自動(dòng)刪除
現(xiàn)在,讓我們創(chuàng)建一個(gè)流程圖以了解池的管理方式:
對(duì)于我們創(chuàng)建的每個(gè) sync.Pool,go 生成一個(gè)連接到每個(gè)處理器(譯者注:處理器即 Go 中調(diào)度模型 GMP 的 P,pool 里實(shí)際存儲(chǔ)形式是 [P]poolLocal)的內(nèi)部池 poolLocal。該結(jié)構(gòu)由兩個(gè)屬性組成:private 和 shared。第一個(gè)只能由其所有者訪問(wèn)(push 和 pop 不需要任何鎖),而 shared 屬性可由任何其他處理器讀取,并且需要并發(fā)安全。實(shí)際上,池不是簡(jiǎn)單的本地緩存,它可以被我們的應(yīng)用程序中的任何 線程/goroutines 使用。
Go 的 1.13 版本將改進(jìn) shared 的訪問(wèn),并且還將帶來(lái)一個(gè)新的緩存,以解決 GC 和池清理相關(guān)的問(wèn)題。
新的無(wú)鎖池和 victim 緩存
Go 1.13 版將 shared 用一個(gè)雙向鏈表poolChain作為儲(chǔ)存結(jié)構(gòu),這次改動(dòng)刪除了鎖并改善了 shared 的訪問(wèn)。以下是 shared 訪問(wèn)的新流程:
使用這個(gè)新的鏈?zhǔn)浇Y(jié)構(gòu)池,每個(gè)處理器可以在其 shared 隊(duì)列的頭部 push 和 pop,而其他處理器訪問(wèn) shared 只能從尾部 pop。由于 next/prev 屬性,shared 隊(duì)列的頭部可以通過(guò)分配一個(gè)兩倍大的新結(jié)構(gòu)來(lái)擴(kuò)容,該結(jié)構(gòu)將鏈接到前一個(gè)結(jié)構(gòu)。初始結(jié)構(gòu)的默認(rèn)大小為 8。這意味著第二個(gè)結(jié)構(gòu)將是 16,第三個(gè)結(jié)構(gòu) 32,依此類(lèi)推。
此外,現(xiàn)在 poolLocal 結(jié)構(gòu)不需要鎖了,代碼可以依賴于原子操作。
關(guān)于新加的 victim 緩存(譯者注:關(guān)于引入 victim 緩存的 commit,引入該緩存就是為了解決之前 Benchmark 那個(gè)問(wèn)題),新策略非常簡(jiǎn)單。現(xiàn)在有兩組池:活動(dòng)池和存檔池(譯者注:allPools 和 oldPools)。當(dāng) GC 運(yùn)行時(shí),它會(huì)將每個(gè)池的引用保存到池中的新屬性(victim),然后在清理當(dāng)前池之前將該組池變成存檔池:
// 從所有 pool 中刪除 victim 緩存 for _, p := range oldPools {p.victim = nilp.victimSize = 0 }// 把主緩存移到 victim 緩存 for _, p := range allPools {p.victim = p.localp.victimSize = p.localSizep.local = nilp.localSize = 0 }// 非空主緩存的池現(xiàn)在具有非空的 victim 緩存,并且池的主緩存被清除 oldPools, allPools = allPools, nil 復(fù)制代碼有了這個(gè)策略,應(yīng)用程序現(xiàn)在將有一個(gè)循環(huán)的 GC 來(lái) 創(chuàng)建/收集 具有備份的新元素,這要?dú)w功于 victim 緩存。在之前的流程圖中,將在請(qǐng)求"shared" pool 的流程之后請(qǐng)求 victim 緩存。
轉(zhuǎn)載于:https://juejin.im/post/5d006254e51d45776031afe3
總結(jié)
以上是生活随笔為你收集整理的[译] Go: 理解 Sync.Pool 的设计的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: nginx实现大小写字母转换(ngx_h
- 下一篇: The Web Audio autopl