Golang GC についていくつか誤解がありますが、本当に Java アルゴリズムよりも高度なのでしょうか?

Golang GC についていくつか誤解がありますが、本当に Java アルゴリズムよりも高度なのでしょうか?

[[273650]]

まず最初に強調しておきたいのは、この記事の発端は High Availability Architecture Back Garden Group でのチャットだということです。Golang の GC が Java の ZGC や Java の CMS GC に似ているかどうかについて、みんなが議論していました。私の個人的な意見としては、Golang の GC は Java の CMS GC に似ています。公式の mgc コメントには次のように書かれています。

  1. // GCはミューテータースレッド並行して実行され型が正確(つまり正確)であり、複数
  2. // GCスレッドを並列実行しますこれは書き込みバリアを使用する同時マークアンドスイープです 
  3. // 非世代的かつ非圧縮。割り当てはP割り当てごとにサイズを分割して行われます
  4. //一般的なケースでは、ロックを排除しながら断片化を最小限に抑える領域

ミューテーターは、メモリの状態を変更する可能性があるため、ミューテーターと名付けられたアプリケーションを指します。この文章を翻訳すると、Go の GC はソート処理のない非世代的 Concurrent Mark and Sweep アルゴリズム (CMS アルゴリズム) を使用する、と大まかに意味します。マーキング処理 (つまり Mark 処理) では 3 色マーキング方式が使用されることを付け加えておきます。議論の中で、2 つの誤った見解が浮かび上がりました。1 つは、CMS アルゴリズムは世代別でなければならないというもので、もう 1 つは、3 色マーキング方式の使用は ZGC アプローチに似ているというものでした (個人的には、なぜこの見解が存在するのかわかりませんし、当時は明確に質問するのを忘れていました。おそらく、3 色マーキング方式は ZGC のポインターの色付けと混同されていたのでしょう)。 CMS を世代に分ける必要があるかどうかについては、まずは序論を述べた後、R 氏の「並行 GC のためにオブジェクトを移動しない限り、最終的には何らかの形の CMS が実現される」という言葉を使って結論を出します。

マークスイープアルゴリズム

マークスイープアルゴリズムは、オブジェクトが消滅した直後にクリーンアップするのではなく、特定の条件下でトリガーされ、システム内で生き残っているオブジェクトを均一にチェックし、リサイクル作業を実行する追跡型ガベージコレクションアルゴリズムです。

マークスイープは、マーキングとスイープの 2 つの部分に分かれています。マーキング プロセスでは、すべてのオブジェクトを走査し、デッド オブジェクトを検出します。オブジェクトの生存は、GC ROOT からのオブジェクトの到達可能性によって確認できます。つまり、最終的にオブジェクトを指す GC ROOT からの参照がある場合、そのオブジェクトは生存していると見なされます。この方法では、生きていることが証明できないオブジェクトを、死んでいるとマークすることができます。マークした後、再度トラバースして、死亡が確認されたオブジェクトをクリーンアップします。

マークスイープは、並行して実行されるマークスイープアルゴリズム、つまり CMS です。 3 色マーキングは、オブジェクトをマークするために使用されるアルゴリズムです。

Go GC 改善履歴

  • 1.3 より前のバージョンでは、マーク アンド スイープ アプローチが使用され、プロセス全体で STW が必要でした。
  • バージョン1.3では、マーキングとクリーニングの動作が分離され、マーキング処理はSTWで、クリーニング処理は並行して実行されます。
  • バージョン 1.5 では、マーキング プロセス中に 3 色マーキング方式が使用されます。リサイクルプロセスは主に 4 つのステージで構成され、その中でマーキングとクリーニングは同時に実行されますが、STW ではマーキング ステージの前後に GC の準備とスタックの再スキャンのために一定の時間が必要です。
  • バージョン1.8では、マーク終了時間を短縮するためにハイブリッドバリア再スキャンが導入されました。

