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

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

このコミットは、Goランタイムのスタック管理に関連するsrc/pkg/runtime/stack.cファイルに対する変更です。stack.cは、Goの軽量スレッドであるGoroutineのスタックの割り当て、拡張、縮小といったライフサイクルを管理するGoランタイムの重要な部分を担っています。特に、Goroutineが使用するメモリを効率的に管理し、必要に応じてスタックサイズを動的に調整するロジックが含まれています。

コミット

  • コミットハッシュ: b8d40172ce2a724ecb125746f37aee989ced5ac9
  • 作者: Dmitriy Vyukov
  • コミット日時: 2014年3月14日 金曜日 21:11:04 +0400
  • コミットメッセージ: runtime: do not shrink stacks GOCOPYSTACK=0

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

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

元コミット内容

runtime: do not shrink stacks GOCOPYSTACK=0

LGTM=rsc
R=golang-codereviews
CC=golang-codereviews, khr, rsc
https://golang.org/cl/76070043

変更の背景

このコミットは、GoランタイムがGoroutineのスタックを縮小する挙動を制御するためのものです。Goランタイムは、メモリ効率のためにGoroutineのスタックを動的に伸縮させます。スタックが過剰に割り当てられている場合、ランタイムはそれを縮小してメモリを解放しようとします。

コミットメッセージにあるGOCOPYSTACK=0という記述は、スタックコピーのメカニズムが特定の条件下で無効になっている、または無効にしたいシナリオを示唆しています。Go 1.3(このコミットの時期と近い)では、従来の「セグメントスタック」モデルから「連続スタック(コピーGC)」モデルへの移行が行われました。セグメントスタックはスタックの成長を容易にする一方で、「ホットスプリット問題」と呼ばれるパフォーマンス上の課題を抱えていました。連続スタックモデルでは、スタックの成長や縮小が必要な場合に、スタックの内容を新しいメモリ領域にコピーすることで対応します。

このコミットは、おそらくスタックコピーメカニズムが有効でない、または意図的に無効化されている場合に、スタックの縮小処理を行わないようにするためのガードを追加しています。これは、特定のデバッグシナリオ、パフォーマンスプロファイリング、あるいはスタックコピーが利用できない環境(非常に稀なケース)での安定性確保のために導入された可能性があります。スタックコピーが行われない状況でスタック縮小を試みると、予期せぬ挙動やクラッシュを引き起こす可能性があるため、それを回避するための安全策と考えられます。

前提知識の解説

GoのGoroutineとスタック

Go言語のGoroutineは、非常に軽量な並行処理の単位です。OSのスレッドとは異なり、数KBという小さなスタックサイズで開始され、必要に応じて自動的にスタックを拡張・縮小します。これにより、数百万ものGoroutineを同時に実行することが可能になります。各Goroutineは独自のスタックを持っており、関数呼び出しのローカル変数やリターンアドレスなどが格納されます。

スタックの動的伸縮

Goランタイムは、Goroutineのスタック使用量を監視し、必要に応じてスタックを「成長」させたり「縮小」させたりします。

  • スタックの成長: Goroutineがより多くのスタックメモリを必要とする場合(例えば、深い関数呼び出しや大きなローカル変数の割り当て)、ランタイムは自動的にスタックを拡張します。
  • スタックの縮小: Goroutineが割り当てられたスタックメモリをほとんど使用していない場合、ランタイムはメモリを解放するためにスタックを縮小することがあります。これは主にガベージコレクション(GC)のサイクル中に行われ、メモリの断片化を防ぎ、全体的なメモリ使用量を削減するのに役立ちます。

セグメントスタックとコピーGC (連続スタック)

Go 1.3より前のバージョンでは、「セグメントスタック」という方式が採用されていました。これは、スタックが複数の不連続なメモリセグメントで構成されるもので、必要に応じて新しいセグメントが追加されることでスタックが成長しました。しかし、頻繁なスタックの成長と縮小(特に「ホットスプリット問題」と呼ばれる、関数呼び出しとリターンがスタックセグメントの割り当てと解放を繰り返す状況)は、パフォーマンスオーバーヘッドを引き起こすことがありました。

