[インデックス 17908] ファイルの概要
このコミットは、Goランタイムにメモリ割り当てと解放のトレーシング機能を追加するものです。特に、ガベージコレクション(GC)のデバッグを目的としており、GODEBUG=allocfreetrace=1
を設定することで、各オブジェクトの割り当て時と解放時に詳細な情報とスタックトレースが出力されるようになります。
コミット
commit 48279bd567cc4d1efca9f69e713469fc6659eb26
Author: Carl Shapiro <cshapiro@google.com>
Date: Tue Dec 3 14:42:38 2013 -0800
runtime: add an allocation and free tracing for gc debugging
Output for an allocation and free (sweep) follows
MProf_Malloc(p=0xc2100210a0, size=0x50, type=0x0 <single object>)
#0 0x46ee15 runtime.mallocgc /usr/local/google/home/cshapiro/go/src/pkg/runtime/malloc.goc:141
#1 0x47004f runtime.settype_flush /usr/local/google/home/cshapiro/go/src/pkg/runtime/malloc.goc:612
#2 0x45f92c gc /usr/local/google/home/cshapiro/go/src/pkg/runtime/mgc0.c:2071
#3 0x45f89e mgc /usr/local/google/home/cshapiro/go/src/pkg/runtime/mgc0.c:2050
#4 0x45258b runtime.mcall /usr/local/google/home/cshapiro/go/src/pkg/runtime/asm_amd64.s:179
MProf_Free(p=0xc2100210a0, size=0x50)
#0 0x46ee15 runtime.mallocgc /usr/local/google/home/cshapiro/go/src/pkg/runtime/malloc.goc:141
#1 0x47004f runtime.settype_flush /usr/local/google/home/cshapiro/go/src/pkg/runtime/malloc.goc:612
#2 0x45f92c gc /usr/local/google/home/cshapiro/go/src/pkg/runtime/mgc0.c:2071
#3 0x45f89e mgc /usr/local/google/home/cshapiro/go/src/pkg/runtime/mgc0.c:2050
#4 0x45258b runtime.mcall /usr/local/google/home/cshapiro/go/src/pkg/runtime/asm_amd64.s:179
R=golang-dev, dvyukov, rsc, cshapiro
CC=golang-dev
https://golang.org/cl/21990045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/48279bd567cc4d1efca9f69e713469fc6659eb26
元コミット内容
このコミットは、Goランタイムに新しいデバッグ機能を追加します。具体的には、GODEBUG=allocfreetrace=1
という環境変数を設定することで、プログラム実行中に発生するすべてのメモリ割り当て(allocation)と解放(free)イベントに対して、詳細な情報とスタックトレースを出力する機能が導入されます。これにより、ガベージコレクション(GC)の動作をより深く理解し、メモリ関連のデバッグを効率的に行うことが可能になります。コミットメッセージには、MProf_Malloc
(割り当て)とMProf_Free
(解放)の出力例が示されており、それぞれにオブジェクトのアドレス、サイズ、型、そしてそのイベントが発生した時点のスタックトレースが含まれています。
変更の背景
Go言語は、その効率的なガベージコレクションによってメモリ管理を自動化していますが、複雑なアプリケーションでは、意図しないメモリ割り当てや解放のパターンがパフォーマンスの問題やメモリリークの原因となることがあります。既存のメモリプロファイリングツール(go tool pprof
など)は、メモリ使用量のスナップショットや累積的な割り当て情報を分析するのに優れていますが、個々の割り当てや解放がプログラムのどのコードパスで発生したかをリアルタイムで追跡する機能は限定的でした。
このコミットは、特にGCのデバッグや、特定のオブジェクトがいつ、どこで割り当てられ、いつ解放されたのかを詳細に知りたいというニーズに応えるために導入されました。これにより、開発者はメモリのライフサイクルをより細かく追跡し、メモリ使用量の最適化やリークの特定をより容易に行えるようになります。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGoランタイムとメモリ管理に関する基本的な知識が必要です。
- Goランタイム: Goプログラムの実行を管理するシステム。スケジューラ、ガベージコレクタ、メモリ割り当て器などが含まれます。
- ガベージコレクション (GC): Goランタイムの重要なコンポーネントで、不要になったメモリを自動的に回収し、再利用可能にするプロセスです。GoのGCは並行・世代別GCを採用しており、プログラムの実行と並行して動作します。
- メモリ割り当て (Allocation): プログラムが新しいデータ構造やオブジェクトを格納するために、ヒープメモリから領域を確保する操作です。Goでは
make
やnew
、あるいは複合リテラルなどによって暗黙的にメモリ割り当てが行われます。 - メモリ解放 (Free): 不要になったメモリ領域をシステムに返却する操作です。GoではGCが自動的にこれを行います。
- スタックトレース (Stack Trace): プログラムの実行中に特定のイベント(ここではメモリ割り当て/解放)が発生した時点での、関数呼び出しの履歴です。どの関数がどの関数を呼び出し、最終的にそのイベントに至ったかを示します。デバッグにおいて、問題の発生源を特定するのに非常に役立ちます。
GODEBUG
環境変数: Goランタイムのデバッグオプションを設定するための環境変数です。GODEBUG=name=value
の形式で複数のオプションを指定できます。例えば、GODEBUG=gctrace=1
はGCのトレース情報を出力します。runtime
パッケージ: Goの標準ライブラリの一部で、Goランタイムとの低レベルなインタラクションを提供します。メモリ管理、ゴルーチン管理、システムコールなどが含まれます。mprof
(Memory Profiling): Goランタイムが提供するメモリプロファイリング機能の一部です。通常は累積的なメモリ使用量や割り当てサイトの情報を収集しますが、このコミットではその機能を拡張して、個々の割り当て/解放イベントのトレースを可能にしています。
技術的詳細
このコミットの主要な技術的変更点は、Goランタイムのメモリ割り当てとプロファイリングのメカニズムに、詳細なトレース機能を追加したことです。
-
allocfreetrace
オプションの導入:src/pkg/runtime/runtime.h
のDebugVars
構造体にallocfreetrace
という新しいフィールド(int32
型)が追加されました。src/pkg/runtime/runtime.c
のdbgvar
配列に{"allocfreetrace", &runtime·debug.allocfreetrace}
が追加され、GODEBUG=allocfreetrace=1
としてこのオプションを有効にできるようになりました。
-
runtime·MProf_Malloc
関数の拡張:src/pkg/runtime/malloc.h
で、runtime·MProf_Malloc
関数のシグネチャが変更され、新たにuintptr typ
引数を受け取るようになりました。このtyp
は、割り当てられたオブジェクトの型情報(ポインタ)と、そのオブジェクトが単一のオブジェクト、配列、チャネルのいずれであるかを示す情報(下位ビット)を含みます。src/pkg/runtime/malloc.goc
のruntime·mallocgc
関数内で、allocfreetrace
が有効な場合、runtime·MProf_Malloc
が常に呼び出されるように変更されました。これにより、MemProfileRate
(メモリプロファイリングのサンプリングレート)の設定に関わらず、すべての割り当てがトレース対象となります。src/pkg/runtime/mprof.goc
のruntime·MProf_Malloc
の実装が大幅に拡張されました。typeinfoname
というヘルパー関数が追加され、typ
引数から抽出された型情報(単一オブジェクト、配列、チャネル)を可読な文字列に変換します。printstackframes
という新しいヘルパー関数が追加され、与えられたスタックトレースを整形して出力します。これにより、ファイル名、行番号、関数名を含む詳細な呼び出し履歴が表示されます。allocfreetrace
が有効な場合、MProf_Malloc
は割り当てられたオブジェクトのアドレス、サイズ、型情報(typeinfoname
と実際のGoの型名を含む)、そしてprintstackframes
を使って取得した割り当て時のスタックトレースを出力します。
-
runtime·MProf_Free
関数の拡張:src/pkg/runtime/mprof.goc
のruntime·MProf_Free
関数も拡張されました。allocfreetrace
が有効な場合、解放されるオブジェクトのアドレスとサイズ、そしてそのオブジェクトが割り当てられた時点のスタックトレース(b->stk
,b->nstk
に保存されているもの)を出力します。これにより、割り当てと解放のペアを追跡し、メモリリークの原因を特定するのに役立ちます。
これらの変更により、Goランタイムは、メモリ割り当てと解放のイベントを非常に詳細に、かつリアルタイムで報告できるようになり、GCのデバッグやメモリ使用量の最適化のための強力なツールが提供されました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
-
src/pkg/runtime/extern.go
:GODEBUG
環境変数に関するドキュメントにallocfreetrace
オプションの説明が追加されました。
--- a/src/pkg/runtime/extern.go +++ b/src/pkg/runtime/extern.go @@ -36,6 +36,9 @@ a comma-separated list of name=val pairs. Supported names are: detailed multiline info every X milliseconds, describing state of the scheduler, processors, threads and goroutines. + allocfreetrace: setting allocfreetrace=1 causes every allocation to be + profiled and a stack trace printed on each object's allocation and free. + The GOMAXPROCS variable limits the number of operating system threads that can execute user-level Go code simultaneously. There is no limit to the number of threads that can be blocked in system calls on behalf of Go code; those do not count against
-
src/pkg/runtime/malloc.goc
:runtime·mallocgc
関数内で、allocfreetrace
が有効な場合にプロファイリングセクションへジャンプするロジックが追加され、runtime·MProf_Malloc
にtyp
引数が渡されるようになりました。
--- a/src/pkg/runtime/malloc.goc +++ b/src/pkg/runtime/malloc.goc @@ -122,6 +122,9 @@ runtime·mallocgc(uintptr size, uintptr typ, uint32 flag) if(m->locks == 0 && g->preempt) // restore the preemption request in case we've cleared it in newstack g->stackguard0 = StackPreempt; + if(runtime·debug.allocfreetrace) + goto profile; + if(!(flag & FlagNoProfiling) && (rate = runtime·MemProfileRate) > 0) { if(size >= rate) goto profile; @@ -135,7 +138,7 @@ runtime·mallocgc(uintptr size, uintptr typ, uint32 flag) profile: runtime·setblockspecial(v, true); - runtime·MProf_Malloc(v, size); + runtime·MProf_Malloc(v, size, typ); } }
-
src/pkg/runtime/malloc.h
:runtime·MProf_Malloc
関数のプロトタイプが更新され、typ
引数が追加されました。
--- a/src/pkg/runtime/malloc.h +++ b/src/pkg/runtime/malloc.h @@ -476,7 +476,7 @@ enum FlagNoInvokeGC = 1<<4, // don't invoke GC }; -void runtime·MProf_Malloc(void*, uintptr); +void runtime·MProf_Malloc(void*, uintptr, uintptr); void runtime·MProf_Free(void*, uintptr); void runtime·MProf_GC(void); int32 runtime·gcprocs(void);
-
src/pkg/runtime/mprof.goc
:typeinfoname
とprintstackframes
の新しいヘルパー関数が追加されました。runtime·MProf_Malloc
とruntime·MProf_Free
がallocfreetrace
が有効な場合に詳細なトレース情報を出力するように変更されました。
--- a/src/pkg/runtime/mprof.goc +++ b/src/pkg/runtime/mprof.goc @@ -247,16 +247,63 @@ found: return nil; } +static int8* +typeinfoname(int32 typeinfo) +{ + if(typeinfo == TypeInfo_SingleObject) + return "single object"; + else if(typeinfo == TypeInfo_Array) + return "array"; + else if(typeinfo == TypeInfo_Chan) + return "channel"; + runtime·throw("typinfoname: unknown type info"); + return nil; +} + +static void +printstackframes(uintptr *stk, int32 nstk) +{ + String file; + Func *f; + int8 *name; + uintptr pc; + int32 frame; + int32 line; + + for(frame = 0; frame < nstk; frame++) { + pc = stk[frame]; + f = runtime·findfunc(pc); + if(f != nil) { + name = runtime·funcname(f); + line = runtime·funcline(f, pc, &file); + runtime·printf("\t#%d %p %s %S:%d\n", frame, pc, name, file, line); + } else { + runtime·printf("\t#%d %p\n", frame, pc); + } + } +} + // Called by malloc to record a profiled block. void -runtime·MProf_Malloc(void *p, uintptr size) +runtime·MProf_Malloc(void *p, uintptr size, uintptr typ) { - int32 nstk; uintptr stk[32]; Bucket *b; + Type *type; + int8 *name; + int32 nstk; nstk = runtime·callers(1, stk, 32); runtime·lock(&proflock); + 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(")>\n"); + printstackframes(stk, nstk); + } b = stkbucket(MProf, stk, nstk, true); b->recent_allocs++; b->recent_alloc_bytes += size; @@ -275,6 +322,10 @@ runtime·MProf_Free(void *p, uintptr size) if(b != nil) { b->recent_frees++; b->recent_free_bytes += size; + if(runtime·debug.allocfreetrace) { + runtime·printf("MProf_Free(p=%p, size=%p)\n", p, size); + printstackframes(b->stk, b->nstk); + } } runtime·unlock(&proflock); }
-
src/pkg/runtime/runtime.c
:dbgvar
配列にallocfreetrace
が追加され、GODEBUG
経由で設定可能になりました。
--- a/src/pkg/runtime/runtime.c +++ b/src/pkg/runtime/runtime.c @@ -387,9 +387,10 @@ static struct { int8* name; int32* value; } dbgvar[] = { + {"allocfreetrace", &runtime·debug.allocfreetrace}, {"gctrace", &runtime·debug.gctrace}, - {"schedtrace", &runtime·debug.schedtrace}, {"scheddetail", &runtime·debug.scheddetail}, + {"schedtrace", &runtime·debug.schedtrace}, }; void
-
src/pkg/runtime/runtime.h
:DebugVars
構造体にallocfreetrace
フィールドが追加されました。
--- a/src/pkg/runtime/runtime.h +++ b/src/pkg/runtime/runtime.h @@ -533,9 +533,10 @@ struct CgoMal // Holds variables parsed from GODEBUG env var. struct DebugVars { + int32 allocfreetrace; int32 gctrace; - int32 schedtrace; int32 scheddetail; + int32 schedtrace; }; extern bool runtime·precisestack;
コアとなるコードの解説
このコミットの核となる変更は、Goランタイムのメモリ割り当てと解放のフックに、詳細なデバッグ情報を出力するロジックを追加した点です。
-
GODEBUG=allocfreetrace=1
による有効化:src/pkg/runtime/runtime.h
のDebugVars
構造体とsrc/pkg/runtime/runtime.c
のdbgvar
配列への追加により、ユーザーはGODEBUG
環境変数を通じてこの新しいトレース機能を有効にできます。これにより、Goプログラムの実行時に、ランタイムレベルでのメモリイベントの可視性が向上します。
-
runtime·mallocgc
の変更:src/pkg/runtime/malloc.goc
内のruntime·mallocgc
関数は、Goプログラムがメモリを割り当てる際の主要なエントリポイントです。この関数にif(runtime·debug.allocfreetrace) goto profile;
という行が追加されたことで、allocfreetrace
が有効な場合、通常のメモリプロファイリングのサンプリングレート(MemProfileRate
)に関わらず、すべての割り当てが強制的にプロファイリングパスを通るようになります。これは、個々の割り当てイベントを漏らさず追跡するために重要です。- また、
runtime·MProf_Malloc
の呼び出しにtyp
引数が追加されました。このtyp
は、割り当てられたオブジェクトのGoの型情報を含んでおり、これによりトレース出力がより詳細になります。
-
src/pkg/runtime/mprof.goc
におけるトレースロジック:typeinfoname
関数: この小さなヘルパー関数は、typ
引数から抽出された型情報(TypeInfo_SingleObject
,TypeInfo_Array
,TypeInfo_Chan
)を、それぞれ「single object」「array」「channel」といった人間が読める文字列に変換します。これにより、出力されるトレース情報がより分かりやすくなります。printstackframes
関数: この関数は、与えられたスタックトレース(stk
とnstk
)を解析し、各フレームのPC(プログラムカウンタ)、関数名、ファイル名、行番号を整形して出力します。これは、メモリ割り当てや解放がプログラムのどの部分で発生したかを正確に特定するために不可欠です。runtime·MProf_Malloc
の拡張:- この関数は、メモリ割り当て時に呼び出されます。
allocfreetrace
が有効な場合、割り当てられたオブジェクトのポインタ、サイズ、そしてtypeinfoname
と実際のGoの型名(*type->string
)を使って詳細な型情報を出力します。 - 最も重要なのは、
printstackframes(stk, nstk);
の呼び出しです。これにより、runtime·callers(1, stk, 32)
で取得された割り当て時のスタックトレースが即座に出力されます。
- この関数は、メモリ割り当て時に呼び出されます。
runtime·MProf_Free
の拡張:- この関数は、メモリ解放時(GCによるスイープなど)に呼び出されます。
allocfreetrace
が有効な場合、解放されるオブジェクトのポインタとサイズを出力します。 - 注目すべきは、
printstackframes(b->stk, b->nstk);
の呼び出しです。ここで使用されるスタックトレース(b->stk
)は、そのオブジェクトが割り当てられた時点で記録されたものです。これにより、あるメモリ領域が解放されたときに、それが元々どのコードパスで割り当てられたものなのかを追跡することが可能になり、メモリリークのデバッグにおいて非常に強力な手がかりとなります。
- この関数は、メモリ解放時(GCによるスイープなど)に呼び出されます。
これらの変更は、Goランタイムの内部動作を深く掘り下げ、メモリ管理のデバッグ能力を大幅に向上させるものです。特に、割り当て時と解放時の両方でスタックトレースを提供することで、メモリのライフサイクル全体を可視化し、複雑なメモリ関連の問題を診断するのに役立ちます。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Goのメモリ管理に関するブログ記事やドキュメント(一般的な情報源)
- Goのガベージコレクションに関する詳細な解説(一般的な情報源)
参考にした情報源リンク
- Goのコミット履歴: https://github.com/golang/go/commits/master
- GoのGerritコードレビューシステム: https://go-review.googlesource.com/
GODEBUG
環境変数に関するGoのドキュメント(もしあれば)- Goのメモリプロファイリングに関するドキュメント(
go tool pprof
など)