GCプロセス1.5

  1. スイープ終了: ルート オブジェクトを収集し、前のラウンドでクリーンアップされなかったスパンをクリーンアップし、書き込みバリアと補助 GC を有効にします。補助 GC は、一定量のマーキングとスイープの作業をユーザー ゴルーチンに引き渡します。書き込みバリアについては、後で詳しく説明します。
  2. マーク: すべてのルートオブジェクトとルートオブジェクトを介して到達可能なオブジェクトをスキャンし、マークします。
  3. マーク終了: マーキング作業を完了し、一部のルートオブジェクトを再スキャンし(STWが必要)、書き込みバリアと補助GCをオフにします。
  4. スイープ: 結果をタグ付けしてオブジェクトをクリーンアップする

GC1.8 について

1.8 最初の STW を最小限に抑えるために混合バリアを導入します。混合バリアとは、次のものを指します。

書き込みバリア。ポインタ f に書き込むときに C オブジェクトをグレーでマークします。 Go 1.5 で使用される Dijkstra 書き込みバリアはこの原則に基づいています。疑似コードは次のとおりです。

  1. writePointer(スロット、ptr):
  2. シェード(ptr)
  3. *スロット = ptr

バリアを取り除くために、使用される Yuasa バリア疑似コードは次のとおりです。

  1. writePointer(スロット、ptr):
  2. if (isGery(スロット) || isWhite(スロット))
  3. シェード(*スロット)
  4. *スロット = ptr

1.8 で導入されたハイブリッド バリア、書き込みバリア、削除バリアにはそれぞれ長所と短所があります。ダイクストラ書き込みバリアは、マーキングの開始時に STW を必要とせず、直接開始して同時に実行できますが、最後にスタックを再スキャンしてスタック上で参照されている白いオブジェクトの生存をマークするために STW が必要です。ユアサ削除バリアでは、GC の開始時にスタックをスキャンして初期スナップショットを記録するために STW が必要です。このプロセスでは、開始時にすべての生存オブジェクトが記録されますが、最後に STW は必要ありません。 Go 1.8 で導入されたハイブリッド書き込みバリアは、Yuasa の削除書き込みバリアと Dijkstra の書き込み書き込みバリアを組み合わせ、両方の利点を兼ね備えています。疑似コードは次のとおりです。

  1. writePointer(スロット、ptr):
  2. シェード(*スロット)
  3. 現在のスタック灰色の場合:
  4. シェード(ptr)
  5. *スロット = ptr

したがって、私の個人的な理解では、マーク初期化フェーズの開始時にハイブリッド書き込みバリアがアクティブになると、STW が実行されます。再スキャン フェーズでは、ハイブリッド書き込みバリアが削除された場合にのみ STW が実行されるはずです。アルゴリズムの観点から見ると、ZGC よりも Java CMS アルゴリズムに近いです。もちろん、Go GC には Java CMS GC と比較して多くの実装の最適化があります。

詳細を理解するために、GC の詳細について多くを語った William の記事を見つけました。翻訳は次のようになります。

Go 1.12 では、Go ガベージ コレクターは、世代を問わない並行 3 色マーク アンド スイープ アルゴリズムを引き続き使用します。この記事にある Ken Fox の GC に関するアニメーションは素晴らしいです。 Go のガベージ コレクターの実装は、Go のバージョンごとに変わります。したがって、次のバージョンがリリースされると、多くの詳細が異なる可能性があります。

ガベージコレクタの動作

Go ガベージ コレクターの動作は、マーク フェーズとスイープ フェーズという 2 つの主要なフェーズに分かれています。マーク フェーズは 3 つのステップに分かれており、そのうちの 2 つには STW (Stop The World) があり、もう 1 つには遅延があり、アプリケーションの遅延を引き起こし、スループットを低下させます。3 つのステップは次のとおりです。

  • マークセットアップフェーズ - STW
  • マーキングフェーズ - 同時実行
  • マーク終了フェーズ - STW

以下で一つずつ説明していきましょう。

マーク設定

ガベージ コレクションが開始されると、最初に実行する必要があるアクションは、書き込みバリアをオンにすることです。書き込みバリアの目的は、ガベージ コレクターとアプリケーションが同時に実行されるため、ガベージ コレクション中にガベージ コレクターがヒープ上のデータの整合性を維持できるようにすることです。

