Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18550] ファイルの概要

このコミットは、Go言語のランタイムとコンパイラ(cmd/gc)において、precisestack機能をデフォルトで有効にする変更を導入しています。precisestackは、ガベージコレクタがスタックをスキャンする際の精度を向上させるための重要な機能です。

コミット

commit ecf700b5ee878519344fc521cb0d02837a943c0d
Author: Russ Cox <rsc@golang.org>
Date:   Mon Feb 17 20:12:40 2014 -0500

    cmd/gc, runtime: enable precisestack by default
    
    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 사실 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, iant, khr
    https://golang.org/cl/64100044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/ecf700b5ee878519344fc521cb0d02837a943c0d

元コミット内容

このコミットは、Goコンパイラ(cmd/gc)とランタイム(src/pkg/runtime)に変更を加え、precisestack機能をデフォルトで有効にしています。

コミットメッセージの要点は以下の通りです。

  • Precisestackの目的: スタックのコレクションを完全に正確にすること。これにより、ガベージコレクタがスタックフレームを収集する際に、「使用されているが設定されていない」エラーが発生せず、ポインタが初期化されていないスタックワードからポインタを読み取るような状況がなくなります。
  • インターフェースへの影響: precisestackが有効になると、インターフェース値を読み取る際に、その値が初期化されていることが保証されます。これにより、型ワードがnilであるか、データワードを記述する有効なインターフェース型ワードであるかのどちらかであることが信頼できるようになります。
  • パフォーマンスへの影響: この変更は、スタック上の特定の値をエントリ時にゼロ初期化する必要があるため、all.bashでの全体的な実行時間に約5%のオーバーヘッドをもたらします。このコストはGo 1.3のリリースまでに削減される予定です(issue 7345)。
  • 既知のバグ: 既存のガベージコレクタのバグ(issue 7343と7344)が少なくとも2つ存在します。issue 7343はprecisestackがなくても発生しますが、issue 7344はprecisestackが有効な場合にのみ確認されています。ただし、これはprecisestackが直接の原因であることを意味するものではなく、既存の問題を悪化させている可能性が指摘されています。これらの問題もGo 1.3で修正される予定です。
  • 早期有効化の理由: precisestackを早期にデフォルトで有効にする理由は、より多くのテスト期間を確保するためと、コピー型スタック(copying stack)の作業がこの機能に依存しているためです。

変更の背景

Go言語のガベージコレクタは、プログラムが使用しなくなったメモリを自動的に解放する役割を担っています。このプロセスにおいて、ガベージコレクタはプログラムのスタックをスキャンし、どの値がポインタであり、どのポインタがまだ参照されているかを正確に識別する必要があります。

従来のGoのガベージコレクタでは、スタック上のポインタの識別に関して、ある程度のヒューリスティックなアプローチが取られていました。これは、パフォーマンスを最適化するための一時的な妥協点であり、スタック上のすべてのワードが常に正確にポインタであるか否かを識別できるわけではありませんでした。この「不正確さ」は、特に初期化されていないスタック領域にゴミデータが残っている場合や、インターフェース値のように型情報とデータが分離して格納される場合に問題を引き起こす可能性がありました。ガベージコレクタがゴミデータをポインタとして誤認識すると、誤ったメモリ領域をたどってしまい、プログラムのクラッシュやデータの破損につながる可能性がありました。

precisestackの導入は、このスタックスキャンの精度を根本的に向上させることを目的としています。特に、インターフェース値の取り扱いにおいて、型情報が常に信頼できる状態であることを保証することは、ガベージコレクタの正確性と堅牢性を高める上で不可欠でした。

このコミットが2014年2月に行われていることから、Go 1.3のリリースに向けた重要なステップであったことが伺えます。Go 1.3では、ガベージコレクタの改善が主要な目標の一つであり、特に「コピー型スタック」(copying stack)の実装が計画されていました。コピー型スタックは、スタックを移動させることで、より効率的なメモリ管理やガベージコレクションを可能にする技術ですが、そのためにはスタック上のポインタの正確な識別が不可欠です。precisestackは、このコピー型スタックを実現するための前提条件となる機能であったため、Go 1.3のリリース前にデフォルトで有効化し、十分なテスト期間を確保する必要がありました。

