[インデックス 18242] ファイルの概要
このコミットは、Goランタイムにおいて、GODEBUG=allocfreetrace
環境変数が設定されている場合に、ガベージコレクション (GC) 実行時のスタックトレースを出力する機能を追加するものです。これにより、メモリの割り当てと解放のトレース情報に加えて、GCがいつ、どのコンテキストで発生したかを詳細に把握できるようになり、メモリ関連のデバッグやプロファイリングがより容易になります。
コミット
commit 3ec60c253d9d419eb7a35a40e01315e3d1497465
Author: Russ Cox <rsc@golang.org>
Date: Tue Jan 14 10:39:50 2014 -0500
runtime: emit collection stacks in GODEBUG=allocfreetrace mode
R=khr, dvyukov
CC=golang-codereviews
https://golang.org/cl/51830043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3ec60c253d9d419eb7a35a40e01315e3d1497465
元コミット内容
runtime: emit collection stacks in GODEBUG=allocfreetrace mode
R=khr, dvyukov
CC=golang-codereviews
https://golang.org/cl/51830043
変更の背景
Go言語のランタイムは、自動メモリ管理(ガベージコレクション)によって開発者がメモリ管理の複雑さから解放されるように設計されています。しかし、メモリリーク、過剰なメモリ使用量、GCの一時停止によるパフォーマンス問題など、メモリ関連のデバッグは依然として困難な場合があります。
GODEBUG=allocfreetrace
は、Goプログラムがメモリを割り当てたり解放したりするたびに、そのイベントと関連するスタックトレースを出力するデバッグモードです。これは、特定のメモリ割り当てがどこで行われたか、または特定のオブジェクトがどこで解放されたかを追跡するのに非常に役立ちます。しかし、このモードではGC自体のイベントに関する詳細な情報、特にGCがどのコードパスからトリガーされたかについての情報が不足していました。
このコミットの背景には、GCが予期せぬタイミングで発生したり、特定のコードパスがGCを頻繁にトリガーしている場合に、その原因を特定しやすくするという目的があります。GCイベント発生時のスタックトレースを出力することで、開発者はGCのトリガー元をより正確に把握し、メモリ使用パターンやGCの挙動を最適化するための洞察を得ることができます。
前提知識の解説
Goのガベージコレクション (GC)
Goのガベージコレクタは、並行マーク&スイープ方式を採用しています。これは、プログラムの実行と並行してGCが動作し、アプリケーションの一時停止(ストップ・ザ・ワールド)時間を最小限に抑えるように設計されています。GCは、到達可能なオブジェクトをマークし、到達不能なオブジェクトを解放することでメモリを再利用します。GCは、ヒープの使用量がある閾値を超えた場合や、特定の時間間隔で自動的にトリガーされます。
GODEBUG
環境変数
GODEBUG
は、Goランタイムのデバッグ情報を制御するための環境変数です。様々なオプションがあり、ランタイムの内部動作に関する詳細な情報を出力させることができます。例えば、gctrace=1
はGCの実行に関する統計情報を出力し、schedtrace=1000ms
はスケジューラの動作を1秒ごとにトレースします。
GODEBUG=allocfreetrace
このオプションは、Goプログラムがメモリを割り当て(alloc
)たり解放(free
)したりするたびに、そのイベントと関連するスタックトレースを標準エラー出力にダンプします。これにより、どのコードがメモリを割り当て、どのコードがそのメモリを解放したかを追跡できます。これは、メモリリークの特定や、不要なメモリ割り当ての削減に非常に強力なツールです。
スタックトレースと runtime.callers
スタックトレースは、プログラムの実行中に現在アクティブな関数呼び出しのリストです。これにより、ある関数がどのように呼び出されたかの「呼び出し履歴」を把握できます。Goランタイムの内部では、runtime.callers
関数が現在のゴルーチンのスタックトレースを取得するために使用されます。この関数は、指定されたスキップ数(呼び出し元をスキップする数)とスタックフレームの最大数を指定して、uintptr
のスライスにプログラムカウンタのリストを返します。
runtime
パッケージ
runtime
パッケージは、Goプログラムの実行環境、ガベージコレクション、スケジューリング、低レベルのメモリ管理など、Goランタイムのコア機能を提供します。このパッケージの関数は、Go言語で直接呼び出すことはできませんが、Goコンパイラやリンカによって内部的に使用されます。
技術的詳細
このコミットの技術的な核心は、GODEBUG=allocfreetrace
が有効な場合に、ガベージコレクションの開始時に runtime.MProf_TraceGC
関数を呼び出すように変更された点です。
-
runtime.MProf_TraceGC
関数の追加:src/pkg/runtime/mprof.goc
にruntime.MProf_TraceGC
という新しい関数が追加されました。この関数は、GCイベントが発生したことを報告するためにコレクタによって呼び出されます。 この関数は、runtime.callers(1, stk, nelem(stk))
を使用して現在のスタックトレースを取得します。1
をスキップ数として指定することで、runtime.MProf_TraceGC
自体の呼び出しフレームをスキップし、その呼び出し元(つまりGCをトリガーしたコードパス)からスタックトレースを取得します。 取得したスタックトレースはprintstackframes
関数によって整形され、標準出力に「MProf_TraceGC」というプレフィックスとともにダンプされます。 -
runtime.gc
からのruntime.MProf_TraceGC
の呼び出し:src/pkg/runtime/mgc0.c
のruntime.gc
関数(Goのガベージコレクションの主要なエントリポイント)内に、runtime.debug.allocfreetrace
が真(つまりGODEBUG=allocfreetrace
が有効)である場合にruntime.MProf_TraceGC()
を呼び出す条件分岐が追加されました。これにより、GCが開始されるたびに、その時点のスタックトレースがallocfreetrace
の出力の一部として記録されるようになります。 -
malloc.h
の更新:src/pkg/runtime/malloc.h
にruntime.MProf_TraceGC
のプロトタイプ宣言が追加され、他のランタイムファイルからこの関数が呼び出せるようになりました。 -
runtime.callers
のnelem(stk)
への変更:src/pkg/runtime/mprof.goc
内のruntime.MProf_Malloc
およびruntime.blockevent
関数におけるruntime.callers
の呼び出しで、スタックフレームの最大数をハードコードされた32
からnelem(stk)
(スタック配列の要素数)に変更しています。これは、より汎用的なアプローチであり、スタック配列のサイズ変更に自動的に対応できるようになります。
これらの変更により、GODEBUG=allocfreetrace
を有効にしてGoプログラムを実行すると、メモリの割り当てと解放のトレースに加えて、ガベージコレクションが実行された時点のスタックトレースも出力されるようになります。これにより、GCがどのコードパスによってトリガーされたのか、そのGCがアプリケーションのどの部分のメモリ使用パターンに関連しているのかを、より詳細に分析できるようになります。
コアとなるコードの変更箇所
src/pkg/runtime/malloc.h
--- a/src/pkg/runtime/malloc.h
+++ b/src/pkg/runtime/malloc.h
@@ -521,6 +521,7 @@ enum
void runtime·MProf_Malloc(void*, uintptr, uintptr);
void runtime·MProf_Free(Bucket*, void*, uintptr);
void runtime·MProf_GC(void);
+void runtime·MProf_TraceGC(void);
int32 runtime·gcprocs(void);
void runtime·helpgc(int32 nproc);
void runtime·gchelper(void);
src/pkg/runtime/mgc0.c
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -2163,6 +2163,9 @@ runtime·gc(int32 force)
a.start_time = runtime·nanotime();
m->gcing = 1;
runtime·stoptheworld();
+
+ if(runtime·debug.allocfreetrace)
+ runtime·MProf_TraceGC();
clearpools();
src/pkg/runtime/mprof.goc
--- a/src/pkg/runtime/mprof.goc
+++ b/src/pkg/runtime/mprof.goc
@@ -173,6 +173,18 @@ printstackframes(uintptr *stk, int32 nstk)
}
}
+// Called by collector to report a gc in allocfreetrace mode.
+void
+runtime·MProf_TraceGC(void)
+{
+ uintptr stk[32];
+ int32 nstk;
+
+ nstk = runtime·callers(1, stk, nelem(stk));
+ runtime·printf("MProf_TraceGC\n");
+ printstackframes(stk, nstk);
+}
+
// Called by malloc to record a profiled block.
void
runtime·MProf_Malloc(void *p, uintptr size, uintptr typ)
@@ -183,14 +195,14 @@ runtime·MProf_Malloc(void *p, uintptr size, uintptr typ)
int8 *name;
int32 nstk;
- nstk = runtime·callers(1, stk, 32);
+ nstk = runtime·callers(1, stk, nelem(stk));
runtime·lock(&proflock);
- if(runtime·debug.allocfreetrace) {
+ if(runtime·debug.allocfreetrace) {
type = (Type*)(typ & ~3);
name = typeinfoname(typ & 3);
runtime·printf("MProf_Malloc(p=%p, size=%p, type=%p <%s", p, size, type, name);
if(type != nil)
- runtime·printf(" of %S", *type->string);
+ runtime·printf(" of %S", *type->string);
runtime·printf(">)\\n\");
printstackframes(stk, nstk);
}
@@ -247,7 +259,7 @@ runtime·blockevent(int64 cycles, int32 skip)
if(rate <= 0 || (rate > cycles && runtime·fastrand1()%rate > cycles))
return;
- nstk = runtime·callers(skip, stk, 32);
+ nstk = runtime·callers(skip, stk, nelem(stk));
runtime·lock(&proflock);\
b = stkbucket(BProf, stk, nstk, true);
b->count++;
コアとなるコードの解説
src/pkg/runtime/malloc.h
の変更
void runtime·MProf_TraceGC(void);
の追加: これは、runtime.MProf_TraceGC
関数のプロトタイプ宣言です。この宣言により、他のランタイムコンポーネント(特にmgc0.c
)がこの関数を認識し、呼び出すことができるようになります。GoランタイムのCコードとGoコード(実際にはGoの擬似Cコードである.goc
ファイル)間のインターフェースを定義する役割を果たします。
src/pkg/runtime/mgc0.c
の変更
if(runtime·debug.allocfreetrace) runtime·MProf_TraceGC();
の追加:runtime·gc
関数は、Goのガベージコレクションプロセスを開始する主要な関数です。この変更により、GCが開始される直前(runtime·stoptheworld()
の後、つまりGCが実際に作業を開始する直前)に、runtime·debug.allocfreetrace
フラグが真であるかどうかがチェックされます。 もしフラグが真であれば、新しく追加されたruntime·MProf_TraceGC()
関数が呼び出されます。これにより、GODEBUG=allocfreetrace
が有効な場合にのみ、GCイベントのスタックトレースが出力されるようになります。
src/pkg/runtime/mprof.goc
の変更
-
runtime·MProf_TraceGC
関数の新規追加: この関数は、GCイベントのスタックトレースを収集し、出力する役割を担います。uintptr stk[32];
: スタックトレースを格納するためのuintptr
型の配列stk
を宣言します。最大32フレームを格納できます。int32 nstk;
: 取得されたスタックフレームの数を格納する変数です。nstk = runtime·callers(1, stk, nelem(stk));
:runtime·callers
を呼び出してスタックトレースを取得します。- 第一引数の
1
は、runtime·MProf_TraceGC
関数自体のフレームをスキップし、その呼び出し元からトレースを開始することを意味します。 - 第二引数の
stk
は、スタックフレームを格納するバッファです。 - 第三引数の
nelem(stk)
は、stk
配列の要素数(この場合は32)を渡し、取得するスタックフレームの最大数を指定します。
- 第一引数の
runtime·printf("MProf_TraceGC\n");
: GCイベントが発生したことを示す識別子を出力します。printstackframes(stk, nstk);
: 取得したスタックフレームを整形して標準出力にダンプします。
-
runtime·MProf_Malloc
およびruntime·blockevent
におけるruntime·callers
の引数変更:nstk = runtime·callers(1, stk, nelem(stk));
(旧:32
)nstk = runtime·callers(skip, stk, nelem(stk));
(旧:32
) これらの変更は、runtime·callers
の第三引数(スタックフレームの最大数)をハードコードされた32
からnelem(stk)
に変更しています。nelem
はGoランタイム内部のマクロで、配列の要素数を返します。これにより、stk
配列のサイズが変更された場合でも、コードを修正することなく、常に配列の最大容量までスタックトレースを取得できるようになり、より堅牢で保守性の高いコードになります。
これらの変更により、GODEBUG=allocfreetrace
を使用する開発者は、メモリ割り当てと解放のイベントだけでなく、ガベージコレクションがいつ、どのコードパスからトリガーされたかという重要なデバッグ情報を得られるようになり、Goアプリケーションのメモリプロファイリングとデバッグ能力が大幅に向上します。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/
- Goのガベージコレクションに関するブログ記事やドキュメント (一般的な情報):
- The Go Blog: Go's new GC: https://go.dev/blog/go15gc
- Go runtime documentation (source code): https://github.com/golang/go/tree/master/src/runtime
参考にした情報源リンク
- Goのソースコード (特に
src/runtime
ディレクトリ) - Goの公式ドキュメント
GODEBUG
環境変数に関する情報 (GoのIssueや関連するブログ記事)runtime.callers
関数の挙動に関する情報 (Goのソースコードコメントや関連する議論)