[インデックス 17096] ファイルの概要
このコミットは、Goランタイムのメモリ管理、特にスカベンジャー(scavenger)のデバッグ出力に関する変更です。runtime/mheap.c
ファイルが修正され、debug.FreeOSMemory
が強制的に実行された際に、スカベンジャーの詳細な動作状況がログに出力されるようになります。これにより、メモリ解放プロセスの可視性が向上し、デバッグやパフォーマンスチューニングに役立ちます。
コミット
commit 9f46efce528eccde8f1d15756bfb8d7088da1dc6
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Thu Aug 8 21:55:04 2013 +0400
runtime: print scavenger details when forced with debug.FreeOSMemory
Fixes #5900.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/12669043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9f46efce528eccde8f1d15756bfb8d7088da1dc6
元コミット内容
このコミットの目的は、debug.FreeOSMemory
関数が呼び出された際に、Goランタイムのスカベンジャーがメモリを解放した詳細な情報を出力するようにすることです。これにより、開発者はメモリ解放の挙動をより深く理解し、デバッグや最適化に役立てることができます。
変更の背景
Goランタイムは、ガベージコレクション(GC)によって使用されなくなったメモリを自動的に解放しますが、OSへのメモリの返却(スカベンジング)は、GCとは独立して行われることがあります。debug.FreeOSMemory
は、GoプログラムがOSに未使用のメモリを強制的に返却するよう要求するための関数です。
このコミットが行われた背景には、おそらくGoプログラムが使用するメモリがOSに適切に返却されているか、あるいはどの程度のメモリが返却されているかを開発者が確認したいというニーズがあったと考えられます。特に、メモリ使用量が重要なアプリケーションや、メモリリークの疑いがある場合に、スカベンジャーの動作状況を詳細に把握することは非常に重要です。
元の実装では、debug.FreeOSMemory
が呼び出された際に、スカベンジャーがどれだけのメモリを解放したかについての詳細なログ出力が不足していました。この不足が、Issue #5900 として報告され、このコミットによって解決されました。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムの概念について理解しておく必要があります。
- ガベージコレクション (GC): GoのGCは、プログラムが参照しなくなったメモリ領域を自動的に特定し、再利用可能にするプロセスです。これにより、開発者は手動でのメモリ管理から解放されます。GoのGCは、並行かつ低遅延で動作するように設計されています。
- ヒープ (Heap): Goプログラムが動的に確保するメモリ領域です。オブジェクトやデータ構造はヒープ上に割り当てられます。
- スカベンジャー (Scavenger): Goランタイムのメモリ管理の一部で、ヒープ上のアイドル状態(未使用)のメモリページをOSに返却する役割を担います。これにより、Goプログラムが使用する物理メモリのフットプリントを削減し、システム全体のメモリ効率を向上させることができます。GCが論理的にメモリを解放するのに対し、スカベンジャーは物理的にOSにメモリを返却します。
mheap.c
: Goランタイムのメモリヒープ管理に関するC言語のソースファイルです。ヒープの割り当て、解放、スカベンジングなどの低レベルなメモリ操作が実装されています。MSpan
: Goランタイムにおけるメモリ管理の基本的な単位の一つで、連続したメモリページの集合を表します。ヒープはこれらのMSpan
のリストとして管理されます。mstats
: Goランタイムのメモリ統計情報を含む構造体です。heap_inuse
(使用中のヒープメモリ)、heap_idle
(アイドル状態のヒープメモリ)、heap_sys
(OSから取得した総メモリ)、heap_released
(OSに返却されたメモリ) などの情報が含まれます。runtime·printf
: Goランタイム内部で使用される、デバッグ目的の出力関数です。通常のfmt.Printf
とは異なり、ランタイムの低レベルな部分で利用されます。runtime·debug.gctrace
: 環境変数またはdebug.SetGCPercent
などで設定できるデバッグフラグで、GCのトレース情報を出力するかどうかを制御します。このコミットでは、このフラグが設定されている場合にスカベンジャーの詳細情報を出力するようになります。debug.FreeOSMemory()
:runtime/debug
パッケージで提供される関数で、Goランタイムに未使用のメモリをOSに返却するよう強制的に要求します。これは、特にメモリ使用量を最小限に抑えたい場合や、メモリリークのデバッグ時に役立ちます。
技術的詳細
このコミットの主要な変更点は、scavenge
関数のシグネチャと、runtime·MHeap_Scavenger
および runtime∕debug·freeOSMemory
からの scavenge
関数の呼び出し方法の変更、そしてスカベンジャーのデバッグ出力の追加です。
-
scavenge
関数のシグネチャ変更:- 変更前:
static uintptr scavenge(uint64 now, uint64 limit)
- 変更後:
static void scavenge(int32 k, uint64 now, uint64 limit)
- 戻り値の型が
uintptr
からvoid
に変更されました。これは、scavenge
関数が解放したメモリ量(sumreleased
)を直接返すのではなく、内部でログ出力を行うようになったためです。 - 新たに
int32 k
という引数が追加されました。これは、スカベンジャーの実行回数や識別子として使用され、ログ出力の際にどのスカベンジャーの実行に関する情報であるかを区別するために使われます。
- 変更前:
-
デバッグ出力の追加:
scavenge
関数内に、runtime·debug.gctrace > 0
の条件付きで詳細なログ出力が追加されました。- 解放されたメモリ量 (
sumreleased
) が0より大きい場合、scvg%d: %D MB released
という形式で解放されたメモリ量が出力されます。 - 常に、
scvg%d: inuse: %D, idle: %D, sys: %D, released: %D, consumed: %D (MB)
という形式で、現在のメモリ統計情報(使用中、アイドル、OSから取得した総量、OSに返却された量、消費量)が出力されます。 - これらの出力は、
runtime·printf
を使用して行われます。
-
runtime·MHeap_Scavenger
の変更:scavenge
関数の戻り値がなくなったため、sumreleased = scavenge(now, limit);
の行がscavenge(k, now, limit);
に変更されました。- 以前
runtime·MHeap_Scavenger
内で行われていたデバッグ出力のロジックが削除され、その責任がscavenge
関数自体に移されました。これにより、デバッグ出力のロジックが一箇所に集約され、コードの重複が解消されました。
-
runtime∕debug·freeOSMemory
の変更:scavenge(~(uintptr)0, 0);
の呼び出しがscavenge(-1, ~(uintptr)0, 0);
に変更されました。scavenge
関数の新しいシグネチャに合わせて、最初の引数k
に-1
が渡されるようになりました。これは、debug.FreeOSMemory
からの呼び出しであることを示す特別な値として使用されます。
これらの変更により、debug.FreeOSMemory
が呼び出された際に、Goランタイムのメモリ管理の内部状態、特にスカベンジャーによるメモリ解放の状況が詳細に可視化されるようになりました。これは、メモリ関連の問題を診断する上で非常に有用な情報を提供します。
コアとなるコードの変更箇所
src/pkg/runtime/mheap.c
ファイルの以下の部分が変更されました。
--- a/src/pkg/runtime/mheap.c
+++ b/src/pkg/runtime/mheap.c
@@ -412,8 +412,8 @@ scavengelist(MSpan *list, uint64 now, uint64 limit)
return sumreleased;
}
-static uintptr
-scavenge(uint64 now, uint64 limit)
+static void
+scavenge(int32 k, uint64 now, uint64 limit)
{
uint32 i;
uintptr sumreleased;
@@ -424,7 +424,14 @@ scavenge(uint64 now, uint64 limit)
for(i=0; i < nelem(h->free); i++)
sumreleased += scavengelist(&h->free[i], now, limit);
sumreleased += scavengelist(&h->large, now, limit);
- return sumreleased;
+
+ if(runtime·debug.gctrace > 0) {
+ if(sumreleased > 0)
+ runtime·printf("scvg%d: %D MB released\n", k, (uint64)sumreleased>>20);
+ runtime·printf("scvg%d: inuse: %D, idle: %D, sys: %D, released: %D, consumed: %D (MB)\n",
+ k, mstats.heap_inuse>>20, mstats.heap_idle>>20, mstats.heap_sys>>20,
+ mstats.heap_released>>20, (mstats.heap_sys - mstats.heap_released)>>20);
+ }
}
static FuncVal forcegchelperv = {(void(*)(void))forcegchelper};
@@ -437,8 +444,7 @@ runtime·MHeap_Scavenger(void)
{
MHeap *h;
uint64 tick, now, forcegc, limit;
- uint32 k;
- uintptr sumreleased;
+ int32 k;
Note note, *notep;
g->issystem = true;
@@ -476,16 +482,8 @@ runtime·MHeap_Scavenger(void)
runtime·lock(h);
now = runtime·nanotime();
}
- sumreleased = scavenge(now, limit);
+ scavenge(k, now, limit);
runtime·unlock(h);
-
- if(runtime·debug.gctrace > 0) {
- if(sumreleased > 0)
- runtime·printf("scvg%d: %p MB released\n", k, sumreleased>>20);
- runtime·printf("scvg%d: inuse: %D, idle: %D, sys: %D, released: %D, consumed: %D (MB)\\n",
- k, mstats.heap_inuse>>20, mstats.heap_idle>>20, mstats.heap_sys>>20,
- mstats.heap_released>>20, (mstats.heap_sys - mstats.heap_released)>>20);
- }
}
}
@@ -494,7 +492,7 @@ runtime∕debug·freeOSMemory(void)
{
runtime·gc(1);
runtime·lock(&runtime·mheap);
- scavenge(~(uintptr)0, 0);
+ scavenge(-1, ~(uintptr)0, 0);
runtime·unlock(&runtime·mheap);
}
コアとなるコードの解説
scavenge
関数の変更:static uintptr scavenge(uint64 now, uint64 limit)
からstatic void scavenge(int32 k, uint64 now, uint64 limit)
へとシグネチャが変更されました。これにより、scavenge
関数は解放されたメモリ量を直接返すのではなく、内部でデバッグ情報を出力するようになりました。if(runtime·debug.gctrace > 0)
ブロックが追加され、sumreleased
が0より大きい場合に解放されたメモリ量 (%D MB released
) を出力し、さらに現在のメモリ統計情報 (inuse
,idle
,sys
,released
,consumed
) を詳細に出力するようになりました。%D
はGoランタイムのprintf
でuint64
を表示するためのフォーマット指定子です。>>20
はバイト単位の値をMB単位に変換しています。
runtime·MHeap_Scavenger
関数の変更:uintptr sumreleased;
の宣言が削除され、int32 k;
の宣言が追加されました。sumreleased = scavenge(now, limit);
の呼び出しがscavenge(k, now, limit);
に変更されました。- 以前この関数内にあったデバッグ出力のロジックが削除されました。これは、
scavenge
関数自体がデバッグ出力を担当するようになったためです。
runtime∕debug·freeOSMemory
関数の変更:scavenge(~(uintptr)0, 0);
の呼び出しがscavenge(-1, ~(uintptr)0, 0);
に変更されました。k
の値として-1
が渡されることで、この呼び出しがdebug.FreeOSMemory
からのものであることを示します。~(uintptr)0
は、uintptr
型の最大値を表し、limit
引数に渡されることで、スカベンジャーが可能な限り多くのメモリを解放しようとすることを示唆しています。
これらの変更により、Goランタイムのメモリ管理の透明性が向上し、開発者がメモリ使用量の挙動をより詳細に分析できるようになりました。
関連リンク
- Go Issue #5900: https://github.com/golang/go/issues/5900
- Go CL 12669043: https://golang.org/cl/12669043
参考にした情報源リンク
- Goのソースコード (
src/pkg/runtime/mheap.c
) - Goのガベージコレクションに関するドキュメント
- Goのメモリ管理に関する記事やブログ
- Goの
runtime/debug
パッケージのドキュメント - GoのIssueトラッカー (Issue #5900)
- Goのコードレビューシステム (CL 12669043)
- Goの
printf
フォーマット指定子に関する情報