[インデックス 18577] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)とランタイム(runtime
)において、precisestack
機能をデフォルトで有効にする変更を導入します。precisestack
は、ガベージコレクタがスタックをスキャンする際の精度を向上させ、未初期化のスタック領域をポインタとして誤認識する問題を解消することを目的としています。
コミット
commit 53061193f1b35aa6eda405909db41900fdc2c5de
Author: Russ Cox <rsc@golang.org>
Date: Wed Feb 19 17:09:08 2014 -0500
cmd/gc, runtime: enable precisestack by default
[Repeat of CL 64100044, after 32-bit fix in CL 66170043.]
Precisestack makes stack collection completely precise,
in the sense that there are no "used and not set" errors
in the collection of stack frames, no times where the collector
reads a pointer from a stack word that has not actually been
initialized with a pointer (possibly a nil pointer) in that function.
The most important part is interfaces: precisestack means
that if reading an interface value, the interface value is guaranteed
to be initialized, meaning that the type word can be relied
upon to be either nil or a valid interface type word describing
the data word.
This requires additional zeroing of certain values on the stack
on entry, which right now costs about 5% overall execution
time in all.bash. That cost will come down before Go 1.3
(issue 7345).
There are at least two known garbage collector bugs right now,
issues 7343 and 7344. The first happens even without precisestack.
The second I have only seen with precisestack, but that does not
mean that precisestack is what causes it. In fact it is very difficult
to explain by what precisestack does directly. Precisestack may
be exacerbating an existing problem. Both of those issues are
marked for Go 1.3 as well.
The reasons for enabling precisestack now are to give it more
time to soak and because the copying stack work depends on it.
LGTM=r
R=r
CC=golang-codereviews
https://golang.org/cl/65820044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/53061193f1b35aa6eda405909db41900fdc2c5de
元コミット内容
このコミットは、以前の変更セット(CL 64100044)の繰り返しであり、32ビット環境での修正(CL 66170043)が適用された後のものです。これは、precisestack
機能の導入が以前にも試みられたが、何らかの問題(特に32ビット環境での問題)により一度ロールバックされたか、あるいはデフォルト有効化が見送られていたことを示唆しています。今回のコミットで、その問題が解決されたため、改めてデフォルト有効化が試みられています。
変更の背景
Goのガベージコレクタ(GC)は、プログラムが使用しているメモリを正確に識別し、不要になったメモリを解放する役割を担っています。このプロセスにおいて、スタック上に存在するポインタを正確に識別することは非常に重要です。しかし、従来のGCでは、スタック上のメモリがまだ初期化されていないにもかかわらず、そこにたまたまポインタのように見える値が存在した場合、GCがそれを有効なポインタと誤認識してしまう可能性がありました。これを「used and not set」エラーと呼びます。
特に、インターフェース値の扱いにおいてこの問題は顕著でした。Goのインターフェースは、内部的に型情報とデータポインタのペアとして表現されます。もしインターフェース変数が初期化される前にGCがスタックをスキャンした場合、型情報が未初期化であるために、GCが不正なメモリを指していると判断したり、クラッシュを引き起こしたりする可能性がありました。
precisestack
は、この問題を根本的に解決するために導入されました。スタック上のポインタを含む可能性のあるすべての場所が、関数エントリ時に確実に初期化(ゼロクリアまたは有効な値で設定)されるようにすることで、GCが常に正確な情報を得られるようにします。
この変更がGo 1.3のリリース前にデフォルトで有効化される背景には、以下の理由があります。
- 十分なテスト期間の確保(Soak Time): 新しいGCの挙動は、様々な実際のアプリケーションでテストされることで、潜在的なバグが発見されやすくなります。Go 1.3のリリース前にデフォルトで有効にすることで、より多くのユーザーがこの機能を使い、フィードバックやバグ報告を促すことができます。
- 将来のGC機能への依存: コミットメッセージには「copying stack work depends on it」と明記されています。これは、GoのGCが将来的にスタックをコピーする方式(おそらく、より低レイテンシなGCを実現するため)に移行する計画があり、その実現には
precisestack
によるスタックの正確なスキャンが不可欠であることを示しています。
前提知識の解説
ガベージコレクション (GC) とスタックスキャン
Goのガベージコレクタは、マーク&スイープ方式をベースとしています。GCは、プログラムが現在使用しているオブジェクト(到達可能なオブジェクト)を特定するために、ルートセット(グローバル変数、レジスタ、そしてスタック上のポインタ)からメモリグラフを辿ります。
スタックは、関数呼び出しの際にローカル変数や引数、戻りアドレスなどが一時的に格納されるメモリ領域です。GoのGCがスタックをスキャンする際、スタック上のどのワードがポインタであり、どのワードが単なる整数やその他の非ポインタデータであるかを正確に識別する必要があります。
ポインタの精度 (Precise vs. Conservative)
- Precise GC (正確なGC): メモリ上のどの場所がポインタであるかを正確に識別できるGCです。これにより、GCは到達可能なオブジェクトのみをマークし、不要なメモリを正確に解放できます。GoのGCは、ヒープ上のオブジェクトに対しては基本的に正確なGCを行います。
- Conservative GC (保守的なGC): メモリ上の特定のワードがポインタであるかどうかを確実に判断できない場合、それをポインタであると仮定するGCです。これは、誤ってポインタでないものをポインタとみなしてしまうリスクがありますが、その代わりにGCの実装が簡素化されます。スタックスキャンにおいては、一部のGCが保守的なアプローチを取ることがあります。GoのGCも、
precisestack
導入以前はスタックの一部で保守的なスキャンを行っていた可能性があります。
「Used and not set」エラー
これは、プログラムがまだ値を書き込んでいないメモリ領域(特にスタック上のローカル変数など)を、GCがポインタとして読み取ってしまう問題です。未初期化のメモリには、以前のプログラムの実行で残されたゴミデータが含まれている可能性があり、それがたまたま有効なメモリアドレスのように見えてしまうことがあります。GCがこれをポインタと誤認識すると、存在しないオブジェクトをマークしようとしたり、不正なメモリにアクセスしてクラッシュしたり、あるいは本来解放されるべきメモリを誤って保持し続けてメモリリークを引き起こしたりする可能性があります。
インターフェース値の内部表現
Goのインターフェース値は、内部的に2つのワードで構成されます。
- 型ワード (Type Word): インターフェースが保持している具体的な値の型情報(
_type
構造体へのポインタ)。 - データワード (Data Word): インターフェースが保持している具体的な値へのポインタ。
インターフェース変数が宣言された直後でまだ値が代入されていない場合、これら2つのワードは未初期化の状態です。precisestack
がなければ、GCがこの未初期化のインターフェース変数をスキャンした際に、型ワードやデータワードにゴミデータが残っていると、それを有効な型情報やデータポインタと誤認識するリスクがありました。precisestack
は、インターフェース変数が初期化されることを保証することで、この問題を解決します。
Goの実験的機能 (GOEXPERIMENT)
Goコンパイラやランタイムには、開発中の機能や実験的な機能を有効にするためのGOEXPERIMENT
環境変数があります。この変数を設定することで、特定の機能を有効/無効にできます。このコミット以前は、precisestack
もこのGOEXPERIMENT
を通じて制御されていたと考えられます。
技術的詳細
precisestack
の導入は、Goのガベージコレクタのスタックスキャン戦略における重要な改善です。
-
スタックのゼロ初期化:
precisestack
を有効にすると、関数が呼び出された際に、その関数のスタックフレーム内でポインタを格納する可能性のあるすべての場所が、エントリ時にゼロ(nil
ポインタ)で初期化されるようになります。これにより、GCがスタックをスキャンする際に、未初期化のメモリをポインタとして誤認識するリスクがなくなります。GCは、ゼロ値の場所はポインタではないか、あるいはnil
ポインタであると安全に判断できます。 -
インターフェースの安全性向上: インターフェース値は、その性質上、型情報とデータポインタの両方を持つため、GCの正確なスキャンが特に重要です。
precisestack
により、インターフェース変数が使用される前にその内部表現が確実に初期化されるため、GCがインターフェースをスキャンする際の信頼性が大幅に向上します。これにより、インターフェース関連のGCバグやクラッシュのリスクが低減されます。 -
性能コストと最適化: スタックのゼロ初期化は、追加のCPUサイクルを必要とするため、一時的に性能オーバーヘッドが発生します。コミットメッセージによると、この時点では
all.bash
(Goのテストスイート全体を実行するスクリプト)で約5%の実行時間増加が見られました。しかし、このコストはGo 1.3のリリースまでに最適化される予定であり、issue 7345で追跡されていました。これは、Go開発チームが正確性向上のために一時的な性能低下を許容しつつ、最終的な性能目標を達成する意図があったことを示しています。 -
既知のGCバグとの関連: コミットメッセージでは、issue 7343と7344という2つの既知のGCバグに言及しています。
- issue 7343は
precisestack
がなくても発生するバグであり、precisestack
とは独立した問題です。 - issue 7344は
precisestack
を有効にした場合にのみ観測されたバグですが、コミットメッセージはprecisestack
が直接の原因ではない可能性を示唆しています。むしろ、precisestack
が既存の潜在的な問題を顕在化させている、あるいは悪化させている可能性が指摘されています。これは、GCの正確性が向上したことで、これまで隠れていたメモリ管理の不整合が露呈したことを意味するかもしれません。これらのバグもGo 1.3で修正が予定されていました。
- issue 7343は
-
将来のGC戦略への布石: 「copying stack work depends on it」という記述は、GoのGCが将来的にスタックをコピーする方式(例えば、スタックをヒープに移動させたり、スタックのサイズ変更を効率化したりする目的)を採用する可能性を示唆しています。スタックをコピーするGCでは、スタック上のポインタを完全に正確に識別できることが絶対条件となります。
precisestack
は、この将来のGCアーキテクチャの基盤を築くものです。
コアとなるコードの変更箇所
このコミットでは、主に2つのファイルが変更されています。
-
src/cmd/gc/lex.c
: Goコンパイラのフロントエンドの一部で、実験的機能の有効/無効を制御する部分です。--- a/src/cmd/gc/lex.c +++ b/src/cmd/gc/lex.c @@ -60,7 +60,7 @@ static void addexp(char *s) { int i; - + for(i=0; exper[i].name != nil; i++) { if(strcmp(exper[i].name, s) == 0) { *exper[i].val = 1; @@ -77,7 +77,9 @@ setexp(void) { char *f[20]; int i, nf; - + + precisestack_enabled = 1; // on by default + // The makefile #defines GOEXPERIMENT for us. nf = getfields(GOEXPERIMENT, f, nelem(f), 1, ","); for(i=0; i<nf; i++)
-
src/pkg/runtime/proc.c
: Goランタイムの初期化処理の一部で、precisestack
機能の有効/無効をランタイムレベルで制御する部分です。--- a/src/pkg/runtime/proc.c +++ b/src/pkg/runtime/proc.c @@ -144,7 +144,7 @@ runtime·schedinit(void) Eface i; runtime·sched.maxmcount = 10000; - runtime·precisestack = haveexperiment("precisestack"); + runtime·precisestack = true; // haveexperiment("precisestack"); runtime·mallocinit(); mcommoninit(m);
コアとなるコードの解説
src/cmd/gc/lex.c
の変更
setexp
関数は、Goコンパイラが起動する際に、GOEXPERIMENT
環境変数で指定された実験的機能を有効にするための処理を行います。
変更前は、precisestack
機能はGOEXPERIMENT
環境変数を通じてのみ有効にすることができました。
変更後、precisestack_enabled = 1; // on by default
という行が追加されました。これは、GOEXPERIMENT
の設定に関わらず、コンパイラレベルでprecisestack
機能をデフォルトで有効にするという明確な指示です。これにより、コンパイルされたGoプログラムは、precisestack
の恩恵を受けるようになります。
src/pkg/runtime/proc.c
の変更
runtime·schedinit
関数は、Goランタイムの初期化処理を行う重要な関数です。
変更前は、runtime·precisestack = haveexperiment("precisestack");
という行があり、ランタイムがprecisestack
機能を有効にするかどうかをGOEXPERIMENT
環境変数の設定に依存していました。haveexperiment
関数は、指定された実験的機能が有効になっているかをチェックします。
変更後、この行はruntime·precisestack = true; // haveexperiment("precisestack");
に変更されました。これは、ランタイムレベルでもprecisestack
機能を無条件にtrue
(有効)に設定することを意味します。コメントアウトされたhaveexperiment("precisestack")
は、以前の挙動を示唆しつつ、もはやそのチェックが不要になったことを示しています。
これらの変更は、コンパイラとランタイムの両方でprecisestack
機能をデフォルトで有効にすることで、Goプログラム全体でスタックの正確なスキャンが保証されるように連携しています。
関連リンク
- Go Issue 7345: cmd/gc: reduce precisestack cost
- Go Issue 7343: runtime: GC bug with uninitialized stack slots
- Go Issue 7344: runtime: GC bug with precisestack and interface values
- Go Change List 64100044 (Previous attempt to enable precisestack)
- https://go-review.googlesource.com/c/go/+/64100044 (これはコミットメッセージからの推測であり、直接アクセスできるURLではない可能性があります。GoのCLは通常、
go-review.googlesource.com/c/go/+/CL_NUMBER
の形式です。)
- https://go-review.googlesource.com/c/go/+/64100044 (これはコミットメッセージからの推測であり、直接アクセスできるURLではない可能性があります。GoのCLは通常、
- Go Change List 66170043 (32-bit fix)
参考にした情報源リンク
- Goのガベージコレクションに関する公式ドキュメントやブログ記事 (当時のGo 1.3リリースノートやGCに関する設計ドキュメントなど)
- Goのソースコード内のコメントや関連するコミット履歴
- GoのIssueトラッカー (上記で参照したIssue 7343, 7344, 7345など)
- Goのインターフェースの内部表現に関する技術記事
- ガベージコレクションの一般的な概念に関するコンピュータサイエンスの文献
- https://go.dev/doc/go1.3 (Go 1.3 Release Notes -
precisestack
に関する言及がある可能性) - https://go.dev/blog/go1.3gc (Go 1.3のGCに関するブログ記事があれば、詳細な情報源となる)
- https://go.dev/src/runtime/ (Goランタイムのソースコード)
- https://go.dev/src/cmd/gc/ (Goコンパイラのソースコード)
- https://go.dev/issue/7345
- https://go.dev/issue/7343
- https://go.dev/issue/7344