また、コミットメッセージで言及されている既存のガベージコレクタのバグ(issue 7343, 7344)は、precisestackの導入がこれらのバグのデバッグや修正を促進する可能性を示唆しています。precisestackによってスタックの状態がより明確になることで、潜在的な問題が顕在化しやすくなり、結果としてより安定したランタイムの実現に貢献すると考えられます。

前提知識の解説

ガベージコレクション (Garbage Collection, GC)

ガベージコレクションは、プログラムが動的に確保したメモリ領域のうち、もはやどの部分からも参照されなくなった(到達不能になった)ものを自動的に解放する仕組みです。これにより、プログラマは手動でのメモリ管理(malloc/freeなど)から解放され、メモリリークやダングリングポインタといったバグのリスクを低減できます。

Go言語のGCは、主に「マーク&スイープ」アルゴリズムをベースにしています。これは、到達可能なオブジェクトをマークし、マークされなかったオブジェクトを「ゴミ」として収集(スイープ)する方式です。このプロセスにおいて、GCはプログラムの実行中に存在するすべてのポインタを正確に識別し、それらが指すオブジェクトを追跡する必要があります。

スタック (Stack)

スタックは、プログラムの実行中に一時的なデータを格納するために使用されるメモリ領域です。関数呼び出しごとに、その関数のローカル変数、引数、戻りアドレスなどがスタックフレームとして積まれていきます。関数が終了すると、そのスタックフレームは解放されます。

Go言語では、ゴルーチン(goroutine)ごとに独立したスタックを持っています。これらのスタックは、必要に応じて動的にサイズが変更される(スタックの拡張・縮小)ことがあります。GCは、これらのスタックをスキャンして、スタック上に存在するポインタを識別し、それらがヒープ上のオブジェクトを指しているかどうかを判断する必要があります。

ポインタ (Pointer)

ポインタは、メモリ上の特定のアドレスを指し示す変数です。Go言語では、ポインタは型付けされており、特定の型の値を指します。GCは、ポインタを正確に識別することで、到達可能なオブジェクトのグラフを構築し、到達不能なオブジェクトを特定します。

インターフェース (Interface)

Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。インターフェース型の変数は、そのインターフェースが定義するすべてのメソッドを実装する任意の型の値を保持できます。

Goのインターフェース値は、内部的に2つのワード(ポインタ)で構成されます。

  1. 型ワード (Type Word): インターフェースが現在保持している具体的な値の型情報(_type構造体へのポインタ)を格納します。
  2. データワード (Data Word): インターフェースが現在保持している具体的な値そのもの(またはその値へのポインタ)を格納します。

GCがインターフェース値をスキャンする際、この型ワードとデータワードの両方を正確に解釈する必要があります。特に、型ワードが有効な型情報を指していることを確認し、その型情報に基づいてデータワードがポインタであるか否かを判断する必要があります。

ゼロ初期化 (Zeroing)

メモリ領域をゼロで埋めることです。Go言語では、変数が宣言されると、その変数は自動的にゼロ値で初期化されます(例: int0string""、ポインタはnil)。しかし、スタック上の特定の領域は、パフォーマンス上の理由から、必ずしもすぐにゼロ初期化されない場合がありました。precisestackは、GCの正確性を保証するために、スタック上の特定の領域を明示的にゼロ初期化することを要求します。これにより、GCが未初期化のメモリを誤ってポインタとして解釈するリスクがなくなります。

cmd/gcruntime

  • cmd/gc: Goコンパイラのバックエンドの一部であり、Goのソースコードを機械語に変換する過程で、スタックフレームのレイアウトやガベージコレクションに関するメタデータを生成します。
  • runtime: Goプログラムの実行を管理するランタイムシステムです。ガベージコレクタ、スケジューラ、メモリ管理などが含まれます。precisestackのロジックは、コンパイラによって生成された情報とランタイムのGCロジックの両方に影響を与えます。