Go 1.3で導入された「連続スタック」モデル(しばしば「コピーGC」と関連付けられる)では、スタックは連続したメモリブロックとして扱われます。スタックの成長や縮小が必要な場合、ランタイムは新しいサイズのメモリブロックを割り当て、古いスタックの内容を新しいブロックにコピーします。このコピー操作は、ガベージコレクタがヒープオブジェクトを移動するのと同様のメカニズムで行われるため、「コピーGC」という文脈で語られることがあります。このモデルは、セグメントスタックのパフォーマンス問題を解決し、スタックの伸縮をより効率的にしました。

runtime.copystack

runtime.copystackは、Goランタイム内部でスタックコピーメカニズムが有効であるか、またはスタックコピー操作自体を指す可能性のある内部的なフラグまたは関数です。このコミットの文脈では、!runtime·copystackという条件が追加されていることから、これはスタックコピー機能が利用可能であるか、あるいは現在有効であるかを示すブーリアン値(またはそれに評価されるもの)であると推測されます。もしこの値がfalseであれば、スタックコピーができない、または望ましくない状況にあることを意味します。

技術的詳細

このコミットは、runtime·shrinkstackというGoランタイムの内部関数にガード条件を追加しています。runtime·shrinkstackは、Goroutineのスタックが過剰に大きい場合に、そのスタックを縮小する役割を担っています。

追加されたコードは以下の通りです。

if(!runtime·copystack)
    return;

この条件は、runtime·shrinkstack関数の冒頭に配置されています。

  • runtime·copystackfalse(または0)である場合、!runtime·copystacktrueとなり、関数は即座にreturnします。
  • これにより、スタックの縮小処理(メモリの再割り当てや内容のコピーなど)が完全にスキップされます。

この変更の意図は、スタックコピーメカニズムが利用できない、または意図的に無効化されている状況下で、スタックの縮小を試みることによる潜在的な問題を回避することにあります。スタックの縮小は、通常、スタックの内容をより小さな新しいメモリ領域にコピーする操作を伴います。もしスタックコピーが何らかの理由で実行できない場合、この操作は失敗するか、未定義の動作を引き起こす可能性があります。

GOCOPYSTACK=0というコミットメッセージは、おそらくGoランタイムのビルド時オプションや内部的な設定によって、スタックコピー機能が明示的に無効化されるシナリオを指していると考えられます。このような設定は、特定のデバッグビルドや、非常に特殊な環境でのみ使用される可能性があります。

結果として、この変更により、runtime·copystackfalseである限り、Goroutineのスタックは決して縮小されなくなります。これは、メモリ使用量が増加する可能性を意味しますが、同時にスタックコピーに関連する潜在的な問題を回避し、ランタイムの安定性を向上させます。

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

--- a/src/pkg/runtime/stack.c
+++ b/src/pkg/runtime/stack.c
@@ -776,6 +776,8 @@ runtime·shrinkstack(G *gp)
 	uintptr used, oldsize, newsize;
 	MSpan *span;
 
+	if(!runtime·copystack)
+		return;
 	oldstk = (byte*)gp->stackguard - StackGuard;
 	oldbase = (byte*)gp->stackbase + sizeof(Stktop);
 	oldsize = oldbase - oldstk;

コアとなるコードの解説

変更はsrc/pkg/runtime/stack.cファイルのruntime·shrinkstack関数内で行われています。

runtime·shrinkstack(G *gp)関数は、引数としてGoroutineのポインタgpを受け取り、そのGoroutineのスタックを縮小しようと試みます。

追加された2行は、この関数の冒頭、既存のスタック縮小ロジックが実行される前に挿入されています。

+	if(!runtime·copystack)
+		return;
  • if(!runtime·copystack): この条件文は、runtime·copystackというグローバル変数またはフラグがfalse(またはC言語の文脈で0)であるかどうかをチェックします。
    • runtime·copystackは、Goランタイムがスタックコピーメカニズムを使用しているかどうかを示す内部的なインジケータであると推測されます。
  • return;: もしruntime·copystackfalseであれば、このreturn文が実行され、runtime·shrinkstack関数はそこで処理を終了します。これにより、その後のスタック縮小のための複雑なロジック(oldstk, oldbase, oldsizeなどの計算や、メモリの再割り当て、内容のコピーなど)は一切実行されません。

この変更の目的は、スタックコピーが有効でない、または何らかの理由で実行できない場合に、スタックの縮小処理を安全にスキップすることです。これにより、スタックコピーに依存する縮小ロジックが、その前提条件が満たされない状況で実行されることによる潜在的なバグやクラッシュを防ぎます。

関連リンク

参考にした情報源リンク