🍵

Go 1.26で導入されたGreen Tea GC:なぜGCは進化したのか

Go プログラミング パフォーマンス

はじめに

Go 1.26でGreen Tea GCが導入され、GCのパフォーマンスが大幅に向上しました。

本記事では、なぜ改善が必要だったのか何が変わったのかを、前提知識から解説します。

前提知識:スタックとヒープ

Goのメモリはスタックヒープの2つの領域で管理されています。

特性スタックヒープ
メモリアクセス速度超高速遅い
ライフサイクル関数終了時に自動解放GCが回収するまで残る
用途ローカル変数関数をまたぐデータ

エスケープ解析

Goのコンパイラは「スタックとヒープのどちらに置くか」を自動判断します(エスケープ解析)。

ヒープにエスケープする条件

1. ポインタを返す

func newUser() *User {
    u := &User{Name: "Alice"}
    return u  // ヒープにエスケープ
}

2. インターフェースに格納

var i interface{} = 42  // 42はヒープに配置

3. サイズが大きすぎる、または実行時に決まる

// 実行時にサイズが決まる
s := make([]int, n)  // ヒープにエスケープ
# エスケープ解析の確認方法
go build -gcflags="-m" main.go

GCの仕組み:マーク&スイープ

Goは並行マーク&スイープ方式のGCを採用。

フェーズ1: マーク

まだ使われているデータを特定するフェーズです。

📌 用語の整理

  • オブジェクト: ヒープ上のデータ(構造体、スライス、マップなど)
  • ルート: マーキングの起点(グローバル変数、スタック、レジスタ)

🔍 マーキングの流れ

💡 Go公式blog中のスライドが超わかりやすいので見てください。

フェーズ2: スイープ

マークされなかったオブジェクトのメモリを回収し、再利用可能にします。

従来のGCの問題点

GCコストの約90%がマーキングに費やされ、そのうち35%がメモリアクセスの停滞でした。

🐌 グラフフラッド方式の課題

従来の方式では、オブジェクト間の参照を追跡する際、メモリ上で離れた場所を行ったり来たりします。

問題点

  • メモリが分散 → CPUキャッシュが効かない
  • 毎回メインメモリにアクセス
  • メインメモリはキャッシュより最大100倍遅い

結果、CPUはメモリフェッチを個別に待つ必要があり、マーキングのボトルネックに。

Green Tea GCの改善点

💡 GCの仕組みと改善点はGo公式blog中のスライドが超わかりやすいので見てほしいです。

発見したオブジェクトをすぐに処理するLIFO方式→FIFO(First In First Out)方式に変更

効果

  • 最初に入れたオブジェクトを後で処理
  • 待機中に同じメモリページの他のオブジェクトがCPUキャッシュに蓄積
  • ページ間を飛び回らず、1ページ内で効率的にマーキング

CPUキャッシュヒット率が向上し、メモリアクセス待ちが削減

結果

Green Tea GCの導入により、メモリアクセスの停滞(従来35%)が大幅に削減され、マーキングフェーズのパフォーマンスが向上しました。

まとめ

  • スタックとヒープ: スタックはメモリアクセスが高速でGC不要、ヒープは柔軟だがGCが必要
  • マーク&スイープ: ルートから到達可能なオブジェクトをマーク→不要なものを回収
  • 従来の問題: グラフフラッド方式ではCPUキャッシュが効かず、メモリアクセスがボトルネック
  • Green Tea GCの改善: FIFO方式でCPUキャッシュの効率を向上

参考リソース