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

[インデックス 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ではmakenew、あるいは複合リテラルなどによって暗黙的にメモリ割り当てが行われます。
  • メモリ解放 (Free): 不要になったメモリ領域をシステムに返却する操作です。GoではGCが自動的にこれを行います。
  • スタックトレース (Stack Trace): プログラムの実行中に特定のイベント(ここではメモリ割り当て/解放)が発生した時点での、関数呼び出しの履歴です。どの関数がどの関数を呼び出し、最終的にそのイベントに至ったかを示します。デバッグにおいて、問題の発生源を特定するのに非常に役立ちます。
  • GODEBUG環境変数: Goランタイムのデバッグオプションを設定するための環境変数です。GODEBUG=name=valueの形式で複数のオプションを指定できます。例えば、GODEBUG=gctrace=1はGCのトレース情報を出力します。
  • runtimeパッケージ: Goの標準ライブラリの一部で、Goランタイムとの低レベルなインタラクションを提供します。メモリ管理、ゴルーチン管理、システムコールなどが含まれます。
  • mprof (Memory Profiling): Goランタイムが提供するメモリプロファイリング機能の一部です。通常は累積的なメモリ使用量や割り当てサイトの情報を収集しますが、このコミットではその機能を拡張して、個々の割り当て/解放イベントのトレースを可能にしています。

技術的詳細

このコミットの主要な技術的変更点は、Goランタイムのメモリ割り当てとプロファイリングのメカニズムに、詳細なトレース機能を追加したことです。

  1. allocfreetraceオプションの導入:

    • src/pkg/runtime/runtime.hDebugVars構造体にallocfreetraceという新しいフィールド(int32型)が追加されました。
    • src/pkg/runtime/runtime.cdbgvar配列に{"allocfreetrace", &runtime·debug.allocfreetrace}が追加され、GODEBUG=allocfreetrace=1としてこのオプションを有効にできるようになりました。
  2. runtime·MProf_Malloc関数の拡張:

    • src/pkg/runtime/malloc.hで、runtime·MProf_Malloc関数のシグネチャが変更され、新たにuintptr typ引数を受け取るようになりました。このtypは、割り当てられたオブジェクトの型情報(ポインタ)と、そのオブジェクトが単一のオブジェクト、配列、チャネルのいずれであるかを示す情報(下位ビット)を含みます。
    • src/pkg/runtime/malloc.gocruntime·mallocgc関数内で、allocfreetraceが有効な場合、runtime·MProf_Mallocが常に呼び出されるように変更されました。これにより、MemProfileRate(メモリプロファイリングのサンプリングレート)の設定に関わらず、すべての割り当てがトレース対象となります。
    • src/pkg/runtime/mprof.gocruntime·MProf_Mallocの実装が大幅に拡張されました。
      • typeinfonameというヘルパー関数が追加され、typ引数から抽出された型情報(単一オブジェクト、配列、チャネル)を可読な文字列に変換します。
      • printstackframesという新しいヘルパー関数が追加され、与えられたスタックトレースを整形して出力します。これにより、ファイル名、行番号、関数名を含む詳細な呼び出し履歴が表示されます。
      • allocfreetraceが有効な場合、MProf_Mallocは割り当てられたオブジェクトのアドレス、サイズ、型情報(typeinfonameと実際のGoの型名を含む)、そしてprintstackframesを使って取得した割り当て時のスタックトレースを出力します。
  3. runtime·MProf_Free関数の拡張:

    • src/pkg/runtime/mprof.gocruntime·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_Malloctyp引数が渡されるようになりました。
    --- 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:

    • typeinfonameprintstackframesの新しいヘルパー関数が追加されました。
    • runtime·MProf_Mallocruntime·MProf_Freeallocfreetraceが有効な場合に詳細なトレース情報を出力するように変更されました。
    --- 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ランタイムのメモリ割り当てと解放のフックに、詳細なデバッグ情報を出力するロジックを追加した点です。

  1. GODEBUG=allocfreetrace=1による有効化:

    • src/pkg/runtime/runtime.hDebugVars構造体とsrc/pkg/runtime/runtime.cdbgvar配列への追加により、ユーザーはGODEBUG環境変数を通じてこの新しいトレース機能を有効にできます。これにより、Goプログラムの実行時に、ランタイムレベルでのメモリイベントの可視性が向上します。
  2. runtime·mallocgcの変更:

    • src/pkg/runtime/malloc.goc内のruntime·mallocgc関数は、Goプログラムがメモリを割り当てる際の主要なエントリポイントです。この関数にif(runtime·debug.allocfreetrace) goto profile;という行が追加されたことで、allocfreetraceが有効な場合、通常のメモリプロファイリングのサンプリングレート(MemProfileRate)に関わらず、すべての割り当てが強制的にプロファイリングパスを通るようになります。これは、個々の割り当てイベントを漏らさず追跡するために重要です。
    • また、runtime·MProf_Mallocの呼び出しにtyp引数が追加されました。このtypは、割り当てられたオブジェクトのGoの型情報を含んでおり、これによりトレース出力がより詳細になります。
  3. src/pkg/runtime/mprof.gocにおけるトレースロジック:

    • typeinfoname関数: この小さなヘルパー関数は、typ引数から抽出された型情報(TypeInfo_SingleObject, TypeInfo_Array, TypeInfo_Chan)を、それぞれ「single object」「array」「channel」といった人間が読める文字列に変換します。これにより、出力されるトレース情報がより分かりやすくなります。
    • printstackframes関数: この関数は、与えられたスタックトレース(stknstk)を解析し、各フレームの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)は、そのオブジェクトが割り当てられた時点で記録されたものです。これにより、あるメモリ領域が解放されたときに、それが元々どのコードパスで割り当てられたものなのかを追跡することが可能になり、メモリリークのデバッグにおいて非常に強力な手がかりとなります。

これらの変更は、Goランタイムの内部動作を深く掘り下げ、メモリ管理のデバッグ能力を大幅に向上させるものです。特に、割り当て時と解放時の両方でスタックトレースを提供することで、メモリのライフサイクル全体を可視化し、複雑なメモリ関連の問題を診断するのに役立ちます。

関連リンク

  • Go言語の公式ドキュメント: https://golang.org/doc/
  • Goのメモリ管理に関するブログ記事やドキュメント(一般的な情報源)
  • Goのガベージコレクションに関する詳細な解説(一般的な情報源)

参考にした情報源リンク