[インデックス 17496] ファイルの概要
このコミットは、Goランタイムのメモリ統計(MemStats
)におけるシステムメモリの計上方法を改善し、より正確なメモリ使用状況の把握を可能にするものです。特に、これまでMemStats
に計上されていなかった様々なシステム割り当て(GCビットマップ、スパンテーブル、GCルートブロックなど)をGCSys
とOtherSys
という新しい統計カテゴリに分類し、Sys
(システムから取得した総メモリ量)がこれらのカテゴリの合計と一致するように修正しています。
コミット
commit a33ef8d11b9db6646991bee5732015562fd4efd2
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Fri Sep 6 16:55:40 2013 -0400
runtime: account for all sys memory in MemStats
Currently lots of sys allocations are not accounted in any of XxxSys,
including GC bitmap, spans table, GC roots blocks, GC finalizer blocks,
iface table, netpoll descriptors and more. Up to ~20% can unaccounted.
This change introduces 2 new stats: GCSys and OtherSys for GC metadata
and all other misc allocations, respectively.
Also ensures that all XxxSys indeed sum up to Sys. All sys memory allocation
functions require the stat for accounting, so that it's impossible to miss something.
Also fix updating of mcache_sys/inuse, they were not updated after deallocation.
test/bench/garbage/parser before:
Sys 670064344
HeapSys 610271232
StackSys 65536
MSpanSys 14204928
MCacheSys 16384
BuckHashSys 1439992
after:
Sys 670064344
HeapSys 610271232
StackSys 65536
MSpanSys 14188544
MCacheSys 16384
BuckHashSys 3194304
GCSys 39198688
OtherSys 3129656
Fixes #5799.
R=rsc, dave, alex.brainman
CC=golang-dev
https://golang.org/cl/12946043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a33ef8d11b9db6646991bee5732015562fd4efd2
元コミット内容
GoランタイムのMemStats
において、システムから取得したメモリ(Sys
)の計上が不完全であった問題を修正します。具体的には、GCビットマップ、スパンテーブル、GCルートブロック、GCファイナライザブロック、ifaceテーブル、netpollディスクリプタなど、多くのシステム割り当てがXxxSys
のいずれにも計上されておらず、最大で約20%のメモリが未計上になる可能性がありました。
この変更では、GCメタデータ用のGCSys
と、その他の雑多な割り当て用のOtherSys
という2つの新しい統計カテゴリを導入します。これにより、すべてのXxxSys
がSys
の合計と一致するようにします。また、すべてのシステムメモリ割り当て関数が計上用の統計カテゴリを要求するように変更することで、メモリの計上漏れを防ぎます。さらに、mcache_sys
/inuse
が解放後に更新されていなかった問題も修正します。
変更前後のtest/bench/garbage/parser
の出力例が示されており、GCSys
とOtherSys
が追加され、BuckHashSys
とMSpanSys
の値が調整されていることが確認できます。
変更の背景
Goランタイムは、プログラムのメモリ使用状況を詳細に把握するためにMemStats
という構造体を提供しています。しかし、このコミットが導入される以前は、MemStats
が報告するシステムから取得した総メモリ量(Sys
)と、個々のカテゴリ(HeapSys
, StackSys
など)の合計との間に乖離がありました。これは、Goランタイムが内部的に使用する一部のメモリ(例えば、ガベージコレクションの内部構造やネットワークポーリングのディスクリプタなど)が、既存のMemStats
カテゴリのいずれにも適切に分類・計上されていなかったためです。
この計上漏れは、メモリプロファイリングやデバッグにおいて、Goプログラムが実際にどれだけのシステムメモリを使用しているのかを正確に把握することを困難にしていました。特に、大規模なアプリケーションや長時間稼働するサービスでは、この未計上メモリが無視できない量になる可能性があり、メモリリークの検出やパフォーマンスチューニングの妨げとなることが懸念されました。
この問題を解決するため、Goランタイムのメモリ計上メカニズムを改善し、すべてのシステム割り当てがMemStats
に適切に反映されるようにする必要がありました。これが、GCSys
とOtherSys
の導入、およびシステムメモリ割り当て関数の変更の背景となります。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムのメモリ管理に関する基本的な知識が必要です。
-
MemStats
: Goランタイムが提供するメモリ統計情報を含む構造体です。runtime.ReadMemStats()
関数を通じて取得でき、ヒープ、スタック、GC、システムメモリなど、様々なカテゴリのメモリ使用量に関する詳細な情報を提供します。Sys
: Goランタイムがオペレーティングシステムから取得した総メモリ量を示します。HeapSys
: ヒープ領域のためにシステムから取得したメモリ量。StackSys
: スタック領域のためにシステムから取得したメモリ量。MSpanSys
: メモリスパン(Goのメモリ管理における連続したページ群)のためにシステムから取得したメモリ量。MCacheSys
: メモリキャッシュ(Mキャッシュ)のためにシステムから取得したメモリ量。BuckHashSys
: プロファイリング用のバケットハッシュテーブルのためにシステムから取得したメモリ量。GCSys
(新規): ガベージコレクションのメタデータのためにシステムから取得したメモリ量。OtherSys
(新規): その他の雑多なランタイム割り当てのためにシステムから取得したメモリ量。
-
Goのメモリ管理の階層: Goランタイムは、OSからメモリを要求し、それを独自のメモリ管理システムで管理します。
- OSからのメモリ取得:
mmap
やVirtualAlloc
などのシステムコールを通じて、OSから仮想メモリ領域を確保します。 - アリーナ (Arena): 確保した仮想メモリ領域を、Goランタイムが管理する大きなチャンク(アリーナ)に分割します。
- スパン (Span): アリーナをさらに小さな単位であるスパンに分割します。スパンは1ページ以上の連続したメモリページで構成され、特定のサイズのオブジェクトを格納するために使用されます。
- オブジェクト: 実際にGoプログラムが使用するデータ構造(オブジェクト)がスパン内に割り当てられます。
- OSからのメモリ取得:
-
SysAlloc
,SysFree
,SysMap
: GoランタイムがOSと直接やり取りするための低レベルなメモリ管理関数です。runtime.SysAlloc(nbytes)
: OSからnbytes
分のメモリを割り当てます。runtime.SysFree(v, nbytes)
:v
から始まるnbytes
分のメモリをOSに解放します。runtime.SysMap(v, nbytes)
: 以前に予約されたアドレス空間を実際に使用可能にするためにマップします。
-
persistentalloc
: Goランタイム内部で、ガベージコレクションの対象とならない永続的なデータ構造(型情報、インターフェーステーブルなど)を割り当てるために使用される関数です。 -
FixAlloc
: 固定サイズのオブジェクトを効率的に割り当てるためのフリーリストアロケータです。MSpan
やMCache
などのランタイム内部構造の割り当てに使用されます。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
MemStats
構造体の拡張:src/pkg/runtime/mem.go
において、MemStats
構造体にGCSys
とOtherSys
の2つのフィールドが追加されました。type MemStats struct { // ... 既存のフィールド ... GCSys uint64 // GC metadata OtherSys uint64 // other system allocations }
これにより、GC関連のメタデータと、その他の雑多なランタイム内部割り当てが個別に計上されるようになります。
-
システムメモリ割り当て関数の変更:
runtime.SysAlloc
,runtime.SysFree
,runtime.SysMap
といったOSレベルのメモリ操作関数に、uint64 *stat
という引数が追加されました。このstat
引数は、割り当てまたは解放されたメモリ量を計上すべきMemStats
内のカウンタ(例:mstats.heap_sys
,mstats.gc_sys
,mstats.other_sys
など)へのポインタです。- 変更前: これらの関数は直接
mstats.sys
を更新していました。 - 変更後: 各呼び出し元が、その割り当てがどのカテゴリに属するかを示す
stat
ポインタを渡すことで、より粒度の高い計上が可能になりました。 runtime.xadd64(stat, n)
: アトミックにstat
が指すカウンタにn
を加算する関数が使用されています。これにより、並行処理環境下でも正確な計上が保証されます。
- 変更前: これらの関数は直接
-
persistentalloc
関数の変更:runtime.persistentalloc
関数にもuint64 *stat
引数が追加されました。これにより、永続的な割り当てがどのカテゴリに属するかを明示的に指定できるようになりました。- 特に、
persistentalloc
がmstats.other_sys
以外の統計カテゴリに計上されるべきメモリを割り当てた場合、mstats.other_sys
からその分を減算し、指定されたstat
に加算するロジックが追加されています。これは、persistentalloc
がデフォルトでother_sys
に計上されるが、特定の用途(例:buckhash_sys
)のために割り当てられた場合は、そのカテゴリに再計上する必要があるためです。
- 特に、
-
FixAlloc_Init
関数の変更:FixAlloc
アロケータの初期化関数runtime.FixAlloc_Init
にもuint64 *stat
引数が追加されました。これにより、FixAlloc
が管理するメモリ(inuse
とsys
)が、指定された統計カテゴリに計上されるようになります。FixAlloc
のsys
フィールドがuintptr
からuint64*
に変更され、直接MemStats
のカウンタを指すようになりました。
-
既存のメモリ割り当て箇所の更新: Goランタイム内の様々な場所で、
runtime.SysAlloc
,runtime.SysFree
,runtime.SysMap
,runtime.persistentalloc
,runtime.FixAlloc_Init
の呼び出しが、新しいstat
引数を渡すように修正されました。- 例: GCビットマップ、スパンテーブル、GCルートブロック、ファイナライザブロック、ifaceテーブル、netpollディスクリプタ、プロファイリング用ハッシュテーブルなどの割り当てが、それぞれ
mstats.gc_sys
やmstats.other_sys
、mstats.buckhash_sys
に計上されるようになりました。
- 例: GCビットマップ、スパンテーブル、GCルートブロック、ファイナライザブロック、ifaceテーブル、netpollディスクリプタ、プロファイリング用ハッシュテーブルなどの割り当てが、それぞれ
-
mcache_sys
/inuse
更新の修正:runtime.allocmcache
関数内で、mstats.mcache_inuse
とmstats.mcache_sys
の更新が削除されました。これは、FixAlloc
の変更により、MCache
の割り当てがFixAlloc
を通じて自動的にmstats.mcache_sys
に計上されるようになったため、重複する更新が不要になったためです。 -
MemStats
のSys
合計の再計算:src/pkg/runtime/mgc0.c
のupdatememstats
関数内で、mstats.sys
の計算ロジックが更新され、新しいGCSys
とOtherSys
を含むすべてのXxxSys
カテゴリの合計として計算されるようになりました。mstats.sys = mstats.heap_sys + mstats.stacks_sys + mstats.mspan_sys + mstats.mcache_sys + mstats.buckhash_sys + mstats.gc_sys + mstats.other_sys;
これにより、
MemStats.Sys
が常にすべてのシステム割り当ての合計を正確に反映するようになります。
コアとなるコードの変更箇所
このコミットは、Goランタイムのメモリ管理の根幹に関わる多数のファイルに影響を与えています。主要な変更箇所は以下の通りです。
src/pkg/runtime/mem.go
:MemStats
構造体にGCSys
とOtherSys
フィールドを追加。src/pkg/runtime/malloc.h
:runtime.SysAlloc
,runtime.SysFree
,runtime.SysMap
,runtime.persistentalloc
,runtime.FixAlloc_Init
関数のシグネチャにuint64 *stat
引数を追加。FixAlloc
構造体のsys
フィールドをuint64*
に変更。src/pkg/runtime/malloc.goc
:runtime.MHeap_SysAlloc
、runtime.persistentalloc
の呼び出し箇所を更新し、stat
引数を渡すように変更。runtime.allocmcache
からmcache_inuse
/mcache_sys
の直接更新を削除。src/pkg/runtime/mem_darwin.c
,mem_freebsd.c
,mem_linux.c
,mem_netbsd.c
,mem_openbsd.c
,mem_plan9.c
,mem_windows.c
: 各OS固有のSysAlloc
,SysFree
,SysMap
の実装を更新し、stat
引数を受け取り、runtime.xadd64
を使用してstat
を更新するように変更。src/pkg/runtime/mfixalloc.c
:runtime.FixAlloc_Init
の実装を更新し、stat
引数を受け取り、f->stat
に保存するように変更。FixAlloc_Alloc
内でpersistentalloc
を呼び出す際にf->stat
を渡すように変更。src/pkg/runtime/mgc0.c
: GC関連のメモリ割り当て(ワークバッファ、GCルート、ファイナライザブロック、ビットマップ)をruntime.SysAlloc
やruntime.persistentalloc
で割り当てる際に、mstats.gc_sys
をstat
として渡すように変更。updatememstats
関数でmstats.sys
の計算ロジックを更新。src/pkg/runtime/mheap.c
:MSpan
やMCache
の割り当てに関連するruntime.SysAlloc
やruntime.FixAlloc_Init
の呼び出しを更新し、mstats.other_sys
やmstats.mspan_sys
、mstats.mcache_sys
をstat
として渡すように変更。src/pkg/runtime/mprof.goc
: プロファイリング関連のメモリ割り当て(バケットハッシュテーブル)をruntime.SysAlloc
やruntime.persistentalloc
で割り当てる際に、mstats.buckhash_sys
をstat
として渡すように変更。src/pkg/runtime/netpoll.goc
: ネットワークポーリングディスクリプタの割り当てをruntime.persistentalloc
で割り当てる際に、mstats.other_sys
をstat
として渡すように変更。src/pkg/runtime/stack.c
: スタックキャッシュの割り当てをruntime.SysAlloc
で割り当てる際に、mstats.stacks_sys
をstat
として渡すように変更。src/pkg/runtime/malloc_test.go
:TestMemStats
という新しいテストが追加され、MemStats
の各XxxSys
フィールドがゼロでないこと、およびSys
がすべてのXxxSys
の合計と一致することを検証しています。
コアとなるコードの解説
このコミットの核心は、GoランタイムがOSからメモリを要求するすべての箇所において、そのメモリがどの目的のために使用されるのかを明示的に指定し、それに対応するMemStats
のカウンタを更新するようにした点です。
例えば、src/pkg/runtime/malloc.goc
のruntime.MHeap_SysAlloc
関数では、ヒープ領域のためにOSからメモリを割り当てる際に、runtime.SysAlloc(n, &mstats.heap_sys)
のように&mstats.heap_sys
をstat
引数として渡しています。これにより、割り当てられたメモリ量が正確にMemStats.HeapSys
に計上されるようになります。
同様に、src/pkg/runtime/mgc0.c
では、GCのワークバッファやルートブロックなどの割り当てに&mstats.gc_sys
が使用されています。
// src/pkg/runtime/mgc0.c (抜粋)
// GCワークバッファの割り当て
work.chunk = runtime·SysAlloc(work.nchunk, &mstats.gc_sys);
// GCルートブロックの割り当て
new = (Obj*)runtime·SysAlloc(cap*sizeof(Obj), &mstats.gc_sys);
// ファイナライザブロックの割り当て
finc = runtime·persistentalloc(PageSize, 0, &mstats.gc_sys);
src/pkg/runtime/mprof.goc
では、プロファイリング用のバケットハッシュテーブルの割り当てに&mstats.buckhash_sys
が使用されています。
// src/pkg/runtime/mprof.goc (抜粋)
// プロファイリングバケットハッシュテーブルの割り当て
buckhash = runtime·SysAlloc(BuckHashSize*sizeof buckhash[0], &mstats.buckhash_sys);
persistentalloc
関数は、永続的なメモリ割り当てに使用されますが、このコミットにより、その割り当てがどのMemStats
カテゴリに属するかを柔軟に指定できるようになりました。もし指定されたstat
がデフォルトのmstats.other_sys
と異なる場合、other_sys
から減算し、指定されたstat
に加算することで、二重計上を防ぎつつ正確なカテゴリ分けを実現しています。
// src/pkg/runtime/malloc.goc (抜粋)
void*
runtime·persistentalloc(uintptr size, uintptr align, uint64 *stat)
{
// ... 既存のロジック ...
if(stat != &mstats.other_sys) {
// reaccount the allocation against provided stat
runtime·xadd64(stat, size);
runtime·xadd64(&mstats.other_sys, -(uint64)size);
}
return p;
}
これらの変更により、GoランタイムがOSから取得するすべてのメモリが、MemStats
内の適切なカテゴリに正確に計上されるようになり、MemStats.Sys
がすべてのXxxSys
カテゴリの合計と常に一致することが保証されます。これは、Goプログラムのメモリ使用状況をより透明にし、開発者がパフォーマンスの問題を診断する上で非常に役立ちます。
関連リンク
- Go Issue #5799:
runtime: MemStats.Sys is not sum of XxxSys
- このコミットが修正する問題のトラッキングイシューです。詳細な議論や背景情報が記載されています。
- https://github.com/golang/go/issues/5799
- Go Code Review 12946043:
runtime: account for all sys memory in MemStats
- このコミットのコードレビューページです。変更内容に関するコメントや議論が含まれています。
- https://golang.org/cl/12946043
参考にした情報源リンク
- Goの
MemStats
に関する公式ドキュメントや解説記事:- https://pkg.go.dev/runtime#MemStats
- https://go.dev/blog/go1.2-gc (Go 1.2のGCに関するブログ記事で、メモリ統計についても触れられている可能性があります)
- Goランタイムのメモリ管理に関する一般的な情報源:
- Goのメモリ管理の仕組みを解説しているブログ記事やドキュメント(例: GoのGCの仕組み、Goのメモリ割り当て戦略など)
- Goのソースコード(特に
src/runtime
ディレクトリ)
(注: 上記の参考情報源リンクは、一般的なGoランタイムのメモリ管理に関する情報源の例であり、このコミットに直接言及しているとは限りません。コミット当時のGoのドキュメントやコミュニティの議論を参照することで、より詳細な背景情報を得られる可能性があります。)