技術的詳細

precisestackは、Goのガベージコレクタがスタックをスキャンする際の「精度」を向上させるためのメカニズムです。ここでの「精度」とは、ガベージコレクタがスタック上の各ワードがポインタであるか否かを、常に100%正確に識別できることを意味します。

従来のGoのGCでは、スタック上のポインタの識別に関して、一部ヒューリスティックなアプローチが取られていました。これは、コンパイラがスタック上のすべてのポインタの位置を正確に追跡するのではなく、ある程度の推測に基づいてGCがスキャンを行うことを意味します。例えば、あるスタックワードがポインタのように見える場合、GCはそれをポインタとして扱う可能性がありました。しかし、これは未初期化のメモリ領域にたまたまポインタのように見えるビットパターンが存在した場合に、誤ってそれをポインタとして解釈し、無効なメモリをたどってしまうという問題(「used and not set」エラー)を引き起こす可能性がありました。

precisestackが導入されると、この問題が解決されます。具体的には、以下の技術的変更が含まれます。

  1. スタックフレームのメタデータ: コンパイラ(cmd/gc)は、各関数のスタックフレームについて、どのオフセットにポインタが存在するか、そしてどの領域がポインタではないかという情報をより詳細に生成するようになります。このメタデータは、ガベージコレクタがスタックをスキャンする際に利用されます。
  2. スタックのゼロ初期化: precisestackを有効にするためには、関数エントリ時にスタック上の特定の領域をゼロ初期化する必要があります。特に、ポインタを格納する可能性のあるが、まだ有効な値が設定されていない領域は、nilポインタで埋められます。これにより、GCが未初期化のメモリを誤って有効なポインタとして解釈するリスクがなくなります。コミットメッセージで言及されている「約5%の実行時間オーバーヘッド」は、この追加のゼロ初期化処理によるものです。
  3. インターフェース値の保証: インターフェース値は、型ワードとデータワードの2つのポインタで構成されます。precisestackが有効になると、インターフェース値がスタック上に配置される際、その型ワードとデータワードが常に有効な状態(nilまたは適切な値)に初期化されることが保証されます。これにより、GCがインターフェース値をスキャンする際に、型ワードが信頼できる情報を提供し、データワードの解釈を正確に行えるようになります。これは、インターフェースの動的な性質を考慮すると非常に重要です。

この変更は、Goのガベージコレクタの堅牢性と正確性を大幅に向上させます。特に、並行処理が多用されるGoプログラムにおいて、GCがスタックを正確にスキャンできることは、メモリ安全性を確保し、デバッグが困難なバグ(例: GC関連のクラッシュ)を減らす上で極めて重要です。

また、コミットメッセージで「copying stack work depends on it」と述べられているように、precisestackはGo 1.3で導入された「コピー型スタック」の前提条件となります。コピー型スタックは、ゴルーチンのスタックをメモリ上で移動させることで、スタックの拡張・縮小をより効率的に行い、GCのパフォーマンスを向上させる技術です。スタックを移動させるためには、スタック上のすべてのポインタを正確に識別し、それらを新しいアドレスに更新する必要があるため、precisestackによるスタックの正確なポインタ識別が不可欠でした。

コアとなるコードの変更箇所

このコミットでは、主に以下の2つのファイルが変更されています。

  1. src/cmd/gc/lex.c
  2. src/pkg/runtime/proc.c

src/cmd/gc/lex.c の変更

--- 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++)

このファイルはGoコンパイラの一部であり、実験的な機能(GOEXPERIMENT)を有効にするためのロジックを含んでいます。 変更点:

  • setexp() 関数内で、precisestack_enabled = 1; // on by default という行が追加されています。
  • これは、precisestack機能がデフォルトで有効になるように、コンパイラ側のフラグを設定していることを意味します。以前は、GOEXPERIMENT環境変数を通じて明示的に有効にする必要がありましたが、この変更により、特別な設定なしにprecisestackが有効になります。

src/pkg/runtime/proc.c の変更

--- 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);