書き込みバリアをオンにするには、各 goroutine を停止する必要があります。このアクションは通常非常に高速で、平均 10 ~ 30 マイクロ秒以内に完了します。

上の図は、ガベージ コレクションが開始される前にアプリケーションを実行する 4 つの goroutine を示しています。すべての goroutine を一時停止する唯一の方法は、ガベージ コレクターに各 goroutine が関数呼び出しを行うのを監視して待機させることです。関数呼び出しを待機する目的は、goroutine が安全なポイントで停止することを保証することです。この場合、ゴルーチンの 1 つが関数呼び出しを行わず、他のゴルーチンが関数呼び出しを行うとどうなるでしょうか?

上の写真は問題を示しています。 P4 で実行されている goroutine が停止されるまで、ガベージ コレクションを開始できません。ただし、P4 は次のループ内にあるため、ガベージ コレクターを起動できない可能性があります。

  1. 関数add (numbers [] int ) int {
  2. var v int  
  3. _の場合、n := 範囲の数値 {
  4. v += n
  5. }
  6. リターンv
  7. }

上記のコード スニペットは、P4 で実行されるコードです。 go ルーチンの実行時間はスライスのサイズによって異なります。このコードはガベージコレクターの起動を防ぎます。さらに悪いことに、ガベージ コレクターが P4 を待機している間、他の P もサービスを提供できなくなります。したがって、GC にとって、goroutine が妥当な時間枠内で関数呼び出しを行うことは非常に重要です。

マーキングステージ

書き込みバリアがオンになると、ガベージ コレクターはマーキング フェーズを開始します。ガベージ コレクターが最初に行うことは、CPU の 25% を占有することです。ガベージ コレクターは、ガベージ コレクション作業を実行するために Goroutine を使用します。つまり、4 スレッドの Go プログラムの場合、1 つの P がガベージ コレクション作業専用になります。

上の図では、P1 はガベージ コレクション専用です。これで、ガベージ コレクターはマーキング フェーズを開始できます。マーキング フェーズでは、ヒープ メモリ内でまだ使用中の値をマークする必要があります。まず、既存のすべての goroutine のスタックをチェックして、ヒープ メモリのルート ポインターを見つけます。次に、コレクターはそれらのルート ポインターからヒープ メモリ グラフをトラバースし、再利用できるメモリをマークする必要があります (翻訳者注: マーキング アルゴリズムは、いわゆる 3 色マーキング アルゴリズムです)。 P1 でマーキング作業が進行中でも、P2、P3、P4 でのアプリケーションは続行できます。これは、ガベージ コレクターの影響が現在の CPU の 25% に最小限に抑えられたことを意味します。

これは理想的な状況ですが、現実はそれほど単純ではありません。ガベージ コレクション中に、ヒープが制限に達する前に P1 がマーキングを完了できない場合 (アプリケーションが大量のメモリを割り当てている可能性があるため) はどうなりますか? 3 つの Goroutine のうち 1 つだけが大量のメモリを割り当て、P1 がマーキング作業を完了できない場合はどうなりますか? この場合、特に割り当てを開始した Go ルーチンがメモリを割り当てるときに、新しいメモリの割り当て速度が低下します。

ガベージ コレクターがメモリ割り当てを遅くする必要があると判断した場合、元々アプリケーションを実行していた Goroutine がマーキング作業を支援します。アプリケーション Goroutine が Mark Assist 状態にある時間の長さは、要求するヒープ メモリの量に比例します。 Mark Assist は、ガベージ コレクションをより速く完了するのに役立ちます。

上記の画像は、P3 上で実行されているアプリケーション Goroutine が現在 Mark Assist を実行し、コレクション作業を行っているところを示しています。

ガベージ コレクターの設計目標の 1 つは、マーク アシストの必要性を減らすことです。現在のガベージ コレクションで、作業を完了するために大量のマーク アシストが必要になる場合、ガベージ コレクターは次のガベージ コレクション サイクルを早期に開始します。そうすることで、次回のガベージ コレクションに必要なマーク アシストの量を削減できます。

