[インデックス 15127] ファイルの概要
このコミットは、Go言語のランタイムにおけるガベージコレクタ (GC) の制御機能を追加するものです。具体的には、runtime/debug
パッケージにGCの統計情報を読み取る機能、GCの動作を調整する機能、およびOSへのメモリ解放を強制する機能が導入されました。
変更されたファイルは以下の通りです。
src/pkg/go/build/deps_test.go
: ビルド依存関係のテストファイル。runtime/debug
パッケージの依存関係にtime
パッケージが追加されました。src/pkg/runtime/debug/debug.c
: 新規追加されたC言語のファイル。runtime/debug
パッケージの一部がC言語で実装されていることをGoコマンドに伝えるためのプレースホルダーです。src/pkg/runtime/debug/garbage.go
: 新規追加されたGo言語のファイル。GC統計情報 (GCStats
) の構造体定義、ReadGCStats
、SetGCPercent
、FreeOSMemory
関数が実装されています。src/pkg/runtime/debug/garbage_test.go
: 新規追加されたGo言語のテストファイル。garbage.go
で追加された機能のテストが含まれています。src/pkg/runtime/debug/stack_test.go
: 既存のスタックテストファイル。パッケージ名がdebug
からdebug_test
に変更され、runtime/debug
パッケージのインポート方法が変更されました。src/pkg/runtime/malloc.h
: ランタイムのメモリ割り当てに関するヘッダファイル。GCStats
構造体の前方宣言が追加され、MStats
構造体内のGC統計情報の保護に関するコメントが更新されました。src/pkg/runtime/mgc0.c
: ランタイムのガベージコレクタのコアロジックを扱うC言語のファイル。GOGC
環境変数の読み込みロジックがreadgogc
関数に分離され、runtime/debug
パッケージから呼び出されるruntime∕debug·readGCStats
およびruntime∕debug·setGCPercent
関数が追加されました。src/pkg/runtime/mheap.c
: ランタイムのヒープ管理を扱うC言語のファイル。未使用メモリをOSに解放するscavenge
関数が導入され、runtime/debug
パッケージから呼び出されるruntime∕debug·freeOSMemory
関数が追加されました。また、MHeap_Scavenger
関数内のメモリ解放ロジックがscavenge
関数に置き換えられました。
コミット
commit 472354f81e6c413508b3f2f43c0f321c4500bacd
Author: Russ Cox <rsc@golang.org>
Date: Mon Feb 4 00:00:55 2013 -0500
runtime/debug: add controls for garbage collector
Fixes #4090.
R=golang-dev, iant, bradfitz, dsymonds
CC=golang-dev
https://golang.org/cl/7229070
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/472354f81e6c413508b3f2f43c0f321c4500bacd
元コミット内容
runtime/debug: add controls for garbage collector
Fixes #4090.
R=golang-dev, iant, bradfitz, dsymonds
CC=golang-dev
https://golang.org/cl/7229070
変更の背景
このコミットは、Go言語のガベージコレクタ (GC) の動作をより詳細に制御し、監視するための機能を提供することを目的としています。コミットメッセージにある Fixes #4090
は、GoのIssueトラッカーにおける特定の課題を解決することを示唆しています。
GoのGCは、通常、自動的に最適なパフォーマンスを発揮するように設計されていますが、特定のアプリケーションやデバッグのシナリオでは、開発者がGCの動作を調整したり、その統計情報を取得したりする必要が生じることがあります。例えば、メモリ使用量の最適化、レイテンシの削減、GCによる一時停止時間の分析などが挙げられます。
この変更以前は、GCの動作をプログラムから直接制御する公式なAPIは限られていました。runtime
パッケージには runtime.GC()
のような関数は存在しましたが、GCの統計情報を詳細に取得したり、GCのトリガー条件を調整したりする機能は提供されていませんでした。
このコミットは、これらのニーズに応えるために、runtime/debug
パッケージに新しいAPIを追加することで、開発者がGoアプリケーションのメモリ管理とパフォーマンスをより深く理解し、調整できるようにすることを目的としています。特に、GCの一時停止時間(Pause time)の統計は、リアルタイム性の高いアプリケーションにおいて非常に重要であり、その情報を取得できるようになったことは大きな進歩です。
前提知識の解説
このコミットの理解を深めるために、以下の前提知識が役立ちます。
Go言語のガベージコレクション (GC)
Go言語は、自動メモリ管理のためにガベージコレクタを採用しています。開発者は手動でメモリを解放する必要がなく、GCが不要になったメモリを自動的に回収します。GoのGCは、並行マーク&スイープ方式をベースにしており、アプリケーションの実行と並行して動作することで、GCによる一時停止時間(Stop-the-world pause)を最小限に抑えるように設計されています。
- マークフェーズ: GCが到達可能なオブジェクト(使用中のメモリ)をマークします。
- スイープフェーズ: マークされていないオブジェクト(不要なメモリ)を解放します。
GoのGCは、ヒープの使用量に基づいてGCをトリガーします。具体的には、前回のGC後に生き残ったオブジェクトの量(ライブデータ)に対して、新しく割り当てられたメモリの量が一定の割合(GCパーセンテージ)に達すると、次のGCが実行されます。
runtime
パッケージ
runtime
パッケージは、Goランタイムシステムとのインタフェースを提供します。これには、ゴルーチン管理、スケジューリング、メモリ割り当て、GCなどの低レベルな機能が含まれます。通常、開発者が直接このパッケージの関数を呼び出すことは稀ですが、パフォーマンスのプロファイリングやデバッグの際には利用されることがあります。
runtime/debug
パッケージ
runtime/debug
パッケージは、Goプログラムのデバッグ情報やランタイムの内部状態にアクセスするための機能を提供します。このパッケージの関数は、主にデバッグやプロファイリングの目的で使用され、通常のアプリケーションロジックで頻繁に利用されることはありません。例えば、スタックトレースの取得や、メモリ統計情報のダンプなどが可能です。
GOGC
環境変数
GOGC
環境変数は、Goのガベージコレクタの動作を制御するための設定です。この変数は、GCがトリガーされるヒープの成長率をパーセンテージで指定します。例えば、GOGC=100
は、ライブデータが100%増加したときにGCが実行されることを意味します。GOGC=off
に設定すると、GCが無効になります(ただし、メモリが枯渇すると強制的にGCが実行される可能性があります)。この環境変数は、アプリケーションの起動時に読み込まれ、GCの初期設定として使用されます。
time.Duration
time.Duration
は、Goの time
パッケージで定義されている型で、時間の長さをナノ秒単位で表す整数型です。GCの一時停止時間などの時間間隔を表現するのに使用されます。
技術的詳細
このコミットでは、主に以下の3つの新しい機能が runtime/debug
パッケージに追加されました。
-
ReadGCStats(stats *GCStats)
:- この関数は、Goランタイムのガベージコレクションに関する統計情報を
GCStats
構造体に読み込みます。 GCStats
構造体には、最後のGCの時刻 (LastGC
)、GCの実行回数 (NumGC
)、全GCの一時停止時間の合計 (PauseTotal
)、個々のGCの一時停止時間の履歴 (Pause
)、そして一時停止時間の分布のクォンタイル値 (PauseQuantiles
) が含まれます。Pause
スライスは、ランタイムが追跡する一時停止履歴を格納します。stats.Pause
の容量が十分であれば再利用され、そうでなければ再割り当てされます。PauseQuantiles
は、GC一時停止時間の分布を要約するために使用されます。例えば、長さが5のPauseQuantiles
スライスを渡すと、最小値、25パーセンタイル、50パーセンタイル(中央値)、75パーセンタイル、最大値が格納されます。- この関数は、内部的にランタイムのCコード (
runtime∕debug·readGCStats
) を呼び出して、生のGC統計データ(ナノ秒単位の一時停止時間、最後のGC時刻、GC回数、合計一時停止時間)を取得します。 - 取得した一時停止時間の履歴は、必要に応じてソートされ、クォンタイル値が計算されます。
- この関数は、Goランタイムのガベージコレクションに関する統計情報を
-
SetGCPercent(percent int) int
:- この関数は、ガベージコレクションのトリガーとなるヒープの成長率(GCパーセンテージ)を設定します。
- GCは、前回のGC後に生き残ったライブデータに対する新しく割り当てられたデータの比率がこのパーセンテージに達したときにトリガーされます。
- 例えば、
SetGCPercent(100)
はデフォルトの動作であり、ライブデータが100%増加するとGCが実行されます。 - 負の値を設定すると、ガベージコレクションが無効になります(ただし、メモリが枯渇すると強制的にGCが実行される可能性があります)。
- この関数は、設定前のGCパーセンテージを返します。
- 内部的には、ランタイムのCコード (
runtime∕debug·setGCPercent
) を呼び出して、グローバルなgcpercent
変数を更新します。
-
FreeOSMemory()
:- この関数は、ガベージコレクションを強制的に実行した後、可能な限り多くのメモリをオペレーティングシステムに返却しようとします。
- Goランタイムは、バックグラウンドタスクで徐々にOSにメモリを返却しますが、この関数を呼び出すことで、より積極的にメモリを解放することができます。これは、特にメモリ使用量がピークに達した後、システムリソースを解放したい場合に有用です。
- 内部的には、まず
runtime.GC(1)
を呼び出して強制的にGCを実行し、その後runtime.mheap
をロックし、scavenge
関数を呼び出して未使用のメモリをOSに返却します。
ランタイム内部の変更点
-
src/pkg/runtime/mgc0.c
:gcpercent
変数の初期値が-2
からGcpercentUnknown
マクロ(値は-2
)に変更され、より明確になりました。GOGC
環境変数を読み込むロジックがreadgogc()
関数として分離され、コードの可読性と再利用性が向上しました。runtime∕debug·readGCStats
関数が追加され、GoのReadGCStats
関数からの呼び出しを受け付け、ランタイム内部のGC統計データ(mstats.pause_ns
,mstats.last_gc
,mstats.numgc
,mstats.pause_total_ns
)をGoのSlice
にコピーして返します。runtime∕debug·setGCPercent
関数が追加され、GoのSetGCPercent
関数からの呼び出しを受け付け、gcpercent
変数を更新します。
-
src/pkg/runtime/mheap.c
:scavengelist
関数とscavenge
関数が新しく導入されました。これらの関数は、ヒープ内の未使用のメモリ領域を走査し、一定期間使用されていないメモリをOSに返却する役割を担います。scavenge
関数は、mheap
のfree
リストとlarge
リストを走査し、unusedsince
の値がlimit
を超えるMSpan
のメモリをruntime·SysUnused
を使ってOSに返却します。- 既存の
runtime·MHeap_Scavenger
関数内のメモリ解放ロジックが、新しく導入されたscavenge
関数を呼び出すようにリファクタリングされました。これにより、コードの重複が排除され、よりモジュール化されました。 runtime∕debug·freeOSMemory
関数が追加され、GoのFreeOSMemory
関数からの呼び出しを受け付け、強制GCの後にscavenge
関数を呼び出してOSへのメモリ解放をトリガーします。
これらの変更により、Goのランタイムは、GCの動作に関するより詳細な情報を提供し、開発者がプログラムからGCの挙動を調整するための強力なツールを提供することになります。
コアとなるコードの変更箇所
src/pkg/runtime/debug/garbage.go
(新規追加)
package debug
import (
"runtime"
"sort"
"time"
)
// GCStats collect information about recent garbage collections.
type GCStats struct {
LastGC time.Time // time of last collection
NumGC int64 // number of garbage collections
PauseTotal time.Duration // total pause for all collections
Pause []time.Duration // pause history, most recent first
PauseQuantiles []time.Duration
}
// Implemented in package runtime.
func readGCStats(*[]time.Duration)
func enableGC(bool) bool
func setGCPercent(int) int
func freeOSMemory()
// ReadGCStats reads statistics about garbage collection into stats.
// ... (省略) ...
func ReadGCStats(stats *GCStats) {
// ... (実装詳細) ...
}
type byDuration []time.Duration
func (x byDuration) Len() int { return len(x) }
func (x byDuration) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byDuration) Less(i, j int) bool { return x[i] < x[j] }
// SetGCPercent sets the garbage collection target percentage:
// ... (省略) ...
func SetGCPercent(percent int) int {
return setGCPercent(percent)
}
// FreeOSMemory forces a garbage collection followed by an
// ... (省略) ...
func FreeOSMemory() {
freeOSMemory()
}
src/pkg/runtime/mgc0.c
#define GcpercentUnknown (-2) // 新規追加
// ... (中略) ...
static int32
readgogc(void) // 新規追加
{
byte *p;
p = runtime·getenv("GOGC");
if(p == nil || p[0] == '\0')
return 100;
if(runtime·strcmp(p, (byte*)"off") == 0)
return -1;
return runtime·atoi(p);
}
// ... (中略) ...
void
runtime∕debug·readGCStats(Slice *pauses) // 新規追加
{
uint64 *p;
uint32 i, n;
// ... (実装詳細) ...
}
void
runtime∕debug·setGCPercent(intgo in, intgo out) // 新規追加
{
runtime·lock(&runtime·mheap);
if(gcpercent == GcpercentUnknown)
gcpercent = readgogc();
out = gcpercent;
if(in < 0)
in = -1;
gcpercent = in;
runtime·unlock(&runtime·mheap);
FLUSH(&out);
}
src/pkg/runtime/mheap.c
static uintptr
scavengelist(MSpan *list, uint64 now, uint64 limit) // 新規追加
{
// ... (実装詳細) ...
}
static uintptr
scavenge(uint64 now, uint64 limit) // 新規追加
{
uint32 i;
uintptr sumreleased;
MHeap *h;
h = &runtime·mheap;
sumreleased = 0;
for(i=0; i < nelem(h->free); i++)
sumreleased += scavengelist(&h->free[i], now, limit);
sumreleased += scavengelist(&h->large, now, limit);
return sumreleased;
}
// ... (中略) ...
void
runtime·MHeap_Scavenger(void)
{
// ... (中略) ...
// 変更前: sumreleased = 0; forループでメモリ解放ロジックが直接記述されていた
// 変更後:
sumreleased = scavenge(now, limit); // scavenge関数を呼び出すように変更
// ... (中略) ...
}
void
runtime∕debug·freeOSMemory(void) // 新規追加
{
runtime·gc(1);
runtime·lock(&runtime·mheap);
scavenge(~(uintptr)0, 0);
runtime·unlock(&runtime·mheap);
}
コアとなるコードの解説
src/pkg/runtime/debug/garbage.go
このファイルは、Go言語で書かれた runtime/debug
パッケージの新しいAPIを定義しています。
GCStats
構造体は、GCに関する様々な統計情報を保持するためのコンテナです。特にPause
とPauseQuantiles
は、GCによるアプリケーションの一時停止時間を詳細に分析するために重要です。readGCStats
,enableGC
,setGCPercent
,freeOSMemory
は、GoランタイムのC言語部分で実装されている関数への外部宣言です。GoのコードからCのランタイム関数を呼び出すためのブリッジとして機能します。ReadGCStats
関数は、GCStats
構造体へのポインタを受け取り、ランタイムからGC統計情報を取得して構造体を埋めます。一時停止時間のクォンタイル計算ロジックもここに実装されています。byDuration
型は、time.Duration
スライスをソートするためのsort.Interface
を実装しています。SetGCPercent
関数は、GCパーセンテージを設定するための高レベルなGo APIです。内部的にはsetGCPercent
ランタイム関数を呼び出します。FreeOSMemory
関数は、強制GCとOSへのメモリ解放をトリガーするための高レベルなGo APIです。内部的にはfreeOSMemory
ランタイム関数を呼び出します。
src/pkg/runtime/mgc0.c
このファイルは、GoランタイムのGCのコアロジックをC言語で実装しています。
GcpercentUnknown
マクロは、gcpercent
変数の初期状態を示す定数として導入され、コードの意図を明確にしています。readgogc
関数は、GOGC
環境変数の値を読み込み、GCパーセンテージとして解釈するロジックをカプセル化しています。これにより、gc
関数内の初期化ロジックが簡素化されました。runtime∕debug·readGCStats
関数は、GoのReadGCStats
関数から呼び出されるランタイム側の実装です。mstats
構造体(ランタイムのメモリ統計情報)から、GCの一時停止履歴、最後のGC時刻、GC回数、合計一時停止時間などの生データを取得し、GoのSlice
にコピーして返します。一時停止履歴は循環バッファに格納されているため、最新のデータから順にコピーするロジックが含まれています。runtime∕debug·setGCPercent
関数は、GoのSetGCPercent
関数から呼び出されるランタイム側の実装です。gcpercent
グローバル変数を更新し、新しいGCパーセンテージを設定します。
src/pkg/runtime/mheap.c
このファイルは、Goランタイムのヒープ管理とメモリ解放ロジックをC言語で実装しています。
scavengelist
関数は、特定のMSpan
リスト(メモリブロックのリスト)を走査し、一定期間使用されていないメモリ領域をOSに返却する(runtime·SysUnused
を呼び出す)役割を担います。scavenge
関数は、mheap
構造体のfree
リストとlarge
リスト(様々なサイズの空きメモリブロックのリスト)を走査し、scavengelist
を呼び出して未使用メモリをOSに返却します。これは、メモリの「スカベンジング」(清掃)プロセスを抽象化したものです。runtime·MHeap_Scavenger
関数は、ランタイムのバックグラウンドで動作し、定期的に未使用メモリをOSに返却するゴルーチンです。このコミットでは、以前直接記述されていたメモリ解放ロジックが、新しく導入されたscavenge
関数を呼び出すように変更されました。これにより、コードの重複が排除され、よりクリーンな実装になりました。runtime∕debug·freeOSMemory
関数は、GoのFreeOSMemory
関数から呼び出されるランタイム側の実装です。まずruntime·gc(1)
を呼び出して強制的にGCを実行し、その後scavenge
関数を呼び出して、可能な限り多くのメモリをOSに返却します。
これらの変更により、Goのランタイムは、GCの動作に関するより詳細な情報を提供し、開発者がプログラムからGCの挙動を調整するための強力なツールを提供することになります。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/472354f81e6c413508b3f2f43c0f321c4500bacd
- Go Change List (CL) 7229070: https://golang.org/cl/7229070
参考にした情報源リンク
- Go Change List (CL) 7229070: https://golang.org/cl/7229070
- Go言語の公式ドキュメント (runtime/debugパッケージ): https://pkg.go.dev/runtime/debug (コミット当時のバージョンとは異なる可能性がありますが、現在のAPIの理解に役立ちます)
- Go言語のガベージコレクションに関する記事やドキュメント (一般的な情報源):
- A Guide to the Go Garbage Collector: https://go.dev/doc/gc-guide
- Go's new GC: https://blog.golang.org/go15gc (このコミットより後のGo 1.5でのGC改善に関する記事ですが、GCの基本的な概念理解に役立ちます)
- Go言語の
GOGC
環境変数に関する情報: https://pkg.go.dev/runtime#hdr-Environment_Variables