[インデックス 15033] ファイルの概要
このコミットは、Goランタイムのメモリプロファイラ (mprof.goc) におけるメモリ割り当ての仕組みを改善するものです。具体的には、プロファイラが使用するメモリをガベージコレクタ (GC) の管理下から外し、GCが不要なメモリブロックを解放できない問題を解決することを目的としています。また、addrhash 変数を .bss セクションから移動させる変更も含まれています。
コミット
commit 4da6b36fbf2af314f62bc51576c7785a5631c016
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date: Wed Jan 30 09:01:31 2013 -0800
runtime: local allocation in mprof.goc
Binary data in mprof.goc may prevent the garbage collector from freeing
memory blocks. This patch replaces all calls to runtime·mallocgc() with
calls to an allocator private to mprof.goc, thus making the private
memory invisible to the garbage collector. The addrhash variable is
moved outside of the .bss section.
R=golang-dev, dvyukov, rsc, minux.ma
CC=dave, golang-dev, remyoudompheng
https://golang.org/cl/7135063
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4da6b36fbf2af314f62bc51576c7785a5631c016
元コミット内容
runtime: local allocation in mprof.goc
Binary data in mprof.goc may prevent the garbage collector from freeing
memory blocks. This patch replaces all calls to runtime·mallocgc() with
calls to an allocator private to mprof.goc, thus making the private
memory invisible to the garbage collector. The addrhash variable is
moved outside of the .bss section.
R=golang-dev, dvyukov, rsc, minux.ma
CC=dave, golang-dev, remyoudompheng
https://golang.org/cl/7135063
変更の背景
Goのランタイムには、プログラムのメモリ使用状況をプロファイルするためのメモリプロファイラ (mprof.goc) が存在します。このプロファイラは、内部でメモリを割り当ててバイナリデータを格納しますが、従来の runtime·mallocgc() を使用した割り当て方法では、プロファイラが使用するメモリがGoのガベージコレクタ (GC) の管理下に置かれていました。
この状況は、以下のような問題を引き起こす可能性がありました。
- GCの効率低下: プロファイラが割り当てたメモリがGCの対象となるため、GCはプロファイラがまだ使用しているメモリブロックを解放できないと判断し、不必要にメモリを保持し続ける可能性がありました。これにより、GCの実行頻度が増えたり、GCのサイクルが長くなったりして、アプリケーション全体のパフォーマンスに悪影響を与えることが考えられます。
- メモリリークの可能性: プロファイラが内部的に使用するデータ構造がGCによって適切に認識されない場合、GCがそのメモリを「到達可能」と誤って判断し、実際には不要になったメモリが解放されない、という論理的なメモリリークが発生するリスクがありました。
- プロファイラの独立性: メモリプロファイラは、それ自体がメモリ使用量を測定するツールであるため、その内部的なメモリ管理がGCの挙動に影響を与えることは望ましくありません。プロファイラ自身のメモリ割り当てがGCの対象となることで、プロファイラの測定結果がGCの挙動によって歪められる可能性も考えられます。
これらの問題を解決するため、mprof.goc が使用するメモリをGCから独立させ、プロファイラ専用のローカルなアロケータを使用するように変更する必要がありました。また、addrhash 変数が .bss セクションに配置されていることが、何らかの理由で問題を引き起こす可能性があったため、これも変更の対象となりました。.bss セクションは通常、初期化されていない静的変数やグローバル変数が配置される場所であり、特定の最適化やメモリ管理の文脈で特別な考慮が必要になることがあります。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムとメモリ管理に関する基本的な知識が必要です。
-
Goのガベージコレクタ (GC):
- Goは自動メモリ管理を採用しており、不要になったメモリを自動的に解放するガベージコレクタを備えています。
- GoのGCは、主に「マーク&スイープ」アルゴリズムをベースにしています。プログラムが使用している(到達可能な)メモリブロックをマークし、マークされなかった(到達不可能な)メモリブロックを解放します。
- GCは、ヒープメモリ(
runtime·mallocgcなどで割り当てられるメモリ)を管理します。 - GCの目的は、開発者が手動でメモリを解放する手間を省き、メモリリークのリスクを低減することですが、GCの実行自体がアプリケーションのパフォーマンスに影響を与えることがあります。
-
Goランタイムのメモリ割り当て:
- Goプログラムがメモリを要求すると、ランタイムはOSからメモリを確保し、それを管理します。
runtime·mallocgc(): これはGoランタイム内部で使用される関数で、GCの管理下にあるヒープメモリを割り当てます。この関数で割り当てられたメモリはGCの対象となり、不要になればGCによって解放されます。runtime·SysAlloc(): これはGoランタイムがOSから直接メモリを要求するために使用する低レベルな関数です。runtime·SysAlloc()で割り当てられたメモリは、GoのGCの直接的な管理下にはありません。通常、ランタイムの内部構造や、GCの対象外としたい特定の目的のために使用されます。
-
メモリプロファイラ (
mprof.goc):- Goのランタイムには、プログラムのメモリ使用状況(どのコードがどれくらいのメモリを割り当てているか、どのオブジェクトがどれくらいのメモリを消費しているかなど)を収集・分析するためのプロファイリングツールが組み込まれています。
mprof.gocは、このメモリプロファイラのC言語部分の実装ファイルです。Goランタイムの多くの部分はC言語で書かれており、Goのコードと連携して動作します。
-
.bssセクション:- コンパイルされたプログラムのメモリレイアウトの一部です。
.bss(Block Started by Symbol) セクションは、初期化されていない静的変数やグローバル変数が格納される領域です。これらの変数は、プログラムの実行開始時にゼロで初期化されます。.dataセクションは初期化された静的変数やグローバル変数が格納される領域です。.textセクションは実行可能なコードが格納される領域です。- 特定の状況下では、
.bssセクションに配置された変数の扱いが、メモリ管理やリンカの挙動に影響を与えることがあります。
-
uintptr:- Goにおける
uintptr型は、ポインタを保持するのに十分な大きさの符号なし整数型です。ポ - インタ演算や、メモリのアドレスを整数として扱う場合によく使用されます。GCの対象外のメモリを扱う際にも利用されます。
- Goにおける
技術的詳細
このコミットの技術的な核心は、mprof.goc 内でのメモリ割り当て戦略の変更にあります。
-
専用アロケータの導入:
mprof.goc内にallocateという新しい関数が導入されました。この関数は、メモリプロファイラが内部的に使用するメモリを割り当てるための専用アロケータです。allocate関数は、runtime·mallocgc()の代わりに、runtime·SysAlloc()を使用してOSから直接メモリを確保します。これにより、allocateによって割り当てられたメモリはGoのGCの管理対象外となります。allocate関数は、poolとpoolfreeという静的変数を使用して、確保したメモリを再利用するシンプルなプールアロケータとして機能します。pool: 現在のメモリプールの開始アドレスを指すポインタ。poolfree: 現在のメモリプールに残っているバイト数。
Chunk定数 (32*PageSize) は、一度にOSから確保するメモリブロックのサイズを定義しています。PageSizeはシステムのメモリページサイズ(通常4KB)です。allocate関数は、要求されたサイズがChunk/2以上の場合、直接runtime·SysAllocを呼び出します。これは、大きな割り当てに対してはプールを介さずに直接OSからメモリを確保する方が効率的であるためです。- 小さな割り当ての場合、
alloclockという新しいロックを使用して、メモリプールへのアクセスを同期します。これは、複数のゴルーチンが同時にプロファイラを使用する可能性があるため、データ競合を防ぐためです。 - プールに十分なメモリがない場合、新しい
Chunkサイズのメモリブロックをruntime·SysAllocで確保し、それを新しいプールとして使用します。
-
runtime·mallocgc()からallocate()への置き換え:mprof.goc内の既存のruntime·mallocgc()の呼び出しが、すべて新しく導入されたallocate()関数に置き換えられました。- これにより、プロファイラが内部的に使用する
Bucket構造体やAddrHash構造体などのメモリが、GCの管理下から外れ、プロファイラ専用のメモリプールから割り当てられるようになります。
-
addrhash変数の移動と初期化:addrhash変数は、以前はstatic AddrHash *addrhash[1<<AddrHashBits];として定義されており、これは.bssセクションに配置される可能性がありました。- 変更後、
static AddrHash **addrhash;とポインタとして宣言され、runtime·mprofinit()関数内でallocateを使用して明示的にメモリが割り当てられるようになりました。 runtime·mprofinit()関数は、runtime·schedinit()(スケジューラの初期化関数) の中で呼び出されるようになり、Goランタイムの起動時にaddrhashが適切に初期化されることが保証されます。- この変更により、
addrhashがGCの管理外のメモリに配置され、.bssセクションに関連する潜在的な問題を回避できます。
変更による効果:
- GCの負荷軽減: メモリプロファイラが使用するメモリがGCの対象外となるため、GCはプロファイラが使用するメモリをスキャンしたり、解放を試みたりする必要がなくなります。これにより、GCの実行効率が向上し、アプリケーション全体のパフォーマンスが改善される可能性があります。
- メモリリークの防止: プロファイラが使用するメモリがGCから独立することで、GCがプロファイラのメモリを誤って保持し続けることによる論理的なメモリリークのリスクがなくなります。
- プロファイラの独立性向上: プロファイラ自身のメモリ管理がGCの挙動に左右されなくなるため、より正確で安定したプロファイリング結果が得られるようになります。
コアとなるコードの変更箇所
src/pkg/runtime/mprof.goc
- 新しい静的変数と定数の追加:
static Lock proflock, alloclock; // alloclockが追加 static byte *pool; // memory allocation pool static uintptr poolfree; // number of bytes left in the pool enum { Chunk = 32*PageSize, // initial size of the pool }; allocate関数の追加:static void* allocate(uintptr size) { void *v; if(size == 0) return nil; if(size >= Chunk/2) return runtime·SysAlloc(size); runtime·lock(&alloclock); if(size > poolfree) { pool = runtime·SysAlloc(Chunk); poolfree = Chunk; } v = pool; pool += size; poolfree -= size; runtime·unlock(&alloclock); return v; }runtime·mallocgcのallocateへの置き換え:stkbucket関数内:- b = runtime·mallocgc(sizeof *b + nstk*sizeof stk[0], FlagNoProfiling, 0, 1); + b = allocate(sizeof *b + nstk*sizeof stk[0]);setaddrbucket関数内:- ah = runtime·mallocgc(sizeof *ah, FlagNoProfiling, 0, 1); + ah = allocate(sizeof *ah); // ... - e = runtime·mallocgc(64*sizeof *e, FlagNoProfiling, 0, 0); + e = allocate(64*sizeof *e);
addrhashの宣言変更と初期化関数の追加:-static AddrHash *addrhash[1<<AddrHashBits]; +static AddrHash **addrhash; // points to (AddrHash*)[1<<AddrHashBits] // ... +void +runtime·mprofinit(void) +{ + addrhash = allocate((1<<AddrHashBits)*sizeof *addrhash); +}
src/pkg/runtime/proc.c
runtime·mprofinit()の呼び出し追加:@@ -190,6 +190,7 @@ runtime·schedinit(void)\n byte *p;\n \n m->nomemprof++;\n+ runtime·mprofinit();\n runtime·mallocinit();\n mcommoninit(m);\n ```
src/pkg/runtime/runtime.h
runtime·mprofinit()の関数プロトタイプ宣言追加:@@ -657,6 +657,7 @@ void runtime·stackfree(void*, uintptr);\n MCache* runtime·allocmcache(void);\n void runtime·freemcache(MCache*);\n void runtime·mallocinit(void);\n+void runtime·mprofinit(void);\n bool runtime·ifaceeq_c(Iface, Iface);\n bool runtime·efaceeq_c(Eface, Eface);\n uintptr runtime·ifacehash(Iface, uintptr);\n ```
コアとなるコードの解説
このコミットの核心は、mprof.goc 内で定義された新しい allocate 関数と、それに関連するメモリプールの実装です。
-
allocate関数:- この関数は、メモリプロファイラが内部的に使用するすべてのメモリ割り当てを処理するためのゲートウェイとなります。
runtime·SysAlloc(size)を直接呼び出すことで、OSから生のメモリを確保します。これにより、Goのガベージコレクタはこのメモリを認識せず、GCの対象外となります。Chunkサイズのメモリプールを管理し、小さな割り当て要求に対しては、このプールからメモリを切り出して提供します。これにより、頻繁なruntime·SysAllocの呼び出しを避け、システムコールによるオーバーヘッドを削減します。alloclockを使用して、メモリプールへのアクセスを保護し、複数のゴルーチンからの同時アクセスによるデータ破損を防ぎます。poolとpoolfreeは、このメモリプールの状態を追跡するために使用されます。poolは次に割り当てるメモリの開始位置を指し、poolfreeはプールに残っている利用可能なバイト数を示します。
-
runtime·mallocgcからallocateへの置き換え:mprof.goc内のstkbucket関数とsetaddrbucket関数は、メモリプロファイラの内部データ構造(BucketやAddrHash)を割り当てるためにruntime·mallocgcを使用していました。- これらの呼び出しを
allocateに置き換えることで、プロファイラが使用するメモリがGCの管理下から完全に外れ、プロファイラ自身のメモリ管理ロジックによって制御されるようになります。これにより、GCの干渉を受けずにプロファイラが動作できるようになります。
-
addrhashの初期化:addrhashは、メモリプロファイラがアドレス情報を効率的にルックアップするために使用するハッシュテーブルのポインタです。- 以前は静的配列として宣言されていたため、
.bssセクションに配置され、Goランタイムの起動時にゼロ初期化されていました。 - 新しい実装では、
addrhashはポインタのポインタ (AddrHash **addrhash) として宣言され、runtime·mprofinit()関数内でallocateを使用して、GC管理外のメモリに明示的に割り当てられます。 runtime·mprofinit()は、Goランタイムの初期化フェーズであるruntime·schedinit()の中で呼び出されるため、プログラムの実行開始前にaddrhashが適切に初期化されることが保証されます。この変更は、.bssセクションに関連する潜在的な問題を回避し、プロファイラのメモリ管理をより一貫性のあるものにするためのものです。
これらの変更により、Goのメモリプロファイラは、自身のメモリ割り当てをGCから独立させ、より効率的かつ予測可能な方法で動作するようになります。これは、プロファイラの正確性を向上させ、Goアプリケーション全体のパフォーマンスプロファイリングの信頼性を高める上で重要な改善です。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Goのガベージコレクタに関する情報: Goの公式ブログやドキュメントでGCの進化や仕組みについて解説されています。
- Goのランタイムソースコード: https://github.com/golang/go/tree/master/src/runtime
- Goのメモリプロファイリングに関するドキュメント: https://pkg.go.dev/runtime/pprof (これはGoのユーザーレベルのプロファイリングツールですが、内部的には
mprof.gocのようなランタイム機能に依存しています)
参考にした情報源リンク
- Goのコミット履歴: https://github.com/golang/go/commits/master
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/7135063は、このGerritの変更リストへのリンクです) - Goのランタイムに関する技術記事やブログポスト (例: GoのGCの仕組み、メモリ管理の内部など)
- C言語のメモリセクション (
.bss,.data,.textなど) に関する一般的な情報源 (コンパイラやリンカのドキュメントなど) runtime·SysAllocやruntime·mallocgcのようなGoランタイム内部関数の挙動に関するGoのソースコードコメントや関連する設計ドキュメント。
[インデックス 15033] ファイルの概要
このコミットは、Goランタイムのメモリプロファイラ (mprof.goc) におけるメモリ割り当ての仕組みを改善するものです。具体的には、プロファイラが使用するメモリをガベージコレクタ (GC) の管理下から外し、GCが不要なメモリブロックを解放できない問題を解決することを目的としています。また、addrhash 変数を .bss セクションから移動させる変更も含まれています。
コミット
commit 4da6b36fbf2af314f62bc51576c7785a5631c016
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date: Wed Jan 30 09:01:31 2013 -0800
runtime: local allocation in mprof.goc
Binary data in mprof.goc may prevent the garbage collector from freeing
memory blocks. This patch replaces all calls to runtime·mallocgc() with
calls to an allocator private to mprof.goc, thus making the private
memory invisible to the garbage collector. The addrhash variable is
moved outside of the .bss section.
R=golang-dev, dvyukov, rsc, minux.ma
CC=dave, golang-dev, remyoudompheng
https://golang.org/cl/7135063
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4da6b36fbf2af314f62bc51576c7785a5631c016
元コミット内容
runtime: local allocation in mprof.goc
Binary data in mprof.goc may prevent the garbage collector from freeing
memory blocks. This patch replaces all calls to runtime·mallocgc() with
calls to an allocator private to mprof.goc, thus making the private
memory invisible to the garbage collector. The addrhash variable is
moved outside of the .bss section.
R=golang-dev, dvyukov, rsc, minux.ma
CC=dave, golang-dev, remyoudompheng
https://golang.org/cl/7135063
変更の背景
Goのランタイムには、プログラムのメモリ使用状況をプロファイルするためのメモリプロファイラ (mprof.goc) が存在します。このプロファイラは、内部でメモリを割り当ててバイナリデータを格納しますが、従来の runtime·mallocgc() を使用した割り当て方法では、プロファイラが使用するメモリがGoのガベージコレクタ (GC) の管理下に置かれていました。
この状況は、以下のような問題を引き起こす可能性がありました。
- GCの効率低下: プロファイラが割り当てたメモリがGCの対象となるため、GCはプロファイラがまだ使用しているメモリブロックを解放できないと判断し、不必要にメモリを保持し続ける可能性がありました。これにより、GCの実行頻度が増えたり、GCのサイクルが長くなったりして、アプリケーション全体のパフォーマンスに悪影響を与えることが考えられます。
- メモリリークの可能性: プロファイラが内部的に使用するデータ構造がGCによって適切に認識されない場合、GCがそのメモリを「到達可能」と誤って判断し、実際には不要になったメモリが解放されない、という論理的なメモリリークが発生するリスクがありました。
- プロファイラの独立性: メモリプロファイラは、それ自体がメモリ使用量を測定するツールであるため、その内部的なメモリ管理がGCの挙動に影響を与えることは望ましくありません。プロファイラ自身のメモリ割り当てがGCの対象となることで、プロファイラの測定結果がGCの挙動によって歪められる可能性も考えられます。
これらの問題を解決するため、mprof.goc が使用するメモリをGCから独立させ、プロファイラ専用のローカルなアロケータを使用するように変更する必要がありました。また、addrhash 変数が .bss セクションに配置されていることが、何らかの理由で問題を引き起こす可能性があったため、これも変更の対象となりました。.bss セクションは通常、初期化されていない静的変数やグローバル変数が配置される場所であり、特定の最適化やメモリ管理の文脈で特別な考慮が必要になることがあります。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムとメモリ管理に関する基本的な知識が必要です。
-
Goのガベージコレクタ (GC):
- Goは自動メモリ管理を採用しており、不要になったメモリを自動的に解放するガベージコレクタを備えています。
- GoのGCは、主に「マーク&スイープ」アルゴリズムをベースにしています。プログラムが使用している(到達可能な)メモリブロックをマークし、マークされなかった(到達不可能な)メモリブロックを解放します。
- GCは、ヒープメモリ(
runtime·mallocgcなどで割り当てられるメモリ)を管理します。 - GCの目的は、開発者が手動でメモリを解放する手間を省き、メモリリークのリスクを低減することですが、GCの実行自体がアプリケーションのパフォーマンスに影響を与えることがあります。
-
Goランタイムのメモリ割り当て:
- Goプログラムがメモリを要求すると、ランタイムはOSからメモリを確保し、それを管理します。
runtime·mallocgc(): これはGoランタイム内部で使用される関数で、GCの管理下にあるヒープメモリを割り当てます。この関数で割り当てられたメモリはGCの対象となり、不要になればGCによって解放されます。runtime·SysAlloc(): これはGoランタイムがOSから直接メモリを要求するために使用する低レベルな関数です。runtime·SysAlloc()で割り当てられたメモリは、GoのGCの直接的な管理下にはありません。通常、ランタイムの内部構造や、GCの対象外としたい特定の目的のために使用されます。
-
メモリプロファイラ (
mprof.goc):- Goのランタイムには、プログラムのメモリ使用状況(どのコードがどれくらいのメモリを割り当てているか、どのオブジェクトがどれくらいのメモリを消費しているかなど)を収集・分析するためのプロファイリングツールが組み込まれています。
mprof.gocは、このメモリプロファイラのC言語部分の実装ファイルです。Goランタイムの多くの部分はC言語で書かれており、Goのコードと連携して動作します。
-
.bssセクション:- コンパイルされたプログラムのメモリレイアウトの一部です。
.bss(Block Started by Symbol) セクションは、初期化されていない静的変数やグローバル変数が格納される領域です。これらの変数は、プログラムの実行開始時にゼロで初期化されます。.dataセクションは初期化された静的変数やグローバル変数が格納される領域です。.textセクションは実行可能なコードが格納される領域です。- 特定の状況下では、
.bssセクションに配置された変数の扱いが、メモリ管理やリンカの挙動に影響を与えることがあります。
-
uintptr:- Goにおける
uintptr型は、ポインタを保持するのに十分な大きさの符号なし整数型です。ポ - インタ演算や、メモリのアドレスを整数として扱う場合によく使用されます。GCの対象外のメモリを扱う際にも利用されます。
- Goにおける
技術的詳細
このコミットの技術的な核心は、mprof.goc 内でのメモリ割り当て戦略の変更にあります。
-
専用アロケータの導入:
mprof.goc内にallocateという新しい関数が導入されました。この関数は、メモリプロファイラが内部的に使用するメモリを割り当てるための専用アロケータです。allocate関数は、runtime·mallocgc()の代わりに、runtime·SysAlloc()を使用してOSから直接メモリを確保します。これにより、allocateによって割り当てられたメモリはGoのGCの管理対象外となります。allocate関数は、poolとpoolfreeという静的変数を使用して、確保したメモリを再利用するシンプルなプールアロケータとして機能します。pool: 現在のメモリプールの開始アドレスを指すポインタ。poolfree: 現在のメモリプールに残っているバイト数。
Chunk定数 (32*PageSize) は、一度にOSから確保するメモリブロックのサイズを定義しています。PageSizeはシステムのメモリページサイズ(通常4KB)です。allocate関数は、要求されたサイズがChunk/2以上の場合、直接runtime·SysAllocを呼び出します。これは、大きな割り当てに対してはプールを介さずに直接OSからメモリを確保する方が効率的であるためです。- 小さな割り当ての場合、
alloclockという新しいロックを使用して、メモリプールへのアクセスを同期します。これは、複数のゴルーチンが同時にプロファイラを使用する可能性があるため、データ競合を防ぐためです。 - プールに十分なメモリがない場合、新しい
Chunkサイズのメモリブロックをruntime·SysAllocで確保し、それを新しいプールとして使用します。
-
runtime·mallocgc()からallocate()への置き換え:mprof.goc内の既存のruntime·mallocgc()の呼び出しが、すべて新しく導入されたallocate()関数に置き換えられました。- これにより、プロファイラが内部的に使用する
Bucket構造体やAddrHash構造体などのメモリが、GCの管理下から外れ、プロファイラ専用のメモリプールから割り当てられるようになります。
-
addrhash変数の移動と初期化:addrhash変数は、以前はstatic AddrHash *addrhash[1<<AddrHashBits];として定義されており、これは.bssセクションに配置される可能性がありました。- 変更後、
static AddrHash **addrhash;とポインタとして宣言され、runtime·mprofinit()関数内でallocateを使用して明示的にメモリが割り当てられるようになりました。 runtime·mprofinit()関数は、runtime·schedinit()(スケジューラの初期化関数) の中で呼び出されるようになり、Goランタイムの起動時にaddrhashが適切に初期化されることが保証されます。- この変更により、
addrhashがGCの管理外のメモリに配置され、.bssセクションに関連する潜在的な問題を回避できます。
変更による効果:
- GCの負荷軽減: メモリプロファイラが使用するメモリがGCの対象外となるため、GCはプロファイラが使用するメモリをスキャンしたり、解放を試みたりする必要がなくなります。これにより、GCの実行効率が向上し、アプリケーション全体のパフォーマンスが改善される可能性があります。
- メモリリークの防止: プロファイラが使用するメモリがGCから独立することで、GCがプロファイラのメモリを誤って保持し続けることによる論理的なメモリリークのリスクがなくなります。
- プロファイラの独立性向上: プロファイラ自身のメモリ管理がGCの挙動に左右されなくなるため、より正確で安定したプロファイリング結果が得られるようになります。
コアとなるコードの変更箇所
src/pkg/runtime/mprof.goc
- 新しい静的変数と定数の追加:
static Lock proflock, alloclock; // alloclockが追加 static byte *pool; // memory allocation pool static uintptr poolfree; // number of bytes left in the pool enum { Chunk = 32*PageSize, // initial size of the pool }; allocate関数の追加:static void* allocate(uintptr size) { void *v; if(size == 0) return nil; if(size >= Chunk/2) return runtime·SysAlloc(size); runtime·lock(&alloclock); if(size > poolfree) { pool = runtime·SysAlloc(Chunk); poolfree = Chunk; } v = pool; pool += size; poolfree -= size; runtime·unlock(&alloclock); return v; }runtime·mallocgcのallocateへの置き換え:stkbucket関数内:- b = runtime·mallocgc(sizeof *b + nstk*sizeof stk[0], FlagNoProfiling, 0, 1); + b = allocate(sizeof *b + nstk*sizeof stk[0]);setaddrbucket関数内:- ah = runtime·mallocgc(sizeof *ah, FlagNoProfiling, 0, 1); + ah = allocate(sizeof *ah); // ... - e = runtime·mallocgc(64*sizeof *e, FlagNoProfiling, 0, 0); + e = allocate(64*sizeof *e);
addrhashの宣言変更と初期化関数の追加:-static AddrHash *addrhash[1<<AddrHashBits]; +static AddrHash **addrhash; // points to (AddrHash*)[1<<AddrHashBits] // ... +void +runtime·mprofinit(void) +{ + addrhash = allocate((1<<AddrHashBits)*sizeof *addrhash); +}
src/pkg/runtime/proc.c
runtime·mprofinit()の呼び出し追加:@@ -190,6 +190,7 @@ runtime·schedinit(void)\n byte *p;\n \n m->nomemprof++;\n+ runtime·mprofinit();\n runtime·mallocinit();\n mcommoninit(m);\n ```
src/pkg/runtime/runtime.h
runtime·mprofinit()の関数プロトタイプ宣言追加:@@ -657,6 +657,7 @@ void runtime·stackfree(void*, uintptr);\n MCache* runtime·allocmcache(void);\n void runtime·freemcache(MCache*);\n void runtime·mallocinit(void);\n+void runtime·mprofinit(void);\n bool runtime·ifaceeq_c(Iface, Iface);\n bool runtime·efaceeq_c(Eface, Eface);\n uintptr runtime·ifacehash(Iface, uintptr);\n ```
コアとなるコードの解説
このコミットの核心は、mprof.goc 内で定義された新しい allocate 関数と、それに関連するメモリプールの実装です。
-
allocate関数:- この関数は、メモリプロファイラが内部的に使用するすべてのメモリ割り当てを処理するためのゲートウェイとなります。
runtime·SysAlloc(size)を直接呼び出すことで、OSから生のメモリを確保します。これにより、Goのガベージコレクタはこのメモリを認識せず、GCの対象外となります。Chunkサイズのメモリプールを管理し、小さな割り当て要求に対しては、このプールからメモリを切り出して提供します。これにより、頻繁なruntime·SysAllocの呼び出しを避け、システムコールによるオーバーヘッドを削減します。alloclockを使用して、メモリプールへのアクセスを保護し、複数のゴルーチンからの同時アクセスによるデータ破損を防ぎます。poolとpoolfreeは、このメモリプールの状態を追跡するために使用されます。poolは次に割り当てるメモリの開始位置を指し、poolfreeはプールに残っている利用可能なバイト数を示します。
-
runtime·mallocgcからallocateへの置き換え:mprof.goc内のstkbucket関数とsetaddrbucket関数は、メモリプロファイラの内部データ構造(BucketやAddrHash)を割り当てるためにruntime·mallocgcを使用していました。- これらの呼び出しを
allocateに置き換えることで、プロファイラが使用するメモリがGCの管理下から完全に外れ、プロファイラ自身のメモリ管理ロジックによって制御されるようになります。これにより、GCの干渉を受けずにプロファイラが動作できるようになります。
-
addrhashの初期化:addrhashは、メモリプロファイラがアドレス情報を効率的にルックアップするために使用するハッシュテーブルのポインタです。- 以前は静的配列として宣言されていたため、
.bssセクションに配置され、Goランタイムの起動時にゼロ初期化されていました。 - 新しい実装では、
addrhashはポインタのポインタ (AddrHash **addrhash) として宣言され、runtime·mprofinit()関数内でallocateを使用して、GC管理外のメモリに明示的に割り当てられます。 runtime·mprofinit()は、Goランタイムの初期化フェーズであるruntime·schedinit()の中で呼び出されるため、プログラムの実行開始前にaddrhashが適切に初期化されることが保証されます。この変更は、.bssセクションに関連する潜在的な問題を回避し、プロファイラのメモリ管理をより一貫性のあるものにするためのものです。
これらの変更により、Goのメモリプロファイラは、自身のメモリ割り当てをGCから独立させ、より効率的かつ予測可能な方法で動作するようになります。これは、プロファイラの正確性を向上させ、Goアプリケーション全体のパフォーマンスプロファイリングの信頼性を高める上で重要な改善です。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Goのガベージコレクタに関する情報: Goの公式ブログやドキュメントでGCの進化や仕組みについて解説されています。
- Goのランタイムソースコード: https://github.com/golang/go/tree/master/src/runtime
- Goのメモリプロファイリングに関するドキュメント: https://pkg.go.dev/runtime/pprof (これはGoのユーザーレベルのプロファイリングツールですが、内部的には
mprof.gocのようなランタイム機能に依存しています)
参考にした情報源リンク
- Goのコミット履歴: https://github.com/golang/go/commits/master
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/7135063は、このGerritの変更リストへのリンクです) - Goのランタイムに関する技術記事やブログポスト (例: GoのGCの仕組み、メモリ管理の内部など)
- C言語のメモリセクション (
.bss,.data,.textなど) に関する一般的な情報源 (コンパイラやリンカのドキュメントなど) runtime·SysAllocやruntime·mallocgcのようなGoランタイム内部関数の挙動に関するGoのソースコードコメントや関連する設計ドキュメント。