マーク終了

同時マーキング フェーズが完了すると、次のフェーズは終了のマーキングです。最後に、書き込みバリアがオフになり、さまざまなクリーンアップ タスクが実行され、次のガベージ コレクション サイクルのターゲットが計算されます。ループ内にある goroutine によって、stw が長くなる可能性もあります (マーク セットアップの場合と同様)。

上の図は、マーク終了フェーズが完了したときにすべての goroutine が停止される様子を示しています。このアクションが完了するまでに平均 60 ~ 90 マイクロ秒かかります。このフェーズは STW なしでも実行できますが、STW を使用するとコードがシンプルになります。

コレクションが完了すると、すべての P がアプリケーションの Goroutines によって再び使用できるようになり、アプリケーションはフルスロットルで再開されます。

上の図は、ガベージ コレクションが完了した後、すべての P がアプリケーションで使用可能になることを示しています。

同時クリーンアップ

マーキングが完了すると、次のフェーズでは同時クリーンアップが実行されます。クリーンアップ フェーズは、マーキング フェーズでマークされた再利用可能メモリを再利用するために使用されます。この操作は、アプリケーションの goroutine がヒープ メモリに新しいメモリを割り当てようとしたときにトリガーされます。クリーンアップによって生じるレイテンシとスループットの低下は、各メモリ割り当てに分散されます。

以下は、goroutine の実行に使用できるハードウェア スレッドが 12 個ある私のマシン上のトレースの例です。

上の図は部分的なトレースのスナップショットを示しています。このガベージ コレクション中、合計 12 個の P のうち 3 個が GC 専用になります。この間、Goroutine 2450、1978、および 2696 はアプリケーションを実行するのではなく、Mark Assist 作業を行っていたことがわかります。マークの最後には、GC 専用の P が 1 つだけ存在し、最終的に STW (マークから停止まで) 作業を実行します。

ガベージ コレクションが完了すると、アプリケーションはほぼフル稼働に戻りますが、Goroutine の下にバラ色の線が多数表示されます。

上の図のバラ色の線は、アプリケーションを実行する代わりにクリーンアップ作業を実行する Goroutine を表しています。これは、Goroutine が新しいメモリを割り当てようとするポイントでもあります。

上の図は、Sweep プロセス中の Goroutines スタック トレースの一部を示しています。新しいメモリを割り当てるには、runtime.mallocgc を呼び出します。最後に、runtime.(*mcache).nextFree が呼び出され、クリーンアップが実行されます。再利用可能なメモリがすべて再利用可能になると、 nextFree への呼び出しは行われなくなります。

ここで説明した動作は、ガベージ コレクションが起動して実行されている場合にのみ発生します。 GC パーセンテージ構成オプションは、ガベージ コレクションがいつ発生するかを決定する上で重要な役割を果たします。

GCパーセント

ランタイムには GC パーセンテージの構成オプションがあり、デフォルトでは 100 になっています。この値は、次のガベージ コレクションを開始する前に割り当てることができる新しいメモリの量の比率を表します。 GC パーセンテージを 100 に設定すると、ガベージ コレクションが完了した後にライブとしてマークされたヒープ メモリの量に基づいて、次のガベージ コレクションの前にヒープ メモリの使用量が 100% 増加する可能性があります。

たとえば、コレクションで 2MB のヒープ メモリが使用されているとします。

注: この記事のヒープ メモリ図は、Go を使用する場合の実際の使用状況を表すものではありません。 Go のヒープメモリは断片化されています。図は概略図のみです。

上記の画像は、最後のガベージ完了後に使用中のヒープ メモリが 2MB であることを示しています。 GC パーセンテージが 100% に設定されているため、2 MB のヒープ メモリが追加されると次のコレクションが開始されます。

上の図はヒープメモリが 2MB 増加したことを示しています。これにより、ガベージ コレクションがトリガーされます。各 GC の GC トレースを生成し、関連するアクションを表示できます。

GC トレース

