[インデックス 19249] ファイルの概要
このコミットは、Goランタイムのヒープダンプ機能に関連する変更です。具体的には、src/pkg/runtime/heapdump.c
ファイルが修正されています。このファイルは、Goプログラムのメモリヒープの状態をダンプ(出力)するための内部的なロジックを含んでいます。ヒープダンプは、メモリリークのデバッグやメモリ使用量の分析において非常に重要なツールです。
コミット
- Author: Keith Randall khr@golang.org
- Date: Mon Apr 28 12:45:00 2014 -0400
- Commit Message: runtime: heapdump - make sure spans are swept before dumping.
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/573cfe95615faac43b9fc0841f13b74640584305
元コミット内容
runtime: heapdump - make sure spans are swept before dumping.
LGTM=rsc
R=golang-codereviews, adonovan, rsc
CC=golang-codereviews
https://golang.org/cl/90440043
変更の背景
Goのガベージコレクション(GC)は、メモリを解放するために「スイープ(sweep)」というフェーズを含んでいます。スイープフェーズでは、GCによって不要とマークされたメモリ領域(スパン)が再利用可能な状態にクリーンアップされます。
ヒープダンプは、特定の時点でのGoプログラムのメモリヒープのスナップショットを取得する機能です。このスナップショットは、デバッグやプロファイリングに利用されます。しかし、ヒープダンプが実行される際に、GCのスイープフェーズがまだ完了していないメモリ領域(unswept spans)が存在する可能性があります。
もし、ヒープダンプがunswept spansを処理しようとすると、そのスパン内のオブジェクトの状態が不整合であったり、メモリのメタデータが最新でなかったりする可能性があります。これにより、生成されるヒープダンプが不正確になったり、最悪の場合、ダンプ処理中にクラッシュする可能性も考えられます。
このコミットの目的は、ヒープダンプを実行する前に、すべての関連するメモリ領域(スパン)が確実にスイープされていることを保証することです。これにより、ヒープダンプの正確性と堅牢性が向上します。
前提知識の解説
Goのメモリ管理とヒープ
Goランタイムは独自のメモリマネージャを持っています。プログラムがオブジェクトを割り当てると、それらはヒープと呼ばれるメモリ領域に配置されます。ヒープは、mheap
という構造体によって管理され、さらに小さな単位であるMSpan
に分割されます。
mheap
: Goランタイム全体のヒープを管理するグローバルな構造体。MSpan
: 連続したページ(通常は8KB)の集合を表す構造体。MSpan
は、特定のサイズのオブジェクトを格納するために使用されます。例えば、小さなオブジェクト用のスパン、大きなオブジェクト用のスパンなどがあります。MSpan
には、その状態(使用中、解放済み、スイープ中など)を示すstate
フィールドがあります。
Goのガベージコレクション(GC)
GoのGCは、主に以下のフェーズで動作します。
- マークフェーズ (Mark Phase): プログラムの実行を一時停止し、到達可能な(使用中の)オブジェクトを特定してマークします。
- マーク終了フェーズ (Mark Termination Phase): マークフェーズの終了を処理します。
- スイープフェーズ (Sweep Phase): マークされなかった(到達不可能な、つまり不要な)オブジェクトが占めるメモリ領域を解放し、再利用可能な状態にします。このフェーズは、プログラムの実行と並行して行われることがあります(並行スイープ)。
MSpan_EnsureSwept
関数
runtime·MSpan_EnsureSwept(s)
は、特定のMSpan
s
が完全にスイープされていることを保証する関数です。もしs
がまだスイープされていない状態であれば、この関数がスイープ処理を完了させます。これは、GCが並行して動作している場合に、あるスパンの正確な状態が必要になったときに呼び出されます。
ヒープダンプの目的とプロセス
ヒープダンプは、Goプログラムの実行中に、その時点でのヒープメモリの内容をファイルに書き出すプロセスです。これは、主に以下のような目的で使用されます。
- メモリリークの特定: 時間とともにヒープダンプを比較することで、解放されるべきメモリが解放されていない(リークしている)場所を特定できます。
- メモリ使用量の分析: どのオブジェクトがどれくらいのメモリを消費しているかを詳細に分析できます。
- デバッグ: プログラムの特定の状態でのメモリレイアウトを理解するのに役立ちます。
ヒープダンプのプロセスは、通常、ヒープ内のすべてのMSpan
を走査し、それぞれのスパン内のオブジェクト情報をシリアライズして出力することを含みます。
技術的詳細
このコミットの核心的な変更は、ヒープダンプの開始時に、すべての使用中のMSpan
が確実にスイープされているようにした点です。
変更前は、dumpobjs
関数(ヒープ内のオブジェクトをダンプする役割を持つ)の内部で、個々のMSpan
を処理する直前にruntime·MSpan_EnsureSwept(s)
が呼び出されていました。これは、ダンプ中にスパンがunsweptである場合に、その場でスイープを完了させるアプローチでした。
しかし、このアプローチには潜在的な問題がありました。ヒープダンプは、ヒープ全体の整合性のあるスナップショットを必要とします。もしdumpobjs
がスパンを一つずつスイープしながらダンプを進める場合、ダンプの途中でGCが別のスパンをスイープし始めたり、あるいはダンプの開始時点ではunsweptだったが、ダンプ中にGCによってスイープが完了するスパンがあったりすると、ヒープダンプ全体としての整合性が損なわれる可能性がありました。
このコミットでは、mdump
関数(ヒープダンプの全体的なオーケストレーションを行う関数)の冒頭に、すべての使用中のMSpan
を事前にスイープするループが追加されました。
// make sure we're done sweeping
for(i = 0; i < runtime·mheap.nspan; i++) {
s = runtime·mheap.allspans[i];
if(s->state == MSpanInUse)
runtime·MSpan_EnsureSwept(s);
}
この変更により、dumpobjs
が呼び出される前に、ヒープ内のすべてのMSpanInUse
状態のスパンが完全にスイープされた状態になります。これにより、ヒープダンプが開始される時点でのヒープの状態がより一貫性のあるものとなり、ダンプの正確性と信頼性が向上します。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/heapdump.c
+++ b/src/pkg/runtime/heapdump.c
@@ -567,7 +567,6 @@ dumpobjs(void)\
s = runtime·mheap.allspans[i];
if(s->state != MSpanInUse)
continue;
- runtime·MSpan_EnsureSwept(s);\
p = (byte*)(s->start << PageShift);
size = s->elemsize;
n = (s->npages << PageShift) / size;
@@ -694,6 +693,15 @@ static void
mdump(G *gp)
{
byte *hdr;
+ uintptr i;
+ MSpan *s;
+
+ // make sure we're done sweeping
+ for(i = 0; i < runtime·mheap.nspan; i++) {
+ s = runtime·mheap.allspans[i];
+ if(s->state == MSpanInUse)
+ runtime·MSpan_EnsureSwept(s);
+ }
runtime·memclr((byte*)&typecache[0], sizeof(typecache));
hdr = (byte*)"go1.3 heap dump\\n";
コアとなるコードの解説
-
dumpobjs
からのruntime·MSpan_EnsureSwept(s)
の削除: 変更前は、dumpobjs
関数内で各MSpan
を処理する直前にruntime·MSpan_EnsureSwept(s)
が呼び出されていました。これは、個々のスパンが必要になったときにその場でスイープを完了させるアプローチでした。この行が削除されたのは、スイープ処理がより上位の関数(mdump
)で一括して行われるようになったためです。 -
mdump
関数へのスイープ処理の追加:mdump
関数はヒープダンプのメインエントリポイントです。この関数の冒頭に、以下のコードブロックが追加されました。+ uintptr i; + MSpan *s; + + // make sure we're done sweeping + for(i = 0; i < runtime·mheap.nspan; i++) { + s = runtime·mheap.allspans[i]; + if(s->state == MSpanInUse) + runtime·MSpan_EnsureSwept(s); + }
uintptr i; MSpan *s;
: ループ変数としてi
(インデックス)とs
(MSpan
ポインタ)が宣言されています。for(i = 0; i < runtime·mheap.nspan; i++)
:runtime·mheap.nspan
は、現在ヒープに存在するMSpan
の総数を表します。このループは、ヒープ内のすべてのMSpan
を走査します。s = runtime·mheap.allspans[i];
:runtime·mheap.allspans
は、すべてのMSpan
へのポインタを格納する配列です。現在のインデックスi
に対応するMSpan
を取得します。if(s->state == MSpanInUse)
: 現在のスパンs
がMSpanInUse
(使用中)状態であるかどうかをチェックします。ヒープダンプの対象となるのは、現在使用されているメモリ領域であるため、この状態のスパンのみが対象となります。runtime·MSpan_EnsureSwept(s);
: もしスパンが使用中であれば、この関数を呼び出して、そのスパンが完全にスイープされていることを保証します。これにより、ヒープダンプが開始される前に、すべての関連するメモリ領域がクリーンで一貫性のある状態になります。
この変更により、ヒープダンプの整合性が大幅に向上し、デバッグやプロファイリングの信頼性が高まります。
関連リンク
- Go Change-Id:
90440043
- https://golang.org/cl/90440043
参考にした情報源リンク
- Go言語のソースコード (
src/pkg/runtime/heapdump.c
) - Go言語のガベージコレクションに関する一般的な知識
- Go言語のメモリ管理に関する一般的な知識
MSpan
とmheap
に関するGoランタイムのドキュメントや解説記事 (Web検索による一般的な情報)- Goのヒープダンプに関するドキュメントや解説記事 (Web検索による一般的な情報)