[インデックス 17627] ファイルの概要
このコミットは、Go 1.2 リリースにおいて、スタックフレームの正確なポインタ収集(precise collection of stack frames)を一時的に無効化する変更です。これは、コールサイト固有のポインタビットマップのコードが間に合わず、その代替として必要となるゼロ初期化のコストが高すぎたためです。この機能はGo 1.3で導入される予定でした。
コミット
commit 30ecb4cd05ac41805593c95bd7967b808e5f4ca5
Author: Russ Cox <rsc@golang.org>
Date: Mon Sep 16 20:26:10 2013 -0400
build: disable precise collection of stack frames
The code for call site-specific pointer bitmaps was not ready in time,
but the zeroing required without it is too expensive to use by default.
We will have to wait for precise collection of stack frames until Go 1.3.
The precise collection can be re-enabled by
GOEXPERIMENT=precisestack ./all.bash
but that will not be the default for a Go 1.2 build.
Fixes #6087.
R=golang-dev, jeremyjackins, dan.kortschak, r
CC=golang-dev
https://golang.org/cl/13677045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/30ecb4cd05ac41805593c95bd7967b808e5f4ca5
元コミット内容
build: disable precise collection of stack frames
このコミットは、Go 1.2のビルドにおいて、スタックフレームの正確なポインタ収集機能を無効化することを目的としています。その理由は、コールサイト固有のポインタビットマップを生成するコードが開発期限に間に合わなかったためです。この機能がない場合、デフォルトで必要となるメモリのゼロ初期化のコストが非常に高く、パフォーマンスに悪影響を与えるため、デフォルトでは無効化されました。正確なスタックフレーム収集はGo 1.3まで延期されることになりました。
ただし、GOEXPERIMENT=precisestack ./all.bash
コマンドを使用することで、この機能を明示的に有効にすることは可能です。しかし、これはGo 1.2ビルドのデフォルト設定ではありません。
この変更は、Issue #6087 を修正するものです。
変更の背景
Goのガベージコレクション(GC)は、メモリ管理において重要な役割を果たします。特に、スタック上のポインタを正確に識別することは、GCが正しく動作し、不要なメモリを解放するために不可欠です。
このコミットが行われた2013年当時、GoのGCは進化の途中にありました。特に、スタックフレーム内のポインタを「正確に」識別する機能(precise collection of stack frames)は、GCの効率と精度を向上させるための重要な目標でした。正確なGCとは、メモリ内のどの値がポインタであり、どれがそうでないかを正確に識別できるGCを指します。これにより、GCは到達可能なオブジェクトを正確に追跡し、誤って非ポインタデータをポインタと解釈してオブジェクトが解放されない「誤った保持(false retention)」を防ぐことができます。
しかし、このコミットの時点では、コールサイト(関数呼び出しが行われる場所)ごとにスタックフレーム内のポインタの位置を示すビットマップを生成するコードが、Go 1.2のリリーススケジュールに間に合いませんでした。このビットマップがない場合、GCはスタック上のすべてのメモリ領域をポインタとして扱う必要があり、これは「保守的なGC(conservative GC)」と呼ばれるアプローチに近くなります。保守的なGCでは、ポインタではないデータが偶然ポインタのように見える場合でも、それが指す先のオブジェクトを到達可能と判断してしまう可能性があります。これを避けるためには、スタック上のメモリを広範囲にゼロ初期化する必要があり、これが非常に高コストな操作となります。
このパフォーマンス上の懸念から、Go開発チームは、正確なスタックフレーム収集機能をGo 1.2のデフォルトから外し、Go 1.3での導入を目指すことを決定しました。このコミットは、その決定を反映し、デフォルトでこの機能を無効化するためのビルドシステムへの変更を加えています。
前提知識の解説
Goのガベージコレクション (GC)
Goのガベージコレクションは、プログラムが動的に割り当てたメモリのうち、もはや到達不可能になった(どの変数からも参照されなくなった)メモリ領域を自動的に解放する仕組みです。GoのGCは、並行(concurrent)かつ低遅延(low-latency)であることを目指して設計されており、プログラムの実行と並行して動作することで、アプリケーションの一時停止時間を最小限に抑えます。
正確なGC (Precise GC) と保守的なGC (Conservative GC)
- 正確なGC (Precise GC): GCがメモリ内のどの値がポインタであり、どれがそうでないかを正確に識別できる方式です。これにより、GCは到達可能なオブジェクトを正確に追跡し、誤って非ポインタデータをポインタと解釈してオブジェクトが解放されない「誤った保持」を防ぎます。GoのGCは、基本的に正確なGCを目指しています。
- 保守的なGC (Conservative GC): GCがメモリ内の値がポインタであるかどうかを確実に判断できない場合、その値をポインタであると仮定して処理する方式です。これは実装が容易ですが、実際にはポインタではない値がポインタのように見えることで、不要なオブジェクトが解放されずにメモリリークを引き起こす可能性があります。
スタックフレーム (Stack Frame)
関数が呼び出されるたびに、その関数のローカル変数、引数、戻りアドレスなどを格納するために、ゴルーチン(goroutine)のスタック上に割り当てられるメモリ領域です。Goのランタイムは、これらのスタックのサイズを動的に管理し、必要に応じて拡大・縮小させます。
スタックルート (Stack Roots)
ガベージコレクションのサイクル中、GCはまず「ルート(roots)」と呼ばれる既知の到達可能な値から追跡を開始します。これらのルートには、グローバル変数や、アクティブなゴルーチンのスタックフレーム上のローカル変数が含まれます。GCはこれらのルートから到達可能なすべてのオブジェクトを追跡し、到達不可能なオブジェクトを解放します。スタックフレーム内のポインタを正確に識別することは、これらのスタックルートを正確に特定するために重要です。
GOEXPERIMENT
環境変数
GOEXPERIMENT
は、Goのビルドシステムで使用される環境変数で、開発中の実験的な機能を有効または無効にするために使用されます。この変数を設定することで、特定の機能がデフォルトで有効になっていない場合でも、開発者やテスターがその機能を試すことができます。このコミットの時点では、precisestack
という実験的なフラグが導入され、スタックフレームの正確な収集を制御するために使用されました。
技術的詳細
このコミットの技術的詳細は、Goのビルドプロセスとランタイムにおけるメモリ管理の複雑さを示しています。
-
コールサイト固有のポインタビットマップの欠如: GoのGCがスタックフレーム内のポインタを正確に識別するためには、各関数呼び出し(コールサイト)において、そのスタックフレーム内のどのオフセットにポインタが存在するかを示すメタデータ(ポインタビットマップ)が必要です。このビットマップは、コンパイラによって生成され、ランタイムがGC時に参照します。このコミットの時点では、このビットマップを効率的かつ正確に生成するコードがGo 1.2のリリースまでに完成していませんでした。
-
ゼロ初期化のコスト: ポインタビットマップがない場合、GCはスタック上のメモリ領域がポインタを含むかどうかを確実に判断できません。この状況で「誤った保持」を防ぐためには、スタック上のすべてのローカル変数をゼロ初期化する必要があります。これにより、古いポインタ値が残存してGCが誤ったオブジェクトを保持することを防ぎます。しかし、このゼロ初期化は、特に大きなスタックフレームや多数の関数呼び出しがある場合に、顕著なパフォーマンスオーバーヘッドを引き起こします。コミットメッセージにある「the zeroing required without it is too expensive to use by default」という記述は、このコストがデフォルトで有効にするには高すぎたことを示しています。
-
GOEXPERIMENT
の導入と利用: この問題を解決するため、開発チームは「正確なスタックフレーム収集」機能をデフォルトで無効化しつつも、開発者がテストできるようにGOEXPERIMENT
環境変数を通じて有効化できるメカニズムを導入しました。src/cmd/dist/a.h
,src/cmd/dist/build.c
,src/cmd/dist/buildruntime.c
の変更は、ビルドシステムにGOEXPERIMENT
環境変数の値をzaexperiment.h
というヘッダーファイルに書き込む機能を追加しています。このヘッダーファイルは、ランタイムコードにGOEXPERIMENT
の値を提供します。src/cmd/gc/go.h
とsrc/cmd/gc/lex.c
の変更は、Goコンパイラ(gc
)にprecisestack_enabled
というフラグを導入し、GOEXPERIMENT=precisestack
が設定されている場合にこのフラグが有効になるようにしています。src/cmd/gc/pgen.c
とsrc/cmd/gc/walk.c
の変更は、このprecisestack_enabled
フラグに基づいて、スタック変数のゼロ初期化ロジックや戻り値のゼロ初期化ロジックを条件付きで適用するように修正しています。precisestack_enabled
が有効な場合は、より厳密なゼロ初期化が行われる可能性があります。src/pkg/runtime/mgc0.c
とsrc/pkg/runtime/proc.c
,src/pkg/runtime/runtime.h
の変更は、GoランタイムがGOEXPERIMENT
の値を取得し、runtime·precisestack
というグローバル変数にその状態を反映させるようにしています。GCのロジック(scaninterfacedata
など)は、このruntime·precisestack
の値に基づいて動作を調整します。
このアプローチにより、Go 1.2では安定性とパフォーマンスを優先し、高コストなゼロ初期化を避けるために正確なスタックフレーム収集を無効化しつつ、Go 1.3での完全な実装に向けた準備を進めることができました。
コアとなるコードの変更箇所
このコミットでは、主にGoのビルドシステム (src/cmd/dist
)、Goコンパイラ (src/cmd/gc
)、およびGoランタイム (src/pkg/runtime
) に変更が加えられています。
src/cmd/dist/a.h
: ビルドシステムがzaexperiment.h
を生成するための関数mkzexperiment
のプロトタイプ宣言を追加。src/cmd/dist/build.c
: ビルドシステムがzaexperiment.h
をビルドターゲットとして認識し、mkzexperiment
関数を呼び出すように設定。src/cmd/dist/buildruntime.c
:GOEXPERIMENT
環境変数の値を読み取り、zaexperiment.h
ファイルに#define GOEXPERIMENT "experiment string"
の形式で書き込むmkzexperiment
関数を実装。また、mkzasm
関数内でもGOEXPERIMENT
の値をzasm_GOOS_GOARCH.h
に書き込むように変更。src/cmd/gc/go.h
: コンパイラ内部で使用されるグローバル変数precisestack_enabled
の宣言を追加。src/cmd/gc/lex.c
:GOEXPERIMENT
環境変数でprecisestack
が指定された場合にprecisestack_enabled
フラグを有効にするためのエントリをexper
配列に追加。src/cmd/gc/pgen.c
:allocauto
関数内で、precisestack_enabled
が有効な場合にのみ、ローカル変数のゼロ初期化に関するループを条件付きで実行するように変更。src/cmd/gc/walk.c
:paramstoheap
関数内で、戻り値のゼロ初期化の条件にprecisestack_enabled
を追加。precisestack
モードでは、GCが戻り値を常にライブであると仮定するため、常にゼロ初期化を行うように変更。src/pkg/runtime/mgc0.c
:scaninterfacedata
関数内で、runtime·precisestack
が有効な場合にのみ特定のGCロジックを実行するように変更。src/pkg/runtime/proc.c
:runtime·schedinit
関数内で、haveexperiment("precisestack")
の結果をruntime·precisestack
グローバル変数に設定。また、zaexperiment.h
をインクルードし、GOEXPERIMENT
の値に基づいて特定の実験が有効かどうかをチェックするhaveexperiment
関数を実装。src/pkg/runtime/runtime.h
:runtime·precisestack
グローバル変数の外部宣言を追加。
コアとなるコードの解説
このコミットの核心は、Goのビルドプロセス、コンパイラ、およびランタイムが連携して、GOEXPERIMENT
環境変数を通じて「正確なスタックフレーム収集」機能を制御するメカニズムを導入した点にあります。
-
GOEXPERIMENT
のビルドシステムへの統合 (src/cmd/dist/buildruntime.c
):mkzexperiment
関数は、GOEXPERIMENT
環境変数の値を読み取り、zaexperiment.h
というCヘッダーファイルを生成します。このヘッダーファイルには、#define GOEXPERIMENT "experiment string"
の形式で、GOEXPERIMENT
の値が文字列リテラルとして埋め込まれます。これにより、GoランタイムのCコードからGOEXPERIMENT
の値にアクセスできるようになります。 また、mkzasm
関数も同様にGOEXPERIMENT
の値をzasm_GOOS_GOARCH.h
に書き込むように変更されており、アセンブリコードからもこの情報が利用可能になります。 -
コンパイラでのフラグ制御 (
src/cmd/gc/lex.c
,src/cmd/gc/go.h
):src/cmd/gc/lex.c
のexper
配列に{"precisestack", &precisestack_enabled}
が追加されたことで、GoコンパイラはGOEXPERIMENT=precisestack
が設定されている場合に、内部フラグprecisestack_enabled
をtrue
に設定します。このフラグはsrc/cmd/gc/go.h
で宣言されています。 -
コンパイラでのコード生成の調整 (
src/cmd/gc/pgen.c
,src/cmd/gc/walk.c
):src/cmd/gc/pgen.c
のallocauto
関数は、ローカル変数のメモリ割り当てと初期化を担当します。このコミットでは、precisestack_enabled
がtrue
の場合にのみ、ローカル変数をゼロ初期化するループが実行されるように変更されました。これは、正確なスタック収集が有効な場合、GCがポインタを正確に識別できるため、すべてのローカル変数をゼロ初期化する必要がない、あるいは異なるゼロ初期化戦略が適用されることを示唆しています。src/cmd/gc/walk.c
のparamstoheap
関数は、関数の戻り値の処理に関連します。この変更では、precisestack_enabled
がtrue
の場合、戻り値が常にライブであるとGCが仮定するため、常にゼロ初期化を行うように条件が追加されました。これは、GCが戻り値を正確に追跡するために必要な措置です。
-
ランタイムでの実験フラグの利用 (
src/pkg/runtime/proc.c
,src/pkg/runtime/mgc0.c
):src/pkg/runtime/proc.c
では、zaexperiment.h
をインクルードすることで、ビルド時に埋め込まれたGOEXPERIMENT
の値にアクセスします。haveexperiment
関数は、この埋め込まれた文字列を解析し、特定の実験(例:precisestack
)が有効かどうかをチェックします。runtime·schedinit
関数内で、haveexperiment("precisestack")
の結果がruntime·precisestack
というグローバルなbool
型変数に格納されます。src/pkg/runtime/mgc0.c
のscaninterfacedata
のようなGC関連の関数は、このruntime·precisestack
の値に基づいて、GCの動作を調整します。例えば、runtime·precisestack
がtrue
の場合にのみ、特定のポインタスキャンロジックが実行されるようになります。
これらの変更により、Go 1.2ではデフォルトで高コストなゼロ初期化を回避しつつ、開発者が実験的に正確なスタックフレーム収集を試せる柔軟なメカニズムが提供されました。
関連リンク
- GitHubコミット: https://github.com/golang/go/commit/30ecb4cd05ac41805593c95bd7967b808e5f4ca5
- Go Issue #6087: https://golang.org/issue/6087
- Go CL 13677045: https://golang.org/cl/13677045
参考にした情報源リンク
- Go's garbage collection (GC) is designed to be precise: https://stackoverflow.com/questions/tagged/go-garbage-collection
- Stack Frame Role: https://medium.com/@ashish.goyal/go-memory-management-and-garbage-collection-a-deep-dive-2c2d2d2d2d2d
- How Precision is Achieved: https://dev.to/jason_c_li/go-s-garbage-collector-a-deep-dive-into-its-internals-and-optimizations-312
- Escape Analysis: https://go.dev/blog/go1.3gc
GOEXPERIMENT
and precise stack frame collection: https://go.dev/doc/go1.3