[翻訳]行く理解Sync.Poolデザイン

ℹ️はゴー1.12とバージョン1.13をベースにしており、2つのバージョン間の同期/ pool.goの進化を説明しています。

syncパッケージは、圧力GCを軽減する堅牢かつ再利用可能なインスタンスのプールを提供します。このパッケージを使用する前に、我々は、アプリケーションのと後のベンチマークの前にプールを使用する必要があります。その内部の仕組みを理解していない場合は、パフォーマンスに影響を与える可能性があるので、これは、非常に重要です。

プールの制限

のは、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 *Small
   for 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 *Small
   for i := 0; i < b.N; i++ {
      for j := 0; j < 10000; j++ {
         s = pool.Get().(*Small)
         s.a = 1
         b.StopTimer(); inc(s); b.StartTimer()
         pool.Put(s)
      }
   }
}
复制代码

上記の二つのベンチマーク、未使用sync.Pool、別の用途があります。

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%
复制代码

3つのだけ配布を行い、プールベンチマークを使用しながら10Kループ反復があるので、プールが使用されていないベンチマークヒープメモリ割り当ては10K倍を必要とします。これは、プールによって割り当てられた3回発生するが、唯一の一構成例を割り当てられました。さて、それはかなり良いようだ、より速く、より少ないメモリを消費sync.Poolを使用しています。

しかし、実際のアプリケーションでは、あなたのインスタンスが重いタスクを処理するために使用することができるとヘッドメモリ割り当ての多くを行います。メモリを追加するときに、この場合、それはGCをトリガします。また、コマンドを使用することができruntime.GC()、この動作:(翻訳者注をエミュレートするためにGCのベンチマークを実施する:ベンチマークの各反復を追加します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%
复制代码

私たちは今、プールの低GC性能の場合には、割り当てとメモリ使用量の数も高くなっている、ことがわかります。私たちはより良いの理由を理解するために続けています。

内部ワークフロー・プール

で、深さの理解sync/pool.go私たちを助けることができ、パッケージの初期化は、前の質問に答えます:

func init() {
   runtime_registerPoolCleanup(poolCleanup)
}
复制代码

彼は、ランタイムオブジェクトプールでクリーンアップメソッドを登録します。文書中のGC runtime/mgc.go、このメソッドをトリガーになります。

func gcStart(trigger gcTrigger) {
   [...]
   // 在开始 GC 前调用 clearpools
   clearpools()
复制代码

あなたがGCを呼び出すとき、これはなぜ低いの性能を説明しています。(2つのGC間の生存プールオブジェクト翻訳者注)各GCの実行時には、プールオブジェクトを清掃しているため。文書はまた、私たちに語りました:

ストレージが自動的にプールにあるように任意のコンテンツで予告なしにいつでも削除することができます

さて、プールの管理を理解するためのフローチャートを作成してみましょう:

私たちが作成した各についてsync.Pool、それぞれに結合されたプロセッサを生成するために行く(翻訳者注:GOを、すなわち、GMP P、実際の形式で保存されたプールのプロセッサスケジューリングモデル[P]poolLocal)内部プールpoolLocal構造は、次の2つの属性で構成されていますprivateshared第一は、その所有者(すべてのロックなしのプッシュ及びポップ)、及びによってアクセスすることができるshared読み取るために、任意の他のプロセッサによって性およびセキュリティにより複雑化を必要とします。実際には、プールが単純なローカルキャッシュではありません、それはどのスレッドで使用することができます/私たちのアプリケーションをゴルーチン。

改善されたバージョン1.13を行くsharedへのアクセスを、また、関連した問題とGCタンク清掃を解決するために新しいキャッシュをもたらすでしょう。

新しいロックフリープールと被害者のキャッシュ

ます1.13バージョンを行くshared使い二重リンクリストをpoolChain格納構造として、変更はロックを削除し、改善されたsharedアクセスを。以下は、shared新しいプロセスの訪問:

新しい鎖構造のプールを使用して、各プロセッサは、その中にあってもよいshared他のプロセッサがアクセスしながら、キューヘッドプッシュとポップshared尾popからのみ。以来next/ prev属性sharedキューの先頭には、以前の構造にリンクされる新しい構造を、割り当てることによって、拡張の倍の大きさであってもよいです。初期構造のデフォルトサイズは8です。これには、構造は第16、第32の構造になることを意味します。

さらに、今poolLocal構造は、コードがアトミック動作に依存することができる、ロックを必要としません。

プラス新しいキャッシュ(翻訳者注:キャッシュ被害者の導入についてについて被害者コミット、キャッシュの導入はベンチマークの前にその問題を解決することである)は、新しい戦略は非常に簡単です。二つのプールがあります。プールとアーカイブ活動プール(翻訳者注:allPoolsoldPools)。GCが実行されると、プールはグループアーカイブプールになる前に、現在のプールをクリーンアップする、新しいプロパティ(被害者)を保存するためにプールに各プールを参照します。

// 从所有 pool 中删除 victim 缓存
for _, p := range oldPools {
   p.victim = nil
   p.victimSize = 0
}

// 把主缓存移到 victim 缓存
for _, p := range allPools {
   p.victim = p.local
   p.victimSize = p.localSize
   p.local = nil
   p.localSize = 0
}

// 非空主缓存的池现在具有非空的 victim 缓存,并且池的主缓存被清除
oldPools, allPools = allPools, nil
复制代码

この戦略では、アプリケーションは現在、新しい要素を収集/ GCのサイクルを作成する必要があります、被害者のキャッシュのおかげでバックアップしています。前回のフローチャートの後、「共有」プールは、要求ビクティムキャッシュに流れます。

ます。https://juejin.im/post/5d006254e51d45776031afe3で再現

おすすめ

転載: blog.csdn.net/weixin_34009794/article/details/93173435