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

[インデックス 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 関数の呼び出し方法の変更、そしてスカベンジャーのデバッグ出力の追加です。

  1. scavenge 関数のシグネチャ変更:

    • 変更前: static uintptr scavenge(uint64 now, uint64 limit)
    • 変更後: static void scavenge(int32 k, uint64 now, uint64 limit)
    • 戻り値の型が uintptr から void に変更されました。これは、scavenge 関数が解放したメモリ量(sumreleased)を直接返すのではなく、内部でログ出力を行うようになったためです。
    • 新たに int32 k という引数が追加されました。これは、スカベンジャーの実行回数や識別子として使用され、ログ出力の際にどのスカベンジャーの実行に関する情報であるかを区別するために使われます。
  2. デバッグ出力の追加:

    • 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 を使用して行われます。
  3. runtime·MHeap_Scavenger の変更:

    • scavenge 関数の戻り値がなくなったため、sumreleased = scavenge(now, limit); の行が scavenge(k, now, limit); に変更されました。
    • 以前 runtime·MHeap_Scavenger 内で行われていたデバッグ出力のロジックが削除され、その責任が scavenge 関数自体に移されました。これにより、デバッグ出力のロジックが一箇所に集約され、コードの重複が解消されました。
  4. 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ランタイムのprintfuint64を表示するためのフォーマット指定子です。>>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のソースコード (src/pkg/runtime/mheap.c)
  • Goのガベージコレクションに関するドキュメント
  • Goのメモリ管理に関する記事やブログ
  • Goのruntime/debugパッケージのドキュメント
  • GoのIssueトラッカー (Issue #5900)
  • Goのコードレビューシステム (CL 12669043)
  • Goのprintfフォーマット指定子に関する情報