任意の Go アプリケーションの実行中に、環境変数 GODEBUG と gctrace=1 オプションを使用して GC トレースを生成できます。ガベージ コレクションが発生するたびに、ランタイムは GC トレース情報を stderr に書き込みます。

  1. GODEBUG=gctrace=1 ./app
  2. gc 1405 @6.068s 11%: 0.058+1.2+0.083 ms クロック、0.70+2.5/1.5/0+0.99 ms CPU、7->11->6 MB、10 MB 目標、12 P
  3. gc 1406 @6.070s 11%: 0.051+1.8+0.076 ms クロック、0.61+2.0/2.5/0+0.91 ms CPU、8->11->6 MB、13 MB 目標、12 P
  4. gc 1407 @6.073s 11%: 0.052+1.8+0.20 ms クロック、0.62+1.5/2.2/0+2.4 ms CPU、8->14->8 MB、13 MB 目標、12 P

上記は、GODEBUG 変数を使用して GC トレースを生成する方法を示しています。また、実行中の Go アプリケーションによって生成された 3 つのトレース メッセージも表示されます。

GC トレースの各値の意味の内訳は次のとおりです。

  1. gc 1405 @6.068s 11%: 0.058+1.2+0.083 ms クロック、0.70+2.5/1.5/0+0.99 ms CPU、7->11->6 MB、10 MB 目標、12 P
  2.  
  3. // 一般的な
  4. gc 1404 : プログラム開始以降に実行された 1404 回の GC
  5. @6.068s : プログラム開始から6秒
  6. 11% :利用可能なCPU11%がGC費やされています
  7.  
  8. // 壁掛け時計
  9. 0.058ms : STW : マーク開始 - 書き込みバリアオン 
  10. 1.2ms : 同時 : マーキング
  11. 0.083ms : STW : マーク終了 - 書き込みバリアオフ そして掃除する
  12.  
  13. //CPU時間 
  14. 0.70ms : STW : マーク開始
  15. 2.5ms : 同時実行 : マーク - アシスト時間(割り当て合わせGC を実行)
  16. 1.5ms : 同時実行 : マーク - バックグラウンドGC時間 
  17. 0ms : 同時実行 : マーク - アイドルGC時間 
  18. 0.99ms : STW : マーク期間
  19.  
  20. //メモリ
  21. 7MB:マーキング開始前に使用されていヒープメモリ
  22. 11MB:マーキング完了後に使用中のヒープメモリ
  23. 6MB:マーキングが完了した後にライブとしてマークされたヒープメモリ
  24. 10MB:マーキング完了後に使用中ヒープメモリ収集目標
  25.  
  26. //スレッド
  27. 12P : Goroutine を実行するために使用される論理プロセッサまたはスレッド

上記はGCトレース(1405)です。これらのほとんどは最終的には取り上げますが、今は 1045 GC トレースのメモリ部分に焦点を当てます。

  1. // Memory7MB:マーキング開始前に使用されていヒープメモリ
  2. 11MB:マーキング完了後に使用中のヒープメモリ
  3. 6MB:マーキングが完了した後にライブとしてマークされたヒープメモリ
  4. 10MB:マーキング完了後に使用中ヒープメモリ収集目標

この GC トレースは、マーキング作業が開始される前は使用中のヒープ メモリの量が 7MB であったことを示しています。マーキング作業が完了すると、使用中のヒープメモリの量は 11MB に達します。これは、収集プロセス中に 4MB の新しいメモリが割り当てられたことを意味します。マーキングが完了した後のアクティブヒープメモリの量は 6MB です。これは、次のガベージ コレクションが開始される前に、アプリケーションがヒープ メモリを 12 MB に増やすことができることを意味します。

ガベージコレクターマークのターゲット値と実際の値には 1MB の差があることがわかります。マーキング作業が完了した後の使用中のヒープメモリの量は、10 MB ではなく 11 MB になります。マークターゲットは、現在使用されているヒープメモリの量などの情報に基づいて計算されるためです。アプリケーションの変更により、マーキング後のヒープ メモリの使用量が増加しました。

