TL;DR
Go 1.26 より Goのガベージコレクタに新しい "Green Tea" モードが導入されました。
そちらについて、公式の記事を読んだためまとめます。
事実の整理
Garbage Collection (GC) とは
GCとは、プログラムが使用しなくなったメモリ領域を自動的に解放する仕組みです。
近年流行っているRustはGCを利用せず、所有権システムを利用してメモリ管理を行います。詳細は下記のRust公式ドキュメントを参照してください。
Goのメモリの構成
Goのメモリは主に以下の2つに分かれますが、スタック領域についてはコンパイル時にライフタイムが決定されるため、GCの対象外となりやすいです。
- ヒープ領域 (The heap): 動的に割り当てられるメモリ領域。GCの対象となる。
- スタック領域 (Stack allocation): ローカル変数に格納されている非ポインター型の変数などを格納するメモリ領域。GCの対象外になることが多い。
GoのGCの基本的な仕組み
GCのサイクル
GCは次の3つのサイクルで動作します。
- Marking phase: 使用中のオブジェクトを特定し、追跡するタイミング。
- Sweeping phase: 使用されていないオブジェクトを解放するタイミング。
- Off phase: GCが動作していないタイミング。
GCのトリガー
GCはGCを稼働することによるCPU負荷と、メモリ使用量のバランスを取るために、次の式のヒープ領域を超えたタイミングでトリガーされます。
target_heap = heap_live + (heap_live + gc_root ) * GOGC / 100
heap_live: 前回のGCサイクルで使用中と判断されたメモリ
gc_root: GCが追跡する必要のあるオブジェクトのメモリ量
GOGC: 環境変数で設定可能なパラメータ (デフォルトは100: https://pkg.go.dev/runtime )
GOMEMLIMIT という環境変数を利用する場合、上記のタイミングよりも早くGCがトリガーされる可能性があります。詳細は下記のドキュメントなどを参照してください。
並行処理
GoのGCは、アプリケーションの実行と並行して動作するため、アプリケーションを停止させずにメモリの解放を行います。
アプリケーションを停止させることを Stop the world (STW) と呼びます。
広く使われるのはGCの文脈で多いですが、Kafkaなどの分散システムにおいても、Consumer Groupの再バランスなどでSTWが発生することがあります。
従来のGoのGCの課題
GoのGCは、アプリケーション全体の20%程度のCPU程度を消費することがあり、
特にマーキングの際にCPUがヒープメモリのアクセスを待って停止してしまう時間が多くを占めていました。
また、従来のGoのGCではオブジェクトからオブジェクトへの探索を繰り返すため、探索の度にメモリ領域の全体を飛び回る必要があり、ページを跨いで小さな作業を頻繁に行うことになってしまい、現代のCPUのアーキテクチャにおいては非効率な動作となっていました。
Green Tea GCの特徴
新しいGCでは、マーキングの際にオブジェクトでグラフ探索をするのではなく、メモリのページング単位で探索を行うような仕組みにしました。
Green Tea GCの効果
ベンチマークの効果として従来に比べて、10%~40%程度のCPU使用率の削減が見られたとのことです。
個人的にあまり理解できなかったところ
- vector hardwareという概念が出てきましたが私はあまり馴染みがなく理解できませんでした。特に近年はほとんどのソフトウェアは仮想マシン上で動作しているため、もしこちらを利用する場合は利用する仮想マシン(例えば、k8s上のコンテナランタイムやクラウドプロバイダのVMイメージ)が対応している必要があると感じたため、利用をしたいモチベーションが出た際に改めて調査したいと感じました。
- GCのドキュメントを読むとスタック領域についてもGCの管理になる可能性は低いとしか書いておらず、断言は避けているように感じました。この点についても、自分の理解とは乖離しておりどういったパターンでGCの管理になるかイメージがわきませんでした。