このファイルはGoランタイムの一部であり、スケジューラの初期化など、ランタイムのコアな処理を含んでいます。 変更点:

  • runtime·schedinit() 関数内で、runtime·precisestack = haveexperiment("precisestack"); という行が runtime·precisestack = true; // haveexperiment("precisestack"); に変更されています。
  • これは、ランタイム側でもprecisestackフラグを常にtrueに設定していることを意味します。以前は、haveexperiment関数(GOEXPERIMENT環境変数をチェックする関数)の結果に基づいてprecisestackの有効/無効が決定されていましたが、この変更により、ランタイムレベルでもprecisestackがデフォルトで有効になります。

コアとなるコードの解説

このコミットの核心は、Goコンパイラとランタイムの両方でprecisestack機能をデフォルトで有効にすることです。

  • src/cmd/gc/lex.c の変更: コンパイラがprecisestackを認識し、それに関連するコード生成(スタックフレームのメタデータ生成やゼロ初期化の指示など)をデフォルトで行うように設定します。これにより、コンパイルされたGoプログラムは、precisestackの恩恵を受けるようになります。
  • src/pkg/runtime/proc.c の変更: ランタイムがprecisestackが有効であることを認識し、ガベージコレクタがスタックをスキャンする際に、precisestackのロジック(より正確なポインタ識別、インターフェース値の取り扱いなど)を使用するように設定します。

これらの変更は、Goのガベージコレクタの動作を根本的に変更し、スタックのポインタ識別をより正確にすることで、メモリ安全性を向上させ、将来のGCの改善(特にコピー型スタック)のための基盤を築いています。

コメントアウトされたhaveexperiment("precisestack")は、この機能が以前は実験的なフラグとして扱われていたことを示唆しています。このコミットによって、その実験的な段階が終わり、デフォルトで有効化される「本番機能」となったことが明確に示されています。

関連リンク

  • Go Issue 7345: cmd/gc: reduce precisestack overhead: https://github.com/golang/go/issues/7345 このissueは、precisestackの導入によって発生するパフォーマンスオーバーヘッドを削減するための作業を追跡しています。コミットメッセージで言及されている「約5%のコスト」を削減するための取り組みです。
  • Go Issue 7343: runtime: GC can miss pointers in stack frames: https://github.com/golang/go/issues/7343 precisestackがなくても発生するとされるガベージコレクタのバグに関するissueです。
  • Go Issue 7344: runtime: GC can miss pointers in stack frames with precisestack: https://github.com/golang/go/issues/7344 precisestackが有効な場合にのみ確認されているガベージコレクタのバグに関するissueです。precisestackが既存の問題を悪化させている可能性が指摘されています。
  • Go CL 64100044: cmd/gc, runtime: enable precisestack by default: https://golang.org/cl/64100044 このコミットに対応するGoのコードレビュー(Change List)ページです。より詳細な議論や変更履歴を確認できます。

参考にした情報源リンク

  • Go言語のガベージコレクションに関する公式ドキュメントやブログ記事:
  • Go言語のガベージコレクタの歴史と進化に関する記事:
    • Go's Garbage Collector: https://go.dev/doc/gc-guide (より新しいGoバージョンに関する情報だが、GCの基本的な概念理解に役立つ)
    • Understanding Go's GC: https://blog.golang.org/go15gc (Go 1.5以降のGCに関する記事だが、GCの進化の背景を理解するのに役立つ)
  • スタックとポインタの正確なスキャンに関する一般的なガベージコレクションの概念:
    • "Garbage Collection Handbook: The Art of Automatic Memory Management" by Richard Jones, Antony Hosking, Eliot Moss (専門書)
  • Go言語のソースコード:
    • src/cmd/gc/lex.c および src/pkg/runtime/proc.c の該当コミット時点でのコード。
  • Go言語のIssueトラッカー:
  • Go言語のコードレビューシステム (Gerrit):

これらの情報源は、precisestackの技術的な詳細、その背景にあるガベージコレクションの原理、そしてGoランタイムの進化におけるその位置付けを理解するために参照されました。