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

[インデックス 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が実行されていない場合でもメモリプロファイルの統計情報を強制的に更新するロジックを追加することです。

  1. MProf_GC関数の分離:

    • 元のruntime·MProf_GC関数は、proflockというグローバルなロックを取得・解放してから、メモリプロファイルの統計を更新する処理を行っていました。
    • このコミットでは、ロックの取得・解放を含まない純粋な統計更新ロジックをstatic void MProf_GC(void)という新しい静的関数として分離しました。
    • 元のruntime·MProf_GC関数は、この新しいMProf_GCを呼び出す形に変更され、外部からの呼び出し時には引き続きロックが適切に管理されるようにしています。この分離により、内部的にロックなしで統計更新ロジックを再利用できるようになります。
  2. MemProfile関数での統計更新のトリガー:

    • MemProfile関数は、メモリプロファイルデータを収集してユーザーに返す関数です。
    • この関数内で、clearという新しいブーリアン変数が導入されます。これは、現在のメモリプロファイルのバケット(mbuckets)に「最近の」アロケーションや解放の統計が全く記録されていないかどうかを判断するために使用されます。
    • MemProfileが呼び出された時点で、もしmbuckets内のどのバケットにもallocs(累積アロケーション数)やfrees(累積解放数)が記録されておらず、かつcleartrueである場合(これは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;\

コアとなるコードの解説

  1. 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);でロックを解放するラッパー関数となりました。これにより、統計更新のコアロジックがロック機構から分離され、他の場所からロックなしで呼び出すことが可能になりました。
  2. MemProfile関数におけるGOGC=off対応:

    • MemProfile関数は、現在のメモリプロファイルデータを取得するためのGoの公開APIです。
    • 関数冒頭でruntime·lock(&proflock);によりロックを取得します。
    • clear = true;という新しい変数が導入されます。これは、メモリプロファイルのバケット(mbuckets)に有効な統計データがまだ存在しない(つまり、GCが一度も実行されていない)可能性を検出するためのフラグです。
    • forループでmbucketsを走査し、各バケットのallocsまたはfrees0でない場合(つまり、何らかのアロケーションや解放が記録されている場合)、clearフラグをfalseに設定します。
    • ループ終了後、if(clear)の条件が評価されます。もしcleartrueのままであれば、それは「ガベージコレクションがまだ発生していないことを示唆する、全くデータがない状態」と判断されます。これは特にGOGC=offでプログラムが起動された場合に発生します。
    • このcleartrueの場合、コメントにもあるように「実行開始時からガベージコレクションが無効になっている場合にプロファイリングを許可するため」、MProf_GC()が明示的に呼び出されます。これにより、GCが実行されていなくても、それまでに発生した「最近の」アロケーションと解放の統計が累積統計に統合されます。
    • MProf_GC()が呼び出された後、n(プロファイルエントリの数)が再計算されます。これは、MProf_GC()によって統計が更新されたため、プロファイルエントリの数が変わる可能性があるためです。
    • 最後にruntime·unlock(&proflock);でロックを解放します。

この一連の変更により、MemProfileが呼び出された際に、GCが実行されていない状況でも、それまでのメモリ使用状況がプロファイルに反映されるようになり、GOGC=off環境下でのメモリプロファイリングが可能になりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (runtime/pprof): https://pkg.go.dev/runtime/pprof
  • Go言語のガベージコレクションに関するドキュメントやブログ記事 (例: GoのGCの仕組み、GOGC環境変数など)
  • Goのソースコード (特にsrc/runtime/mprof.gosrc/runtime/mprof.gocの周辺コード)