[インデックス 11656] ファイルの概要
このコミットは、Go言語のランタイムにおけるメモリ統計情報 (runtime.MemStats
) の取得方法を根本的に変更するものです。具体的には、既存の runtime.UpdateMemStats
関数を削除し、代わりに runtime.ReadMemStats(&stats)
という新しいAPIを導入しています。これにより、メモリ統計情報がグローバル変数として直接アクセスされるのではなく、ユーザーが提供する MemStats
構造体へのポインタを介して取得されるようになります。また、runtime.MemStats
は非公開化され、MemStatsType
は MemStats
に名称変更されています。
コミット
- コミットハッシュ: 842c906e2e9560187d4877d9f52e8f9ceb63d84c
- Author: Rémy Oudompheng oudomphe@phare.normalesup.org
- Date: Mon Feb 6 19:16:26 2012 +0100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/842c906e2e9560187d4877d9f52e8f9ceb63d84c
元コミット内容
runtime: delete UpdateMemStats, replace with ReadMemStats(&stats).
Unexports runtime.MemStats and rename MemStatsType to MemStats.
The new accessor requires passing a pointer to a user-allocated
MemStats structure.
Fixes #2572.
R=bradfitz, rsc, bradfitz, gustavo
CC=golang-dev, remy
https://golang.org/cl/5616072
変更の背景
この変更は、Go 1のリリース(2012年3月)に向けて、Goランタイムのメモリ統計情報 (runtime.MemStats
) の扱いを改善するために行われました。以前は、runtime.MemStats
はグローバル変数として公開されており、runtime.UpdateMemStats()
を呼び出すことでその内容が更新されていました。しかし、このアプローチにはいくつかの課題がありました。
- スレッドセーフティと一貫性: グローバル変数に直接アクセスし、それを更新する形式では、複数のゴルーチンが同時にメモリ統計情報にアクセスしようとした際に、データの不整合や競合状態が発生する可能性がありました。
UpdateMemStats
を呼び出すことで最新の状態に更新されるものの、その更新タイミングや、更新中に他のゴルーチンが古いデータを読み取る可能性が問題となり得ました。 - APIの明確性:
UpdateMemStats
を呼び出してからMemStats
を読み取るという二段階のプロセスは、APIの利用を複雑にしていました。開発者は、いつUpdateMemStats
を呼び出すべきか、そしてその呼び出しがどのような副作用をもたらすのかを常に意識する必要がありました。 - Go 1の安定性への貢献: Go 1は長期的な安定性と互換性を目指して設計されており、ランタイムの重要な部分であるメモリ統計情報のAPIも、より堅牢で予測可能なものにする必要がありました。Issue #2572は、このAPIの改善を具体的に提案したものです。
これらの背景から、runtime.MemStats
を非公開化し、ユーザーが明示的に MemStats
構造体を割り当て、そのポインタを runtime.ReadMemStats
に渡すことで統計情報を取得する、より安全で明確なAPIデザインへと変更されました。これにより、統計情報の取得がより制御され、スレッドセーフな方法で行われるようになりました。
前提知識の解説
Goのメモリ管理とガベージコレクション (GC)
Go言語は、自動メモリ管理(ガベージコレクション、GC)を採用しています。開発者はC++のように手動でメモリを解放する必要がなく、Goランタイムが不要になったメモリを自動的に回収します。GoのGCは、並行マーク&スイープ方式をベースにしており、プログラムの実行と並行して動作することで、アプリケーションの一時停止(ストップ・ザ・ワールド)時間を最小限に抑えるように設計されています。
runtime.MemStats
構造体
runtime.MemStats
は、Goプログラムのメモリ使用状況に関する詳細な統計情報を提供する構造体です。この構造体には、以下のような多岐にわたる情報が含まれています。
Alloc
: 現在割り当てられていて使用中のヒープメモリのバイト数。TotalAlloc
: プログラム開始以降に割り当てられた総バイト数(解放されたメモリも含む)。Sys
: オペレーティングシステムから取得したメモリの総バイト数。HeapAlloc
: ヒープに割り当てられているオブジェクトのバイト数。HeapSys
: ヒープのためにOSから取得したメモリのバイト数。HeapObjects
: ヒープに割り当てられているオブジェクトの数。NumGC
: 実行されたGCの総回数。PauseTotalNs
: GCによる一時停止時間の合計(ナノ秒)。PauseNs
: 直近のGC一時停止時間の履歴。BySize
: 特定のサイズクラスごとのアロケーション統計。
これらの統計情報は、Goアプリケーションのパフォーマンスチューニング、メモリリークの検出、GC動作の理解などに不可欠です。
以前のメモリ統計情報取得方法 (runtime.UpdateMemStats
)
このコミット以前は、runtime.MemStats
はグローバル変数として公開されており、その内容は runtime.UpdateMemStats()
関数を呼び出すことで更新されていました。開発者は、最新のメモリ統計情報を取得するために、まず runtime.UpdateMemStats()
を呼び出し、その後 runtime.MemStats
グローバル変数から必要なフィールドにアクセスしていました。
// 以前のコードの例
runtime.UpdateMemStats()
fmt.Printf("HeapAlloc: %d bytes\n", runtime.MemStats.HeapAlloc)
この方法は、グローバル変数への直接アクセスと、その更新を明示的にトリガーする必要があるという点で、いくつかの課題を抱えていました。
技術的詳細
このコミットによる技術的な変更は、Goランタイムのメモリ統計情報APIの設計思想を大きく変えるものです。
-
runtime.UpdateMemStats
の削除: 以前はメモリ統計情報を更新するために必要だったruntime.UpdateMemStats()
関数が完全に削除されました。これにより、開発者は明示的に統計情報の更新をトリガーする必要がなくなりました。 -
runtime.MemStats
の非公開化と名称変更:- 以前は
runtime.MemStats
という名前でグローバル変数として公開されていたものが、このコミットにより非公開化されました。 - 同時に、
src/pkg/runtime/mem.go
内で定義されていたMemStatsType
という構造体の型名がMemStats
に変更されました。これにより、外部からアクセス可能なMemStats
型は、新しいReadMemStats
関数を通じてのみ利用されることになります。
--- a/src/pkg/runtime/mem.go +++ b/src/pkg/runtime/mem.go @@ -6,9 +6,9 @@ package runtime import "unsafe" -type MemStatsType struct { +// A MemStats records statistics about the memory allocator. +type MemStats struct { // General statistics. - // Not locked during update; approximate. Alloc uint64 // bytes allocated and still in use TotalAlloc uint64 // bytes allocated (even if freed) Sys uint64 // bytes obtained from system (should be sum of XxxSys below) @@ -43,7 +43,6 @@ type MemStatsType struct { DebugGC bool // Per-size allocation statistics. - // Not locked during update; approximate. // 61 is NumSizeClasses in the C code. BySize [61]struct { \tSize uint32 @@ -54,21 +53,17 @@ type MemStatsType struct {\n var sizeof_C_MStats uintptr // filled in by malloc.goc +var memStats MemStats + func init() { -\tif sizeof_C_MStats != unsafe.Sizeof(MemStats) {\n-\t\tprintln(sizeof_C_MStats, unsafe.Sizeof(MemStats))\n+\tif sizeof_C_MStats != unsafe.Sizeof(memStats) {\n+\t\tprintln(sizeof_C_MStats, unsafe.Sizeof(memStats))\n \t\tpanic("MStats vs MemStatsType size mismatch") \t} } -// MemStats holds statistics about the memory system. -// The statistics may be out of date, as the information is -// updated lazily from per-thread caches. -// Use UpdateMemStats to bring the statistics up to date. -var MemStats MemStatsType - -// UpdateMemStats brings MemStats up to date. -func UpdateMemStats() +// ReadMemStats populates m with memory allocator statistics. +func ReadMemStats(m *MemStats) // GC runs a garbage collection. func GC()
- 以前は
-
runtime.ReadMemStats
の導入: 新しいruntime.ReadMemStats(m *MemStats)
関数が導入されました。この関数は、引数としてMemStats
構造体へのポインタを受け取り、そのポインタが指す構造体に現在のメモリ統計情報を書き込みます。// 新しいコードの例 var m runtime.MemStats runtime.ReadMemStats(&m) fmt.Printf("HeapAlloc: %d bytes\n", m.HeapAlloc)
この変更により、メモリ統計情報の取得は以下の点で改善されました。
- 明示的なデータ取得: 開発者は、統計情報を取得したいときに
ReadMemStats
を呼び出し、その結果を自身のMemStats
構造体に格納します。これにより、データのライフサイクルと所有権が明確になります。 - スレッドセーフティの向上:
ReadMemStats
の内部実装(src/pkg/runtime/mgc0.c
)では、統計情報を取得する際にruntime·stoptheworld()
とruntime·starttheworld()
を使用してGCを一時停止し、一貫性のあるスナップショットを取得しています。これにより、統計情報が取得される瞬間のデータの一貫性が保証されます。
--- a/src/pkg/runtime/mgc0.c +++ b/src/pkg/runtime/mgc0.c @@ -997,7 +997,7 @@ runtime·gc(int32 force)\n }\n void -runtime·UpdateMemStats(void)\n +runtime·ReadMemStats(MStats *stats)\n {\n \t// Have to acquire gcsema to stop the world,\n \t// because stoptheworld can only be used by\n @@ -1007,6 +1007,7 @@ runtime·UpdateMemStats(void)\n \tm->gcing = 1;\n \truntime·stoptheworld();\n \tcachestats(); +\t*stats = mstats;\n \tm->gcing = 0;\n \truntime·semrelease(&gcsema);\n \truntime·starttheworld(false);
- 明示的なデータ取得: 開発者は、統計情報を取得したいときに
これらの変更は、Goのメモリ統計情報APIをより堅牢で、予測可能で、スレッドセーフなものにするための重要なステップでした。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は、以下のファイルに集中しています。
-
src/pkg/runtime/mem.go
:MemStatsType
構造体の名称がMemStats
に変更されました。runtime.MemStats
グローバル変数が削除され、代わりにvar memStats MemStats
という非公開の変数として内部的に管理されるようになりました。UpdateMemStats()
関数の宣言が削除され、ReadMemStats(m *MemStats)
関数の宣言が追加されました。
-
src/pkg/runtime/mgc0.c
:- C言語で実装されている
runtime·UpdateMemStats
関数がruntime·ReadMemStats
に名称変更され、引数としてMStats *stats
を受け取るように変更されました。 - この関数内で、内部の
mstats
構造体の内容が引数で渡されたstats
ポインタにコピーされるようになりました (*stats = mstats;
)。
- C言語で実装されている
-
src/cmd/godoc/godoc.go
およびsrc/pkg/*/
以下のテストファイル群:runtime.MemStats
への直接アクセスやruntime.UpdateMemStats()
の呼び出しが、新しいruntime.ReadMemStats()
を使用する形式に一括で変更されました。これは、Goエコシステム全体にわたるAPI変更の影響を示しています。
例:
src/cmd/godoc/godoc.go
--- a/src/cmd/godoc/godoc.go +++ b/src/cmd/godoc/godoc.go @@ -1510,9 +1510,12 @@ func updateIndex() { \t\tlog.Printf("index updated (%gs, %d bytes of source, %d files, %d lines, %d unique words, %d spots)", \t\t\tsecs, stats.Bytes, stats.Files, stats.Lines, stats.Words, stats.Spots) \t} -\tlog.Printf("before GC: bytes = %d footprint = %d", runtime.MemStats.HeapAlloc, runtime.MemStats.Sys) +\tmemstats := new(runtime.MemStats) +\truntime.ReadMemStats(memstats) +\tlog.Printf("before GC: bytes = %d footprint = %d", memstats.HeapAlloc, memstats.Sys) \truntime.GC() -\tlog.Printf("after GC: bytes = %d footprint = %d", runtime.MemStats.HeapAlloc, runtime.MemStats.Sys) +\truntime.ReadMemStats(memstats) +\tlog.Printf("after GC: bytes = %d footprint = %d", memstats.HeapAlloc, memstats.Sys) } func indexer() {
コアとなるコードの解説
このコミットの核心は、メモリ統計情報の取得方法を「グローバル変数への直接アクセスと明示的な更新」から「ユーザーが提供する構造体へのポインタを介したデータコピー」へと変更した点にあります。
src/pkg/runtime/mem.go
の変更
-
type MemStats struct { ... }
: 以前はMemStatsType
という名前だった構造体がMemStats
に変更され、これが外部に公開されるメモリ統計情報の型となりました。この構造体自体は、メモリ使用量、GC回数、一時停止時間など、Goランタイムのメモリ管理に関する詳細な情報を含んでいます。 -
var memStats MemStats
: この行は、ランタイム内部でメモリ統計情報を保持するための非公開のMemStats
型の変数を宣言しています。以前のvar MemStats MemStatsType
(公開変数) とは異なり、このmemStats
は外部から直接アクセスできません。 -
func ReadMemStats(m *MemStats)
: この関数が新しいAPIのエントリポイントです。m *MemStats
という引数は、呼び出し元がMemStats
型の構造体を事前に割り当て、そのアドレス(ポインタ)をこの関数に渡す必要があることを示しています。ReadMemStats
は、ランタイム内部の最新のメモリ統計情報を、このm
が指す構造体にコピーします。これにより、呼び出し元は常に最新かつ一貫性のある統計情報のスナップショットを受け取ることができます。
src/pkg/runtime/mgc0.c
の変更
-
void runtime·ReadMemStats(MStats *stats)
: C言語で実装されたこの関数は、Go言語のruntime.ReadMemStats
から呼び出されます。引数のMStats *stats
は、Go側から渡されたMemStats
構造体へのポインタに対応します。 -
runtime·stoptheworld();
とruntime·starttheworld(false);
: この部分が、統計情報取得の原子性を保証する重要なメカニズムです。stoptheworld()
は、Goランタイム内のすべてのゴルーチン(GCゴルーチンを除く)の実行を一時停止させます。これにより、メモリ統計情報が更新される最中に他のゴルーチンがアクセスして不整合なデータを読み取ることを防ぎます。統計情報のコピーが完了した後、starttheworld()
でゴルーチンの実行が再開されます。 -
*stats = mstats;
: この行が、実際のデータコピーを行っています。ランタイム内部で管理されている最新のメモリ統計情報 (mstats
) が、引数で渡されたstats
ポインタが指すメモリ領域にコピーされます。これにより、呼び出し元は、stoptheworld
中に取得された、その時点での正確なメモリ統計情報のスナップショットを受け取ることができます。
これらの変更により、Goのメモリ統計情報APIは、より安全で、より予測可能で、そしてよりGoらしい(ポインタを介した明示的なデータ受け渡し)設計へと進化しました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/842c906e2e9560187d4877d9f52e8f9ceb63d84c
- Go Issue #2572: https://golang.org/issue/2572 (このコミットが修正したIssue)
- Go CL 5616072: https://golang.org/cl/5616072 (このコミットに対応するGo Code Reviewのチェンジリスト)
参考にした情報源リンク
- Go 1 Release Notes (Memory Statistics changes): https://go.dev/doc/go1 (Go 1のリリースノートには、
runtime.MemStats
の変更に関する言及があります。) - Go 1.1 Release Notes (Heap Allocator improvements): https://go.dev/doc/go1.1 (Go 1.1でのさらなる改善点も関連情報として参照しました。)
- Go issue 2572 runtime.MemStats: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFOXF8EFWb1I9SPv3l0z1ufZAtQr17_grXZiZtUgaUIv6YPx0CRt2DJoU2U9mai_8Hw78MzZvuzx54aJfl9b0hMiDGZU-DWgDaFRyBGX7BDwY83xLX5DziiNeOthQghQoUIRQzKel4UUplbjyijtd4vEKY2nE0GYu82LlcRslsJ0bpvLUg81ehkjZhPp-CTpeB5stcKF-t84s7kAClF0pPV
- Web search results for "Go runtime.MemStats UpdateMemStats ReadMemStats changes 2012": https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEl5w8h3wvAlYvO0BpjBY1xp09OmOmT1da69c5J3DQmb4pmySSsW7NWVtoqLB15GBKnaqC87Eiykf0eD1Rqufcn1e5-cit0HqVnBLEAd33ZZcg=
- h-da.de (Go runtime.MemStats changes): https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE3SBp15BOGUfZfnNgLht3NfzAOv01nX6zWuwS9LbMydtYG57N3ZPz6KXhbLYg-S_65eNrdnpAhGmlX4pThNUERVE7e6U8u4mEXiuDdy-eQZwEJq-pxYHjkviwshDQc_kSJOLNZdf63iGvyLj97RqqQAeybIht0NmdHgvl8I7yYXTkPLwcTTXnXjKiqv9gjjBps_acEMo6xdfy0Lfjg73yzqg==