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

[インデックス 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) の構造体定義、ReadGCStatsSetGCPercentFreeOSMemory 関数が実装されています。
  • 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 パッケージに追加されました。

  1. 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回数、合計一時停止時間)を取得します。
    • 取得した一時停止時間の履歴は、必要に応じてソートされ、クォンタイル値が計算されます。
  2. SetGCPercent(percent int) int:

    • この関数は、ガベージコレクションのトリガーとなるヒープの成長率(GCパーセンテージ)を設定します。
    • GCは、前回のGC後に生き残ったライブデータに対する新しく割り当てられたデータの比率がこのパーセンテージに達したときにトリガーされます。
    • 例えば、SetGCPercent(100) はデフォルトの動作であり、ライブデータが100%増加するとGCが実行されます。
    • 負の値を設定すると、ガベージコレクションが無効になります(ただし、メモリが枯渇すると強制的にGCが実行される可能性があります)。
    • この関数は、設定前のGCパーセンテージを返します。
    • 内部的には、ランタイムのCコード (runtime∕debug·setGCPercent) を呼び出して、グローバルな gcpercent 変数を更新します。
  3. 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 関数は、mheapfree リストと 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に関する様々な統計情報を保持するためのコンテナです。特に PausePauseQuantiles は、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の挙動を調整するための強力なツールを提供することになります。

関連リンク

参考にした情報源リンク