[インデックス 14511] ファイルの概要
このコミットは、Goランタイムにおけるガベージコレクション(GC)の挙動を最適化するための変更です。具体的には、GC関数 gc()
がより大きなスタックで開始されるように reflect·call()
を使用することで、スタック容量不足による runtime·morestack()
の頻繁な呼び出しを削減し、GCの効率を向上させることを目的としています。
コミット
commit 51b8edcb37e7f20859b69623f69e9032e9601add
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date: Tue Nov 27 13:04:59 2012 -0500
runtime: use reflect·call() to enter the function gc()
Garbage collection code (to be merged later) is calling functions
which have many local variables. This increases the probability that
the stack capacity won't be big enough to hold the local variables.
So, start gc() on a bigger stack to eliminate a potentially large number
of calls to runtime·morestack().
R=rsc, remyoudompheng, dsymonds, minux.ma, iant, iant
CC=golang-dev
https://golang.org/cl/6846044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/51b8edcb37e7f20859b69623f69e9032e9601add
元コミット内容
runtime: use reflect·call() to enter the function gc()
Garbage collection code (to be merged later) is calling functions
which have many local variables. This increases the probability that
the stack capacity won't be big enough to hold the local variables.
So, start gc() on a bigger stack to eliminate a potentially large number
of calls to runtime·morestack().
変更の背景
Go言語のランタイムにおけるガベージコレクション(GC)は、プログラムの実行中に不要になったメモリを自動的に解放する重要なプロセスです。このコミットが作成された当時、GoのGCコードは、多くのローカル変数を必要とする関数を呼び出す傾向がありました。
Goの関数呼び出しでは、各関数にスタックフレームが割り当てられ、その中にローカル変数や引数などが格納されます。デフォルトのスタックサイズは限られており、もし関数が多くのローカル変数を必要とする場合、現在のスタック容量では足りなくなる可能性があります。このような状況が発生すると、Goランタイムは runtime·morestack()
という関数を呼び出して、スタックを拡張しようとします。
runtime·morestack()
の呼び出しは、スタックの拡張処理を伴うため、ある程度のオーバーヘッドが発生します。GCのような頻繁に実行される可能性のある、かつパフォーマンスが重要なシステムレベルの処理において、この runtime·morestack()
の呼び出しが多発すると、GCの効率が低下し、ひいてはアプリケーション全体のパフォーマンスに悪影響を及ぼす可能性がありました。
このコミットの目的は、GC関数 gc()
が最初から十分なスタック空間を持って開始されるようにすることで、runtime·morestack()
の呼び出し回数を減らし、GCのパフォーマンスを向上させることにありました。
前提知識の解説
Goランタイム (Go Runtime)
Goランタイムは、Goプログラムの実行を管理する低レベルのシステムです。これには、スケジューラ(ゴルーチンの管理)、ガベージコレクタ(メモリ管理)、チャネル、リフレクションなどの機能が含まれます。Goプログラムは、オペレーティングシステム上で直接実行されるのではなく、このランタイム上で動作します。
ガベージコレクション (Garbage Collection, GC)
ガベージコレクションは、プログラムが動的に割り当てたメモリのうち、もはや到達不可能(参照されていない)になったオブジェクトを自動的に識別し、解放するプロセスです。これにより、プログラマは手動でのメモリ管理から解放され、メモリリークのリスクを低減できます。GoのGCは、並行マーク&スイープ方式を採用しており、プログラムの実行と並行してGCが動作することで、アプリケーションの一時停止(Stop-the-World)時間を最小限に抑えるように設計されています。
スタック (Stack)
スタックは、プログラムの実行中に一時的なデータを格納するために使用されるメモリ領域です。関数が呼び出されるたびに、その関数のローカル変数、引数、戻りアドレスなどがスタック上に「スタックフレーム」としてプッシュされます。関数が終了すると、そのスタックフレームはポップされ、メモリが解放されます。Goでは、スタックは動的に拡張・縮小される「スプリットスタック」というメカニズムを採用しています。
スプリットスタック (Split Stack) と runtime·morestack()
Goのスプリットスタックは、スタックのサイズを固定するのではなく、必要に応じて動的に拡張する仕組みです。これにより、非常に小さなスタックでゴルーチンを開始し、必要に応じてスタックを成長させることができます。スタックが現在の容量を超えそうになると、Goランタイムは runtime·morestack()
という関数を呼び出します。この関数は、より大きな新しいスタックセグメントを割り当て、現在のスタックの内容を新しいセグメントにコピーし、実行を新しいスタックに切り替えます。このプロセスは透過的に行われますが、頻繁に発生するとパフォーマンスオーバーヘッドの原因となります。
リフレクション (Reflection) と reflect·call()
Goのリフレクションは、実行時にプログラムの構造(型、フィールド、メソッドなど)を検査・操作する機能です。reflect·call()
は、リフレクションメカニズムの一部として、関数を動的に呼び出すために使用される低レベルのランタイム関数です。通常、Goの関数呼び出しはコンパイル時に解決されますが、reflect·call()
を使用すると、実行時に決定された関数を呼び出すことができます。このコミットでは、この reflect·call()
の特性を利用して、gc()
関数を特定のスタックサイズで開始させています。
技術的詳細
このコミットの核心は、gc()
関数を通常の関数呼び出しパスではなく、reflect·call()
を介して呼び出すように変更した点にあります。
通常の関数呼び出しでは、Goランタイムはデフォルトのスタックサイズで関数を開始します。しかし、reflect·call()
を使用する場合、呼び出し元は m->moreframesize_minalloc
というフィールドを設定することで、呼び出される関数に割り当てる最小スタックフレームサイズを指定できます。
このコミットでは、runtime·gc
関数内で、実際のGC処理を行う gc
関数を直接呼び出す代わりに、以下のように変更しています。
gc_args
という構造体を定義し、gc
関数に渡す引数(force
)をこの構造体に格納するように変更しました。これは、reflect·call()
が引数を構造体として受け取るためです。m->moreframesize_minalloc
をStackBig
という大きな値に設定します。これにより、gc
関数が呼び出される際に、最初からStackBig
で指定されたサイズのスタックが確保されるようになります。reflect·call((byte*)gc, (byte*)&ap, sizeof(ap))
を呼び出します。これにより、gc
関数がStackBig
のスタックサイズを保証された状態で実行されます。
src/pkg/runtime/proc.c
の runtime·newstack
関数も変更され、m->moreframesize_minalloc
の値が考慮されるようになりました。runtime·newstack
は、スタック拡張が必要な場合に呼び出される関数ですが、この変更により、moreframesize_minalloc
が設定されている場合は、その値が最小フレームサイズとして考慮され、スタックが拡張される際に最初から十分なサイズが確保されるようになります。これにより、gc()
が開始される際に、たとえ多くのローカル変数が必要であっても、runtime·morestack()
が頻繁に呼び出されることを防ぎます。
このアプローチにより、GCの実行中にスタック拡張のためのオーバーヘッドが削減され、GCの全体的なパフォーマンスが向上することが期待されます。
コアとなるコードの変更箇所
src/pkg/runtime/mgc0.c
runtime·gc
関数がgc
関数を直接呼び出す代わりに、reflect·call
を使用するように変更されました。gc_args
構造体が定義され、gc
関数への引数をカプセル化します。m->moreframesize_minalloc = StackBig;
が追加され、gc
関数が大きなスタックで開始されるように設定されます。reflect·call((byte*)gc, (byte*)&ap, sizeof(ap));
が追加され、gc
関数をリフレクション経由で呼び出します。- 元の
gc
関数の本体がstatic void gc(struct gc_args *args)
として分離され、runtime·gc
から呼び出されるようになりました。
src/pkg/runtime/proc.c
runtime·newstack
関数にminalloc
変数が追加され、m->moreframesize_minalloc
の値を受け取るようになりました。framesize < minalloc
のチェックが追加され、minalloc
が設定されている場合は、スタックフレームサイズがその値以上になるように調整されます。
src/pkg/runtime/runtime.h
M
構造体にuint32 moreframesize_minalloc;
フィールドが追加されました。これは、reflect·call
を使用して関数を呼び出す際に、その関数に割り当てる最小スタックフレームサイズを指定するために使用されます。
コアとなるコードの解説
src/pkg/runtime/mgc0.c
の変更
// Structure of arguments passed to function gc().
// This allows the arguments to be passed via reflect·call.
struct gc_args
{
int32 force;
};
static void gc(struct gc_args *args);
void
runtime·gc(int32 force)
{
// ... (既存のコード) ...
// Run gc on a bigger stack to eliminate
// a potentially large number of calls to runtime·morestack.
a.force = force;
ap = &a;
m->moreframesize_minalloc = StackBig; // ここで最小スタックサイズを設定
reflect·call((byte*)gc, (byte*)&ap, sizeof(ap)); // reflect·call を使用して gc を呼び出す
// ... (既存のコード) ...
}
static void
gc(struct gc_args *args)
{
// ... (元の runtime·gc の本体がここに移動) ...
// 引数は args->force でアクセス
}
runtime·gc
は、外部からGCをトリガーするエントリポイントです。この関数内で、実際のGCロジックを含む gc
関数を直接呼び出すのではなく、reflect·call
を介して呼び出すように変更されました。m->moreframesize_minalloc = StackBig;
の行が重要で、これにより gc
関数が StackBig
で定義された十分な大きさのスタックで開始されることが保証されます。gc_args
構造体は、reflect·call
が引数を渡すためのラッパーとして機能します。
src/pkg/runtime/proc.c
の変更
void
runtime·newstack(void)
{
int32 framesize, minalloc, argsize; // minalloc が追加
// ...
minalloc = m->moreframesize_minalloc; // M構造体から最小割り当てサイズを取得
m->moreframesize_minalloc = 0; // 使用後はリセット
// ...
if(framesize < minalloc) // 実際のフレームサイズが最小割り当てサイズより小さい場合
framesize = minalloc; // 最小割り当てサイズに調整
// ...
}
runtime·newstack
は、現在のスタックが不足した場合に新しいスタックを割り当てる役割を担います。この変更により、m->moreframesize_minalloc
が設定されている場合(つまり、reflect·call
経由で呼び出された場合)、newstack
は framesize
が minalloc
以上になるように調整します。これにより、gc
関数が最初から大きなスタックを持つことが保証され、頻繁なスタック拡張の必要性が減少します。
src/pkg/runtime/runtime.h
の変更
struct M
{
// ...
uint32 moreframesize_minalloc; // 新しいフィールド
// ...
};
M
構造体は、Goランタイムにおける「マシン」(OSスレッド)を表す重要な構造体です。この構造体に moreframesize_minalloc
フィールドが追加されたことで、各M(OSスレッド)が、次に呼び出す関数に割り当てるべき最小スタックフレームサイズを保持できるようになりました。これは、reflect·call
と runtime·newstack
の間で情報を共有するためのメカニズムとして機能します。
これらの変更により、Goのガベージコレクタは、より効率的に動作し、スタック関連のオーバーヘッドを削減できるようになりました。
関連リンク
- Go言語のガベージコレクションに関する公式ドキュメントやブログ記事 (当時の情報源を探すのは困難ですが、GoのGCの進化を追うことで理解が深まります)
- Goのランタイムに関する設計ドキュメントやソースコード
- Goのリフレクションに関する公式ドキュメント
参考にした情報源リンク
- Goのソースコード (特に
src/pkg/runtime/
ディレクトリ) - Goのガベージコレクションに関する技術記事や論文
- Goのリフレクションに関する解説記事
- Goのスタック管理に関する議論やドキュメント
[インデックス 14511] ファイルの概要
このコミットは、Goランタイムにおけるガベージコレクション(GC)の挙動を最適化するための変更です。具体的には、GC関数 gc()
がより大きなスタックで開始されるように reflect·call()
を使用することで、スタック容量不足による runtime·morestack()
の頻繁な呼び出しを削減し、GCの効率を向上させることを目的としています。
コミット
commit 51b8edcb37e7f20859b69623f69e9032e9601add
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date: Tue Nov 27 13:04:59 2012 -0500
runtime: use reflect·call() to enter the function gc()
Garbage collection code (to be merged later) is calling functions
which have many local variables. This increases the probability that
the stack capacity won't be big enough to hold the local variables.
So, start gc() on a bigger stack to eliminate a potentially large number
of calls to runtime·morestack().
R=rsc, remyoudompheng, dsymonds, minux.ma, iant, iant
CC=golang-dev
https://golang.org/cl/6846044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/51b8edcb37e7f20859b69623f69e9032e9601add
元コミット内容
runtime: use reflect·call() to enter the function gc()
Garbage collection code (to be merged later) is calling functions
which have many local variables. This increases the probability that
the stack capacity won't be big enough to hold the local variables.
So, start gc() on a bigger stack to eliminate a potentially large number
of calls to runtime·morestack().
変更の背景
Go言語のランタイムにおけるガベージコレクション(GC)は、プログラムの実行中に不要になったメモリを自動的に解放する重要なプロセスです。このコミットが作成された当時、GoのGCコードは、多くのローカル変数を必要とする関数を呼び出す傾向がありました。
Goの関数呼び出しでは、各関数にスタックフレームが割り当てられ、その中にローカル変数や引数などが格納されます。デフォルトのスタックサイズは限られており、もし関数が多くのローカル変数を必要とする場合、現在のスタック容量では足りなくなる可能性があります。このような状況が発生すると、Goランタイムは runtime·morestack()
という関数を呼び出して、スタックを拡張しようとします。
runtime·morestack()
の呼び出しは、スタックの拡張処理を伴うため、ある程度のオーバーヘッドが発生します。GCのような頻繁に実行される可能性のある、かつパフォーマンスが重要なシステムレベルの処理において、この runtime·morestack()
の呼び出しが多発すると、GCの効率が低下し、ひいてはアプリケーション全体のパフォーマンスに悪影響を及ぼす可能性がありました。
このコミットの目的は、GC関数 gc()
が最初から十分なスタック空間を持って開始されるようにすることで、runtime·morestack()
の呼び出し回数を減らし、GCのパフォーマンスを向上させることにありました。
前提知識の解説
Goランタイム (Go Runtime)
Goランタイムは、Goプログラムの実行を管理する低レベルのシステムです。これには、スケジューラ(ゴルーチンの管理)、ガベージコレクタ(メモリ管理)、チャネル、リフレクションなどの機能が含まれます。Goプログラムは、オペレーティングシステム上で直接実行されるのではなく、このランタイム上で動作します。
ガベージコレクション (Garbage Collection, GC)
ガベージコレクションは、プログラムが動的に割り当てたメモリのうち、もはや到達不可能(参照されていない)になったオブジェクトを自動的に識別し、解放するプロセスです。これにより、プログラマは手動でのメモリ管理から解放され、メモリリークのリスクを低減できます。GoのGCは、並行マーク&スイープ方式を採用しており、プログラムの実行と並行してGCが動作することで、アプリケーションの一時停止(Stop-the-World)時間を最小限に抑えるように設計されています。
スタック (Stack)
スタックは、プログラムの実行中に一時的なデータを格納するために使用されるメモリ領域です。関数が呼び出されるたびに、その関数のローカル変数、引数、戻りアドレスなどがスタック上に「スタックフレーム」としてプッシュされます。関数が終了すると、そのスタックフレームはポップされ、メモリが解放されます。Goでは、スタックは動的に拡張・縮小される「スプリットスタック」というメカニズムを採用しています。
スプリットスタック (Split Stack) と runtime·morestack()
Goのスプリットスタックは、スタックのサイズを固定するのではなく、必要に応じて動的に拡張する仕組みです。これにより、非常に小さなスタックでゴルーチンを開始し、必要に応じてスタックを成長させることができます。スタックが現在の容量を超えそうになると、Goランタイムは runtime·morestack()
という関数を呼び出します。この関数は、より大きな新しいスタックセグメントを割り当て、現在のスタックの内容を新しいセグメントにコピーし、実行を新しいスタックに切り替えます。このプロセスは透過的に行われますが、頻繁に発生するとパフォーマンスオーバーヘッドの原因となります。
リフレクション (Reflection) と reflect.Call()
Goのリフレクションは、実行時にプログラムの構造(型、フィールド、メソッドなど)を検査・操作する機能です。reflect.Call()
は、リフレクションメカニズムの一部として、関数を動的に呼び出すために使用される低レベルのランタイム関数です。通常、Goの関数呼び出しはコンパイル時に解決されますが、reflect.Call()
を使用すると、実行時に決定された関数を呼び出すことができます。このコミットでは、この reflect.Call()
の特性を利用して、gc()
関数を特定のスタックサイズで開始させています。
技術的詳細
このコミットの核心は、gc()
関数を通常の関数呼び出しパスではなく、reflect.Call()
を介して呼び出すように変更した点にあります。
通常の関数呼び出しでは、Goランタイムはデフォルトのスタックサイズで関数を開始します。しかし、reflect.Call()
を使用する場合、呼び出し元は m->moreframesize_minalloc
というフィールドを設定することで、呼び出される関数に割り当てる最小スタックフレームサイズを指定できます。
このコミットでは、runtime·gc
関数内で、実際のGC処理を行う gc
関数を直接呼び出す代わりに、以下のように変更しています。
gc_args
という構造体を定義し、gc
関数に渡す引数(force
)をこの構造体に格納するように変更しました。これは、reflect.Call()
が引数を構造体として受け取るためです。m->moreframesize_minalloc
をStackBig
という大きな値に設定します。これにより、gc
関数が呼び出される際に、最初からStackBig
で指定されたサイズのスタックが確保されるようになります。reflect.Call((byte*)gc, (byte*)&ap, sizeof(ap))
を呼び出します。これにより、gc
関数がStackBig
のスタックサイズを保証された状態で実行されます。
src/pkg/runtime/proc.c
の runtime·newstack
関数も変更され、m->moreframesize_minalloc
の値が考慮されるようになりました。runtime·newstack
は、スタック拡張が必要な場合に呼び出される関数ですが、この変更により、moreframesize_minalloc
が設定されている場合は、その値が最小フレームサイズとして考慮され、スタックが拡張される際に最初から十分なサイズが確保されるようになります。これにより、gc()
が開始される際に、たとえ多くのローカル変数が必要であっても、runtime·morestack()
が頻繁に呼び出されることを防ぎます。
このアプローチにより、GCの実行中にスタック拡張のためのオーバーヘッドが削減され、GCの全体的なパフォーマンスが向上することが期待されます。
コアとなるコードの変更箇所
src/pkg/runtime/mgc0.c
runtime·gc
関数がgc
関数を直接呼び出す代わりに、reflect.Call
を使用するように変更されました。gc_args
構造体が定義され、gc
関数への引数をカプセル化します。m->moreframesize_minalloc = StackBig;
が追加され、gc
関数が大きなスタックで開始されるように設定されます。reflect.Call((byte*)gc, (byte*)&ap, sizeof(ap));
が追加され、gc
関数をリフレクション経由で呼び出します。- 元の
gc
関数の本体がstatic void gc(struct gc_args *args)
として分離され、runtime·gc
から呼び出されるようになりました。
src/pkg/runtime/proc.c
runtime·newstack
関数にminalloc
変数が追加され、m->moreframesize_minalloc
の値を受け取るようになりました。framesize < minalloc
のチェックが追加され、minalloc
が設定されている場合は、スタックフレームサイズがその値以上になるように調整されます。
src/pkg/runtime/runtime.h
M
構造体にuint32 moreframesize_minalloc;
フィールドが追加されました。これは、reflect.Call
を使用して関数を呼び出す際に、その関数に割り当てる最小スタックフレームサイズを指定するために使用されます。
コアとなるコードの解説
src/pkg/runtime/mgc0.c
の変更
// Structure of arguments passed to function gc().
// This allows the arguments to be passed via reflect·call.
struct gc_args
{
int32 force;
};
static void gc(struct gc_args *args);
void
runtime·gc(int32 force)
{
// ... (既存のコード) ...
// Run gc on a bigger stack to eliminate
// a potentially large number of calls to runtime·morestack.
a.force = force;
ap = &a;
m->moreframesize_minalloc = StackBig; // ここで最小スタックサイズを設定
reflect·call((byte*)gc, (byte*)&ap, sizeof(ap)); // reflect·call を使用して gc を呼び出す
// ... (既存のコード) ...
}
static void
gc(struct gc_args *args)
{
// ... (元の runtime·gc の本体がここに移動) ...
// 引数は args->force でアクセス
}
runtime·gc
は、外部からGCをトリガーするエントリポイントです。この関数内で、実際のGCロジックを含む gc
関数を直接呼び出すのではなく、reflect.Call
を介して呼び出すように変更されました。m->moreframesize_minalloc = StackBig;
の行が重要で、これにより gc
関数が StackBig
で定義された十分な大きさのスタックで開始されることが保証されます。gc_args
構造体は、reflect.Call
が引数を渡すためのラッパーとして機能します。
src/pkg/runtime/proc.c
の変更
void
runtime·newstack(void)
{
int32 framesize, minalloc, argsize; // minalloc が追加
// ...
minalloc = m->moreframesize_minalloc; // M構造体から最小割り当てサイズを取得
m->moreframesize_minalloc = 0; // 使用後はリセット
// ...
if(framesize < minalloc) // 実際のフレームサイズが最小割り当てサイズより小さい場合
framesize = minalloc; // 最小割り当てサイズに調整
// ...
}
runtime·newstack
は、現在のスタックが不足した場合に新しいスタックを割り当てる役割を担います。この変更により、m->moreframesize_minalloc
が設定されている場合(つまり、reflect.Call
経由で呼び出された場合)、newstack
は framesize
が minalloc
以上になるように調整します。これにより、gc
関数が最初から大きなスタックを持つことが保証され、頻繁なスタック拡張の必要性が減少します。
src/pkg/runtime/runtime.h
の変更
struct M
{
// ...
uint32 moreframesize_minalloc; // 新しいフィールド
// ...
};
M
構造体は、Goランタイムにおける「マシン」(OSスレッド)を表す重要な構造体です。この構造体に moreframesize_minalloc
フィールドが追加されたことで、各M(OSスレッド)が、次に呼び出す関数に割り当てるべき最小スタックフレームサイズを保持できるようになりました。これは、reflect.Call
と runtime·newstack
の間で情報を共有するためのメカニズムとして機能します。
これらの変更により、Goのガベージコレクタは、より効率的に動作し、スタック関連のオーバーヘッドを削減できるようになりました。
関連リンク
- Go言語のガベージコレクションに関する公式ドキュメントやブログ記事 (当時の情報源を探すのは困難ですが、GoのGCの進化を追うことで理解が深まります)
- Goのランタイムに関する設計ドキュメントやソースコード
- Goのリフレクションに関する公式ドキュメント
参考にした情報源リンク
- Goのソースコード (特に
src/pkg/runtime/
ディレクトリ) - Goのガベージコレクションに関する技術記事や論文
- Goのリフレクションに関する解説記事
- Goのスタック管理に関する議論やドキュメント
- Go runtime garbage collection stack management reflect.call morestack (Web検索結果)