次の GC トレース (1406) を見ると、2 ミリ秒の間に多くの変更が行われたことがわかります。

  1. gc 1406 @6.070s 11%: 0.051+1.8+0.076 ms クロック、0.61+2.0/2.5/0+0.91 ms CPU、8->11->6 MB、13 MB 目標、12 P
  2.  
  3. //メモリ
  4. 8MB:マーキング開始前に使用されていヒープメモリ
  5. 11MB:マーキング完了後に使用中のヒープメモリ
  6. 6MB:マーキングが完了した後にライブとしてマークされたヒープメモリ
  7. 13MB:マーキング完了後に使用中ヒープメモリ収集目標

これは、このガベージ コレクションが前回のガベージ コレクションの開始から 2 ミリ秒 (6.068 秒対 6.070 秒) 後に開始され、使用中のヒープ メモリが 8 MB に達したことを示しています。アプリケーションが大量のメモリを割り当て、ガベージ コレクターがこのコレクション中にマーク アシストによって発生する遅延を削減しようとするため、ガベージ コレクションが早期に発生することがあります。

さらに注目すべき点が 2 つあります。今回はガベージコレクターがその目的を達成しました。マーキングが完了した後の使用中のヒープ メモリの量は、13 MB ではなく 11 MB となり、2 MB 減少します。マーキングが完了した後も、アクティブなヒープ メモリは 6 MB のままです。

gcpacertrace=1 を追加すると、GC トレースからより詳細な情報を取得できます。これにより、ガベージ コレクターは同時実行ペースメーカーの内部状態に関する情報を出力します。

  1. $ エクスポート GODEBUG=gctrace=1,gcpacertrace=1 ./app
  2.  
  3. サンプル出力:
  4. gc 5 @0.071s 0%: 0.018+0.46+0.071 ms クロック、0.14+0/0.38/0.14+0.56 ms CPU、29->29->29 MB、目標 30 MB、8 P
  5. ペースメーカー:ヒープサイズ29MBスイープが完了しました。0MBスパンを割り当てました。 +6.183550e-004 ページ/バイト3752 ページをスイープしました。
  6. ペースメーカー: アシスト率=+1.232155e+000 (1 MB を 70->71 MBスキャン) ワーカー=2+0
  7. ペースメーカー: H_m_prev=30488736 h_t=+2.334071e-001 H_T=37605024 h_a=+1.409842e+000 H_a=73473040 h_g=+1.000000e+000 H_g=60977472 u_a=+2.500000e-001 u_g=+2.500000e-001 W_a=308200 goalΔ=+7.665929e-001 actualΔ=+1.176435e+000 u_a/u_g=+1.000000e+000

GC トレースを実行すると、アプリケーションの健全性とコレクターの速度について多くのことがわかります。コレクターの動作速度は、収集プロセスにおいて重要な役割を果たします。

ペース

ガベージ コレクターは、ガベージ コレクションをいつ開始するかを決定するために使用されるペーシング アルゴリズムを使用します。このアルゴリズムは、実行中のアプリケーションに関する情報と、メモリを割り当てるアプリケーションにかかる負荷に依存します。プレッシャーは、アプリケーションが特定の時間にヒープ メモリを割り当てる速度です。ガベージコレクターの速度を決定するのは圧力です。

ガベージ コレクターはコレクションを開始する前に、コレクションが完了するまでに予想される時間を計算します。ガベージ コレクターの実行が開始されると、実行中のアプリケーションに影響が及び、遅延が発生し、プログラムの速度が低下します。各コレクションにより、アプリケーションの全体的なレイテンシが増加します。コレクターの起動頻度を下げることは、パフォーマンスを向上させる方法ではありません。

GC パーセンテージ値を 100 より大きい値に変更できます。これにより、次のコレクションが開始される前に割り当てることができるヒープ メモリの量が増加します。また、ガベージコレクションにかかる時間も長くなります。

上の画像は、GC パーセンテージを変更するとヒープ メモリがどのように割り当てられるかを示しています。 メモリを多く使用するとガベージコレクションが遅くなることは直感的に理解できます。

