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

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

このコミットは、Goランタイムにおけるスタックコピー機能を一時的に無効化するものです。具体的には、src/pkg/runtime/proc.cファイル内のruntime·copystack変数をfalseに設定しています。この変更には「TODO: remove」というコメントが付されており、これは一時的な措置であることを示唆しています。Goランタイムのスタック管理戦略がセグメントスタックから連続スタック(コピー方式)へと移行する過渡期における、テストやデバッグ、あるいは特定の挙動の検証を目的とした変更であると考えられます。

コミット

  • コミットハッシュ: f50a87058b6773f277d139b9c85ad421b92620d2
  • 作者: Keith Randall khr@golang.org
  • コミット日時: 2014年2月27日 木曜日 01:45:22 -0800
  • コミットメッセージ:
    runtime: disable stack copying
    TBR=dvyukov
    
    TBR=dvyukov
    CC=golang-codereviews
    https://golang.org/cl/69080045
    

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

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

元コミット内容

commit f50a87058b6773f277d139b9c85ad421b92620d2
Author: Keith Randall <khr@golang.org>
Date:   Thu Feb 27 01:45:22 2014 -0800

    runtime: disable stack copying
    TBR=dvyukov

    TBR=dvyukov
    CC=golang-codereviews
    https://golang.org/cl/69080045
---
 src/pkg/runtime/proc.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/pkg/runtime/proc.c b/src/pkg/runtime/proc.c
index 94d08bb55c..6b56634225 100644
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -174,6 +174,7 @@ runtime·schedinit(void)
  	procresize(procs);

  	runtime·copystack = runtime·precisestack;
+	runtime·copystack = false; // TODO: remove
  	p = runtime·getenv("GOCOPYSTACK");
  	if(p != nil && !runtime·strcmp(p, (byte*)"0"))
  	\truntime·copystack = false;

変更の背景

このコミットが行われた2014年2月は、Go言語のランタイムがスタック管理戦略において大きな転換期を迎えていた時期にあたります。Goの初期バージョンでは「セグメントスタック (segmented stacks)」と呼ばれる方式が採用されていました。これは、各ゴルーチンが小さなスタックセグメント(例: 4KB)から開始し、スタックが不足すると新しい、より大きなセグメントを割り当てて既存のセグメントにリンクするというものでした。この方式は、多数のゴルーチンを効率的に扱うために、固定サイズの大きなスタックを割り当てることによるメモリの無駄を避ける目的がありました。

しかし、セグメントスタックにはいくつかの課題がありました。

  • パフォーマンスオーバーヘッド: セグメント境界をまたぐ関数呼び出しは、スタックオーバーフローのチェックや新しいセグメントの割り当てが必要となるため、コストが高くなりがちでした。
  • ガベージコレクションの複雑性: 非連続なスタックセグメント間でポインタを管理することは、ガベージコレクタの実装を複雑にしていました。
  • スタックスプリットとスタックスラッシング: 深い再帰関数や大きなスタックフレームを持つ関数は、頻繁なスタックスプリットを引き起こし、「スタックスラッシング」と呼ばれるパフォーマンス問題につながることがありました。

これらの問題を解決するため、GoランタイムチームはGo 1.4(2014年12月リリース)で「連続スタック (contiguous stacks)」または「スタックコピー (stack copying)」方式への移行を進めていました。この新しい方式では、スタックが不足しそうになると、現在のスタックの内容を新しい、より大きな連続したメモリブロックにコピーします。

このコミット(2014年2月)は、Go 1.4での連続スタックへの完全な移行に先立つ、開発・テスト段階での一時的な措置であると推測されます。// TODO: removeというコメントは、この変更が永続的なものではなく、特定のテストシナリオやデバッグのために一時的にスタックコピーの挙動を無効化していることを明確に示しています。これは、新しいスタックコピーメカニズムの安定性やパフォーマンスを評価する過程で、その機能を一時的に切り離して他の部分の動作を確認する必要があったためと考えられます。

前提知識の解説

Goランタイム (Go Runtime)

Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ゴルーチン(軽量スレッド)のスケジューリング、メモリ管理(ガベージコレクションを含む)、スタック管理、チャネル通信などが含まれます。Goプログラムは、オペレーティングシステムのネイティブスレッド上で実行されますが、Goランタイムがこれらのスレッド上で多数のゴルーチンを多重化して実行することで、高い並行性を実現しています。

ゴルーチン (Goroutine)

ゴルーチンはGo言語における並行処理の基本単位です。OSのスレッドよりもはるかに軽量で、数百万ものゴルーチンを同時に実行することが可能です。各ゴルーチンは独自のスタックを持ち、このスタックは必要に応じて動的にサイズが変更されます。

スタック (Stack)

プログラムのスタックは、関数呼び出しの際にローカル変数、関数引数、リターンアドレスなどを格納するために使用されるメモリ領域です。関数が呼び出されるたびにスタックフレームがプッシュされ、関数から戻るたびにポップされます。

セグメントスタック (Segmented Stacks)

Go 1.4以前に採用されていたスタック管理方式です。ゴルーチンのスタックは、必要に応じて小さなメモリセグメントを連結していくことで拡張されました。これにより、初期のスタックサイズを小さく保ち、メモリ使用量を抑えることができましたが、セグメント間のポインタ管理やスタックの拡張・縮小に伴うオーバーヘッドが課題でした。

