[インデックス 15553] ファイルの概要
このコミットは、Goランタイムのガベージコレクション(GC)システムテストである TestGcSys を、メインのテストプロセスから分離された独立したプロセスで実行するように変更するものです。これにより、テストの安定性が向上し、ヒープサイズが大きくなることによるテストの失敗が回避されます。
コミット
commit 46890f60cee89ffef7a9b5f2b8d5e263650f61f7
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Sat Mar 2 08:36:06 2013 +0200
runtime: move TestGcSys into a separate process
Fixes #4904.
The problem was that when the test runs the heap had grown to ~100MB,
so GC allows it to grow to 200MB, and so the test fails.
Moving the test to a separate process makes it much more isolated and stable.
R=golang-dev, minux.ma
CC=golang-dev
https://golang.org/cl/7441046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/46890f60cee89ffef7a9b5f2b8d5e263650f61f7
元コミット内容
runtime: move TestGcSys into a separate process
Fixes #4904.
The problem was that when the test runs the heap had grown to ~100MB,
so GC allows it to grow to 200MB, and so the test fails.
Moving the test to a separate process makes it much more isolated and stable.
変更の背景
TestGcSys は、Goランタイムのガベージコレクション(GC)がシステムメモリを適切に解放するかどうかを検証するためのテストです。このテストは、大量のメモリを割り当て、GCが実行された後にシステムメモリの使用量が一定の閾値を超えないことを確認します。
しかし、このテストが既存のテストスイート内で実行されると、テスト開始時にはすでにヒープサイズが約100MBに達しているという問題がありました。GoのGCは、ヒープサイズが一定の割合(デフォルトでは2倍)に達するまでGCを実行しないという挙動があります。このため、テストが開始された時点でヒープが100MBあると、GCはヒープが200MBに達するまで実行されず、結果として TestGcSys が期待するメモリ使用量の閾値(16MB)を超えてしまい、テストが失敗するという不安定な状況が発生していました。
この問題を解決するため、TestGcSys を独立したプロセスで実行することが決定されました。独立したプロセスで実行することで、テストはクリーンな状態のヒープから開始され、他のテストの影響を受けることなく、GCの挙動を正確に検証できるようになります。これにより、テストの分離性が高まり、安定性が向上します。
前提知識の解説
- Goのガベージコレクション (GC): Goは自動メモリ管理(ガベージコレクション)を採用しています。プログラムが不要になったメモリを自動的に解放し、開発者が手動でメモリを管理する手間を省きます。GoのGCは、ヒープの使用量に基づいてトリガーされることが多く、ヒープが一定のサイズに達するとGCが実行されます。
runtime.MemStats: Goのruntimeパッケージが提供する構造体で、Goプログラムのメモリ使用状況に関する詳細な統計情報を含みます。これには、ヒープの使用量、GCの実行回数、システムに割り当てられたメモリ量などが含まれます。runtime.GC():runtimeパッケージの関数で、明示的にガベージコレクションを実行します。通常はGCが自動的に実行されますが、特定のテストシナリオやデバッグ目的で手動でトリガーすることがあります。runtime.GOMAXPROCS(): Goプログラムが同時に実行できるOSスレッドの最大数を設定します。このテストではruntime.GOMAXPROCS(1)を設定しており、単一のCPUコアで実行されることを保証し、GCの挙動をより予測可能にしています。testing.Short(): Goのtestingパッケージの関数で、テストが「ショートモード」で実行されているかどうかを返します。go test -shortコマンドでショートモードを有効にできます。このモードでは、時間がかかるテストをスキップしたり、テストのイテレーション回数を減らしたりすることが一般的です。- テストの分離: ソフトウェアテストにおいて、各テストが互いに独立して実行されることを指します。これにより、あるテストの実行結果が別のテストに影響を与えることがなくなり、テストの信頼性と再現性が向上します。特にメモリ使用量やグローバルな状態に依存するテストでは、分離が重要になります。独立したプロセスでテストを実行することは、強力な分離メカニズムの一つです。
技術的詳細
このコミットの主要な変更点は、TestGcSys テストを gc_test.go ファイル内で直接実行するのではなく、独立したGoプログラムとしてコンパイルし、それを実行するというアプローチに切り替えたことです。
- テストソースの埋め込み:
testGCSysSourceという定数に、TestGcSysのロジックを含む完全なGoプログラムのソースコードが文字列として埋め込まれています。このソースコードは、mainパッケージとmain関数を持ち、独立して実行可能な形式です。 - テンプレートエンジンの利用: 埋め込まれたソースコードには
{{if .Short}}と{{end}}といったGoのテンプレート構文に似たプレースホルダーが含まれています。これは、testing.Short()の値に基づいてitercountを調整するためのものです。executeTest関数内で、このテンプレートが処理され、実際のテストコードが生成されます。 executeTest関数の導入:TestGcSysは、executeTestというヘルパー関数を呼び出すように変更されました。この関数は以下の処理を行います。- 埋め込まれた
testGCSysSourceを受け取り、必要に応じてテンプレートを処理します。 - 処理されたソースコードを一時ファイルに書き込みます。
- その一時ファイルを独立したGoプログラムとしてコンパイルし、実行します。
- 実行結果(標準出力)をキャプチャし、呼び出し元に返します。
- 埋め込まれた
- 出力の変更: 元の
TestGcSysでは、メモリ使用量が閾値を超えた場合にt.Fatalfを使用してテストを失敗させていました。独立プロセスに移行したことで、テストの失敗はプロセスの終了コードや標準出力のメッセージによって判断される必要があります。そのため、t.Fatalfはfmt.Printfに変更され、成功時には "OK"、失敗時にはメモリ使用量を出力するように変更されました。これにより、executeTestがこれらの出力を解析してテストの成否を判断できるようになります。 runtime.GOMAXPROCS(1)の移動: 元のテストではdefer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))を使用していましたが、独立プロセスではmain関数内で直接runtime.GOMAXPROCS(1)を呼び出すように変更されました。これにより、テストが常に単一のCPUコアで実行されることが保証されます。
この変更により、TestGcSys は他のテストのヒープ状態に影響されることなく、常にクリーンな環境でGCの挙動を検証できるようになり、テストの信頼性と安定性が大幅に向上しました。
コアとなるコードの変更箇所
src/pkg/runtime/gc_test.go ファイルが変更されました。
--- a/src/pkg/runtime/gc_test.go
+++ b/src/pkg/runtime/gc_test.go
@@ -14,7 +14,24 @@ func TestGcSys(t *testing.T) {
if os.Getenv("GOGC") == "off" {
t.Fatalf("GOGC=off in environment; test cannot pass")
}
- defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
+ data := struct{ Short bool }{testing.Short()}
+ got := executeTest(t, testGCSysSource, &data)
+ want := "OK\\n"
+ if got != want {
+ t.Fatalf("expected %q, but got %q", want, got)
+ }
+}
+
+const testGCSysSource = `
+package main
+
+import (
+ "fmt"
+ "runtime"
+)
+
+func main() {
+ runtime.GOMAXPROCS(1)
memstats := new(runtime.MemStats)
runtime.GC()
runtime.ReadMemStats(memstats)
@@ -23,9 +40,9 @@ func TestGcSys(t *testing.T) {
runtime.MemProfileRate = 0 // disable profiler
itercount := 1000000
- if testing.Short() {
- itercount = 100000
- }
+{{if .Short}}
+ itercount = 100000
+{{end}}
for i := 0; i < itercount; i++ {
workthegc()
}
@@ -38,15 +55,17 @@ func TestGcSys(t *testing.T) {
} else {
sys = memstats.Sys - sys
}
- t.Logf("used %d extra bytes", sys)
if sys > 16<<20 {
- t.Fatalf("using too much memory: %d bytes", sys)
+ fmt.Printf("using too much memory: %d bytes\\n", sys)
+ return
}
+ fmt.Printf("OK\\n")
+}
func workthegc() []byte {\n \treturn make([]byte, 1029)\n }\n+`
func TestGcDeepNesting(t *testing.T) {
type T [2][2][2][2][2][2][2][2][2][2]*int
コアとなるコードの解説
-
TestGcSys関数の変更:- 元の
TestGcSysの本体は削除され、代わりにexecuteTest関数を呼び出すようになりました。 data := struct{ Short bool }{testing.Short()}:testing.Short()の結果をShortフィールドに持つ匿名構造体を作成し、これをexecuteTestに渡します。これは、埋め込まれたテストソース内でショートモードの挙動を制御するために使用されます。got := executeTest(t, testGCSysSource, &data):testGCSysSourceに定義されたテストコードを独立プロセスで実行し、その標準出力をgotに格納します。want := "OK\\n": 期待される出力は "OK" と改行です。if got != want { t.Fatalf("expected %q, but got %q", want, got) }: 独立プロセスからの出力が "OK\n" でない場合、テストを失敗させます。
- 元の
-
testGCSysSource定数:- これは、独立プロセスで実行されるGoプログラムのソースコードを文字列として保持する定数です。
package mainとfunc main()を持ち、独立した実行ファイルとして機能します。runtime.GOMAXPROCS(1):main関数内でGOMAXPROCSを1に設定し、単一のCPUコアで実行されることを保証します。memstats := new(runtime.MemStats): メモリ統計情報を格納するための構造体を初期化します。runtime.GC(): 明示的にGCを実行します。runtime.ReadMemStats(memstats): 現在のメモリ統計情報を取得します。itercountの調整:{{if .Short}} itercount = 100000 {{end}}: これはGoのテンプレート構文に似ており、executeTest関数内でdata.Shortがtrueの場合(つまり、go test -shortで実行された場合)にitercountを100000に設定します。これにより、ショートモードでのテスト時間を短縮します。
- メモリ使用量のチェックと出力:
if sys > 16<<20 { fmt.Printf("using too much memory: %d bytes\\n", sys); return }: システムメモリの使用量が16MB(16<<20は16 * 2^20バイト、つまり16MB)を超えた場合、エラーメッセージを出力してプログラムを終了します。fmt.Printf("OK\\n"): メモリ使用量が閾値内であれば "OK" を出力します。
-
workthegc()関数:return make([]byte, 1029): 1029バイトのバイトスライスを割り当てて返します。これはGCをトリガーするための「作業」をシミュレートします。この関数がループ内で繰り返し呼び出されることで、ヒープに一時的なオブジェクトが生成され、GCの対象となります。
この変更により、TestGcSys は独立した環境で実行され、他のテストの影響を受けずにGCの挙動を正確に検証できるようになりました。
関連リンク
- Go Issue #4904: https://github.com/golang/go/issues/4904 (このコミットが修正した問題)
- Go Code Review CL 7441046: https://golang.org/cl/7441046 (このコミットのコードレビューページ)
参考にした情報源リンク
特になし。コミットメッセージとコードの差分から直接情報を抽出しました。