コレクターの起動頻度を低くしても、ガベージ コレクターがコレクション作業をより速く完了するのに役立ちません。頻度を減らすと、ガベージ コレクターはコレクション中により多くの作業を行うことになります。 これにより、新しく割り当てられたオブジェクトの数が減り、ガベージ コレクターはコレクションをより速く完了できるようになります。

注: このアプローチでは、必要なスループットを達成するために、可能な限り小さいヒープを使用することもできます。 クラウド環境で実行する場合、ヒープメモリなどのリソースの使用を最小限に抑えることが重要です。

上の画像は、実行中のアプリケーションに関するいくつかの統計を示しています。青色のバージョンには、最適化されていないアプリの統計が表示されます。緑色のバージョンは、非生産的なメモリ割り当て 4.48 GB を削除した後の統計です。

2 つのバージョンの平均収集速度 (2.08 ミリ秒対 1.96 ミリ秒) を確認すると、どちらも約 2.0 ミリ秒です。 2 つのバージョン間の基本的な違いは、各ガベージ コレクションのスループットです。リクエストが 3.98 から 7.13 に改善されました。スループットが 79.1% 増加しました。メモリ割り当てが減少してもガベージ コレクションの時間は遅くならず、一定のままでした。パフォーマンスの向上は、各ガベージ コレクション中に他の Go ルーチンがより多くの作業を実行できるという事実から生まれます。

ガベージ コレクションのペーシング レートを調整してレイテンシ コストを延期することは、アプリケーションのパフォーマンスを向上させる方法ではありません。

コレクター遅延コスト

各ガベージ コレクションでは 2 種類の遅延が発生します。 1 つ目は、CPU 容量を盗むことです。 この CPU 容量の盗難の影響により、ガベージ コレクション プロセス中にアプリケーションがフル スピードで実行されなくなります。アプリケーション Goroutine は、ガベージ コレクターの Goroutine と P または Complete Mark Assist を共有するようになりました。

上記の画像は、アプリケーションが CPU 作業の 75% を使用していることを示しています。 これは、コレクターに専用の P1 があるためです。

上記のグラフは、アプリケーションが (通常はわずか数マイクロ秒) アプリケーションの作業に CPU 容量の半分しか使用できないことを示しています。 P3 の goroutine が Mark Assist を実行しており、ガベージ コレクターがすでに P1 を引き継いでいるためです。

2 番目のタイプの遅延は、収集中に発生する STW 遅延です。 STW 中、アプリケーション Goroutine はアプリケーションを実行しません。 アプリケーションは基本的に死んでいます。

上記のグラフは、すべての Goroutine が停止している STW レイテンシを示しています。 各ガベージコレクションは 2 回発生します。 アプリケーションが適切に動作している場合、ガベージ コレクターは、ほとんどのガベージ コレクションで合計 STW 時間を 100 マイクロ秒以下に維持できます。

これで、ガベージ コレクターのさまざまなフェーズ、メモリのサイズ設定方法、チューニングの仕組み、およびガベージ コレクターが実行中のアプリケーションに引き起こす可能性のあるさまざまな遅延について理解できました。 この知識があれば、どのようにチューニングするかという疑問にようやく答えることができます。

チューニング

ヒープ メモリへの負荷を軽減することが最適化の最善の方法です。 プレッシャーは、アプリケーションが指定された時間内にヒープ メモリを割り当てる速度として定義できます。 ヒープメモリの負荷が減少すると、ガベージコレクターの影響も減少します。 GC レイテンシを削減する方法は、アプリケーションから不要なメモリ割り当てを識別して削除することです。

次のプラクティスはガベージ コレクターに役立ちます。

  • ヒープをできるだけ小さく保ちます。
  • 最適な一貫したペーシング レート。
  • 各コレクションの目標を守りましょう。
  • 各ガベージ コレクションの STW とマーク アシストの期間を最小限に抑えます。

これらすべてにより、ガベージ コレクションによって発生する遅延が削減され、アプリケーションのパフォーマンスとスループットも向上します。 ガベージコレクションの頻度はこれとは何の関係もありません。