連続スタック (Contiguous Stacks) / スタックコピー (Stack Copying)

Go 1.4以降に採用されているスタック管理方式です。ゴルーチンのスタックは連続したメモリブロックとして確保されます。スタックが不足しそうになると、Goランタイムはより大きな新しい連続メモリブロックを割り当て、古いスタックの内容を新しいブロックにコピーします。これにより、スタックの拡張・縮小がより効率的になり、ガベージコレクションの複雑性も軽減されました。

runtime·copystack

Goランタイム内部で使用される変数で、スタックコピーの挙動を制御するフラグであると推測されます。この変数がtrueであればスタックコピーが有効になり、falseであれば無効になる、といった役割を持つと考えられます。

技術的詳細

このコミットは、Goランタイムのruntime·schedinit関数内でruntime·copystack = false;という行を追加しています。runtime·schedinitは、Goランタイムの初期化処理を行う重要な関数です。この関数内でruntime·copystackfalseに設定するということは、Goプログラムが起動する際に、スタックコピー機能がデフォルトで無効化されることを意味します。

元のコードではruntime·copystack = runtime·precisestack;という行があり、これはruntime·copystackruntime·precisestackという別の変数の値に依存して設定されることを示しています。runtime·precisestackは、正確なスタックスキャン(ガベージコレクションのためにスタック上のポインタを正確に識別する機能)が有効かどうかを示すフラグである可能性が高いです。つまり、このコミット以前は、正確なスタックスキャンが有効な場合にスタックコピーも有効になる、という関連性があったのかもしれません。

しかし、このコミットではその直後にruntime·copystack = false; // TODO: removeという行が追加されています。これは、runtime·precisestackの値にかかわらず、強制的にスタックコピーを無効化していることを意味します。そして、最も重要なのは// TODO: removeというコメントです。このコメントは、この変更が一時的なものであり、将来的に削除される予定であることを明確に示しています。

考えられる技術的背景としては、以下のようなシナリオが挙げられます。

  1. スタックコピー機能のデバッグ/テスト: 新しいスタックコピーメカニズムを開発している最中に、その機能が有効な場合と無効な場合で、他のランタイムコンポーネント(スケジューラ、ガベージコレクタなど)がどのように振る舞うかを比較テストする必要があった。一時的に無効化することで、スタックコピーが原因で発生している可能性のあるバグを切り分けたり、スタックコピーなしでのベースラインパフォーマンスを測定したりすることが可能になります。
  2. 特定のバグの回避: スタックコピー機能に、特定の条件下で発生する既知のバグがあり、その修正が完了するまでの間、一時的に機能を無効化して安定性を確保する必要があった。
  3. 移行期間中の互換性維持: セグメントスタックから連続スタックへの移行は複雑なプロセスであり、すべてのコンポーネントが新しいスタックモデルに対応するまでには時間がかかります。この一時的な無効化は、移行期間中に特定のレガシーコードパスやテストケースが正しく動作するようにするための措置であった可能性もあります。

このコミットは、Goランタイムがセグメントスタックから連続スタックへの移行という、Goのパフォーマンスとガベージコレクションの効率を大きく改善する重要な変更に取り組んでいた時期の、内部的な開発プロセスの一端を示しています。

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

src/pkg/runtime/proc.c ファイルの runtime·schedinit 関数内:

--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -174,6 +174,7 @@ runtime·schedinit(void)
  	procresize(procs);

  	runtime·copystack = runtime·precisestack;
+	runtime·copystack = false; // TODO: remove
  	p = runtime·getenv("GOCOPYSTACK");
  	if(p != nil && !runtime·strcmp(p, (byte*)"0"))
  	\truntime·copystack = false;

コアとなるコードの解説

追加された行は runtime·copystack = false; // TODO: remove です。

  • runtime·copystack: これはGoランタイム内部でスタックコピーの挙動を制御するグローバル変数またはフラグであると推測されます。C言語で書かれたGoランタイムのコードでは、runtime·プレフィックスはランタイム内部のシンボルによく使われます。
  • = false;: この行は、runtime·copystackの値を明示的にfalseに設定しています。これにより、Goランタイムがゴルーチンのスタックを拡張する必要がある場合に、スタックコピーのメカニズムが実行されなくなります。代わりに、Go 1.4以前のセグメントスタックの挙動、あるいはスタックコピーが有効でない場合のフォールバックメカニズムが使用されることになります。
  • // TODO: remove: このコメントは非常に重要です。これは、このコード行が一時的なものであり、将来的に削除される予定であることを開発者が意図していることを示しています。これは、前述の「変更の背景」や「技術的詳細」で述べたような、開発中のテスト、デバッグ、または一時的な回避策として導入された可能性を強く裏付けています。

この変更は、Goランタイムの初期化時に一度だけ実行されるため、プログラムのライフサイクル全体にわたってスタックコピーが無効化されます。ただし、その後のコードで環境変数GOCOPYSTACK"0"に設定されているかどうかのチェックも行われており、これによりユーザーが環境変数を通じてスタックコピーを無効化するオプションも提供されていることがわかります。このコミットの変更は、その環境変数による制御とは別に、コードレベルで強制的に無効化するものです。

関連リンク

参考にした情報源リンク