[インデックス 15283] ファイルの概要
このコミットは、Goランタイムのメモリプロファイリング機能に関するもので、特にGOGC=off
(ガベージコレクション無効化)の状況下でもメモリプロファイルが正しく機能するように修正を加えています。変更はsrc/pkg/runtime/mprof.goc
ファイルに集中しており、メモリプロファイル統計の更新ロジックが調整されています。
コミット
commit 0c3b17a55aebb2f9ea481269b67a2a0099674101
Author: Russ Cox <rsc@golang.org>
Date: Fri Feb 15 14:48:58 2013 -0500
runtime: allow mem profiles with GOGC=off
Fixes #3586.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/7304098
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0c3b17a55aebb2f9ea481269b67a2a0099674101
元コミット内容
このコミットは、Goランタイムにおいて、GOGC=off
という設定でガベージコレクション(GC)が無効化されている場合でも、メモリプロファイルが正しく機能するように修正を行うものです。具体的には、メモリプロファイルの統計情報がGCの発生に依存しているため、GCが行われない環境では統計が更新されないという問題(Issue #3586)を解決します。
変更の背景
Goのメモリプロファイラは、プログラムのメモリ使用状況を分析するための強力なツールです。しかし、従来のGoランタイムでは、メモリプロファイルの統計情報(例えば、各スタックトレースからのアロケーション数や解放数)は、ガベージコレクション(GC)が実行されるたびに更新される設計になっていました。
ここで問題となるのが、GOGC=off
という環境変数を設定してGCを完全に無効化した場合です。GCが無効化されると、メモリプロファイルの統計情報が一切更新されなくなり、結果としてMemProfile
関数を呼び出しても常に空のプロファイルが返されるという不具合が発生していました。これは、特にGCのオーバーヘッドを避けたい、あるいは特定のメモリリークをデバッグしたいといったシナリオで、メモリプロファイラが利用できないという大きな制約となっていました。
このコミットは、この問題を解決し、GCの有無にかかわらずメモリプロファイラが常に正確な情報を提供できるようにすることを目的としています。具体的には、Issue #3586で報告された問題に対応しています。
前提知識の解説
Goのガベージコレクション (GC)
Go言語は、自動メモリ管理のためにガベージコレクタ(GC)を内蔵しています。開発者は手動でメモリを解放する必要がなく、GCが不要になったメモリ領域を自動的に回収します。
GOGC
環境変数:GOGC
はGoランタイムのガベージコレクタの動作を制御する環境変数です。デフォルト値は100
で、これは前回のGC後にヒープサイズが100%増加したら次のGCを実行するという意味です。GOGC=off
またはGOGC=0
: この設定はガベージコレクションを完全に無効化します。これにより、GCによる一時停止(STW: Stop The World)を避けることができますが、メモリは解放されず、プログラムの実行中にメモリ使用量が際限なく増加する可能性があります。これは、特定のベンチマークや、メモリ管理をアプリケーション層で厳密に制御したい場合にのみ使用される特殊な設定です。
Goのメモリプロファイリング
Goのメモリプロファイラは、プログラムがどこでどれだけのメモリを割り当てているかを詳細に分析するためのツールです。runtime/pprof
パッケージを通じて利用でき、主に以下の情報を提供します。
- アロケーションプロファイル: どのコードパスがメモリを割り当てているか。
- インユースプロファイル: 現在使用中のメモリがどのコードパスによって割り当てられたものか。
これらのプロファイルは、通常、GCが実行される際に収集・更新される統計情報に基づいています。Goランタイム内部では、runtime.mprof.goc
のようなファイルでメモリプロファイル関連の統計(mbuckets
など)が管理されており、アロケーションや解放のたびに一時的な統計(recent_allocs
, recent_frees
など)が記録されます。そして、GCが実行されると、これらの「最近の」統計が「累積の」統計に統合され、プロファイルデータが更新されます。
mprof.goc
ファイル
src/pkg/runtime/mprof.goc
は、Goランタイムにおけるメモリプロファイリングの低レベルな実装を含むファイルです。このファイルには、メモリプロファイルのバケット(Bucket
構造体)の管理、アロケーションと解放の追跡、そしてプロファイルデータの収集と集計に関する関数が含まれています。
技術的詳細
このコミットの核心は、MemProfile
関数が呼び出された際に、GCが実行されていない場合でもメモリプロファイルの統計情報を強制的に更新するロジックを追加することです。
-
MProf_GC
関数の分離:- 元の
runtime·MProf_GC
関数は、proflock
というグローバルなロックを取得・解放してから、メモリプロファイルの統計を更新する処理を行っていました。 - このコミットでは、ロックの取得・解放を含まない純粋な統計更新ロジックを
static void MProf_GC(void)
という新しい静的関数として分離しました。 - 元の
runtime·MProf_GC
関数は、この新しいMProf_GC
を呼び出す形に変更され、外部からの呼び出し時には引き続きロックが適切に管理されるようにしています。この分離により、内部的にロックなしで統計更新ロジックを再利用できるようになります。
- 元の
-
MemProfile
関数での統計更新のトリガー:MemProfile
関数は、メモリプロファイルデータを収集してユーザーに返す関数です。- この関数内で、
clear
という新しいブーリアン変数が導入されます。これは、現在のメモリプロファイルのバケット(mbuckets
)に「最近の」アロケーションや解放の統計が全く記録されていないかどうかを判断するために使用されます。 MemProfile
が呼び出された時点で、もしmbuckets
内のどのバケットにもallocs
(累積アロケーション数)やfrees
(累積解放数)が記録されておらず、かつclear
がtrue
である場合(これはGCが一度も実行されていないことを強く示唆します)、MProf_GC()
関数が明示的に呼び出されます。- この
MProf_GC()
の呼び出しにより、GCが実行されていなくても、それまでに発生した「最近の」アロケーションと解放の統計が累積統計に統合され、メモリプロファイルデータが更新されます。 - 統計が更新された後、
MemProfile
は再度バケットを走査し、更新された統計に基づいてプロファイルデータを生成します。
この変更により、GOGC=off
でGCが全く実行されない環境でも、MemProfile
が呼び出された時点で最新のメモリプロファイル統計が強制的に計算されるようになり、常に正確なプロファイルデータが提供されるようになります。
コアとなるコードの変更箇所
src/pkg/runtime/mprof.goc
ファイルの以下の部分が変更されています。
--- a/src/pkg/runtime/mprof.goc
+++ b/src/pkg/runtime/mprof.goc
@@ -140,13 +140,11 @@ stkbucket(int32 typ, uintptr *stk, int32 nstk, bool alloc)\n return b;\n }\n \n-// Record that a gc just happened: all the 'recent' statistics are now real.\n-void\n-runtime·MProf_GC(void)\n+static void\n+MProf_GC(void)\n {\n Bucket *b;\n-\t\n-\truntime·lock(&proflock);\
+\n for(b=mbuckets; b; b=b->allnext) {\n b->allocs += b->recent_allocs;\n b->frees += b->recent_frees;\
@@ -157,6 +155,14 @@ runtime·MProf_GC(void)\n b->recent_alloc_bytes = 0;\n b->recent_free_bytes = 0;\n }\n+}\n+\n+// Record that a gc just happened: all the 'recent' statistics are now real.\n+void\n+runtime·MProf_GC(void)\n+{\n+\truntime·lock(&proflock);\
+\tMProf_GC();\
\truntime·unlock(&proflock);\
}\n \n@@ -370,12 +376,28 @@ record(Record *r, Bucket *b)\n func MemProfile(p Slice, include_inuse_zero bool) (n int, ok bool) {\n Bucket *b;\n Record *r;\n+\tbool clear;\
\n runtime·lock(&proflock);\
n = 0;\
-\tfor(b=mbuckets; b; b=b->allnext)\
+\tclear = true;\
+\tfor(b=mbuckets; b; b=b->allnext) {\
\t\tif(include_inuse_zero || b->alloc_bytes != b->free_bytes)\
\t\t\tn++;\
+\t\tif(b->allocs != 0 || b->frees != 0)\
+\t\t\tclear = false;\
+\t}\n+\tif(clear) {\
+\t\t// Absolutely no data, suggesting that a garbage collection\n+\t\t// has not yet happened. In order to allow profiling when\n+\t\t// garbage collection is disabled from the beginning of execution,\n+\t\t// accumulate stats as if a GC just happened, and recount buckets.\n+\t\tMProf_GC();\
+\t\tn = 0;\
+\t\tfor(b=mbuckets; b; b=b->allnext)\
+\t\t\tif(include_inuse_zero || b->alloc_bytes != b->free_bytes)\
+\t\t\t\tn++;\
+\t}\
\tok = false;\
\tif(n <= p.len) {\n \t\tok = true;\
コアとなるコードの解説
-
MProf_GC
関数の分離と再定義:- 元の
runtime·MProf_GC
関数は、メモリプロファイルの統計(recent_allocs
,recent_frees
などをallocs
,frees
に加算し、recent_
統計をリセットする)を更新する役割を担っていました。この関数はproflock
というグローバルなロックを内部で取得・解放していました。 - 変更後、この統計更新のロジック自体は
static void MProf_GC(void)
という新しい静的関数に抽出されました。この静的関数はロックを扱いません。 - 元の
runtime·MProf_GC
関数は、単にruntime·lock(&proflock);
でロックを取得し、新しいMProf_GC()
を呼び出し、runtime·unlock(&proflock);
でロックを解放するラッパー関数となりました。これにより、統計更新のコアロジックがロック機構から分離され、他の場所からロックなしで呼び出すことが可能になりました。
- 元の
-
MemProfile
関数におけるGOGC=off
対応:MemProfile
関数は、現在のメモリプロファイルデータを取得するためのGoの公開APIです。- 関数冒頭で
runtime·lock(&proflock);
によりロックを取得します。 clear = true;
という新しい変数が導入されます。これは、メモリプロファイルのバケット(mbuckets
)に有効な統計データがまだ存在しない(つまり、GCが一度も実行されていない)可能性を検出するためのフラグです。for
ループでmbuckets
を走査し、各バケットのallocs
またはfrees
が0
でない場合(つまり、何らかのアロケーションや解放が記録されている場合)、clear
フラグをfalse
に設定します。- ループ終了後、
if(clear)
の条件が評価されます。もしclear
がtrue
のままであれば、それは「ガベージコレクションがまだ発生していないことを示唆する、全くデータがない状態」と判断されます。これは特にGOGC=off
でプログラムが起動された場合に発生します。 - この
clear
がtrue
の場合、コメントにもあるように「実行開始時からガベージコレクションが無効になっている場合にプロファイリングを許可するため」、MProf_GC()
が明示的に呼び出されます。これにより、GCが実行されていなくても、それまでに発生した「最近の」アロケーションと解放の統計が累積統計に統合されます。 MProf_GC()
が呼び出された後、n
(プロファイルエントリの数)が再計算されます。これは、MProf_GC()
によって統計が更新されたため、プロファイルエントリの数が変わる可能性があるためです。- 最後に
runtime·unlock(&proflock);
でロックを解放します。
この一連の変更により、MemProfile
が呼び出された際に、GCが実行されていない状況でも、それまでのメモリ使用状況がプロファイルに反映されるようになり、GOGC=off
環境下でのメモリプロファイリングが可能になりました。
関連リンク
- Go Issue #3586: https://github.com/golang/go/issues/3586
- Go CL 7304098: https://golang.org/cl/7304098
参考にした情報源リンク
- Go言語の公式ドキュメント (runtime/pprof): https://pkg.go.dev/runtime/pprof
- Go言語のガベージコレクションに関するドキュメントやブログ記事 (例: GoのGCの仕組み、GOGC環境変数など)
- Goのソースコード (特に
src/runtime/mprof.go
やsrc/runtime/mprof.goc
の周辺コード)