作業量を理解するということは、作業を完了するために適切な数の goroutine を使用するようにすることを意味します。 CPU ボトルネックと IO ボトルネックのワークロードは異なるため、異なるエンジニアリング上の決定が必要です。この記事を参照してください。 https://www.ardanlabs.com/blog/2018/12/scheduling-in-go-part3.html

データを理解するということは、解決したい問題を理解するということです。 データ セマンティック一貫性は、データの整合性を維持するための重要な部分であり、メモリをヒープ上に割り当てるかスタック上に割り当てるかを決定できます。 https://www.ardanlabs.com/blog/2017/06/design-philosophy-on-data-and-semantics.html

結論は

Go ランタイムにとって、有効なメモリ割り当て (アプリケーションに役立つもの) と無効なメモリ割り当て (アプリケーションに悪影響を与えるもの) の違いを認識することが重要です。 そうすると、ガベージ コレクターが効率的に実行されることを信頼することしかできなくなります。

ガベージコレクターを持つことは良いトレードオフです。 ガベージコレクションのコストはかかりますが、メモリ管理の負担はありません。 Go 言語は開発と運用の効率の両方を考慮しています。 ガベージコレクターはこの目標を達成する上で重要な役割を果たします。

元の住所:

https://www.ardanlabs.com/blog/2018/12/garbage-collection-in-go-part1-semantics.html

参考文献:

https://github.com/golang/go/blob/release-branch.go1.5/src/runtime/mgc.go

https://github.com/golang/proposal/blob/master/design/17505-concurrent-rescan.md

https://github.com/golang/proposal/blob/master/design/17503-eliminate-rescan.md

https://blog.golang.org/ismmkeynote

https://www.youtube.com/watch?v=aiv1JOfMjm0&t=1208s

<<:  この「水中トランスフォーマー」はNASAによって困難な水中作業のためにテストされている。

>>:  電流制限アルゴリズムを理解すれば十分です。

ブログ    

推薦する

世界各国の人工知能の配置をご存知ですか?

[[207472]]人工知能は未来をリードする戦略技術です。世界の主要先進国は人工知能の発展を国家...

対話 | QingCloud CTO: AI が到来し、基本的なクラウド サービス プロバイダーもそれに備える必要があります。

[51CTO記者の李玲玲が北京からレポート] 真夏が到来し、人工知能も北京の天気のように、より暑い...

スタンフォード大学、AIがシマウマを犬と誤認する理由を発見

この記事はAI新メディアQuantum Bit(公開アカウントID:QbitAI)より許可を得て転載...

NVIDIA、医療用 AI コンピューティング プラットフォームを発表

NVIDIA は最近、AI 駆動型イメージング、ゲノミクス、スマート センサーの開発と展開のための...

...

ChatGPT エッセイの書き方の説明

1. 質問の仕方を理解するChatGPT と対話するときに使用されるプロンプトの主な 4 つのタイプ...

プログラミング能力はGPT-4を超え、アルパカコード版「スーパーカップ」が登場、ザッカーバーグ氏も自らLlama3をネタバレ

アルパカファミリーの「最強のオープンソースコードモデル」が「スーパーカップ」を発売しました——今朝、...

人工知能と機械学習が進化する10の方法

[[411678]]人工知能は現在、多くの CEO にとって最重要課題となっています。この話題は目新...

AI技術は製薬業界でますます重要な役割を果たしている

製薬会社における人工知能 (AI) の活用は、より優れた診断の提供、より高品質の医薬品の開発、患者に...

...

...

ジェフ・ディーン: 「スパースモデル設計ガイド」を作成しましたので、ぜひご覧ください。

スパースモデルは、ディープラーニングの分野でますます重要な役割を果たしています。特定のトークンまたは...

...

より賢いAIの代償:Google、音声アシスタントがユーザーの会話の録音を漏洩したことを認める

AI時代の生活にプライバシーは存在しません。スマート音声アシスタントはプライバシー漏洩の次の大きな災...

機械学習におけるデータ駆動型アルゴリズムの応用

機械学習の概念分析機械学習の概念は、アルゴリズムとニューラル ネットワーク モデルを使用して学習し、...