[インデックス 16980] ファイルの概要
このコミットは、Goランタイムのテストファイルである src/pkg/runtime/proc_test.go
に変更を加えています。具体的には、TestGoroutineParallelism
、TestPreemption
、TestPreemptionGC
の各テストにおいて、go test -short
フラグが指定された場合にテストの実行回数や並列度を削減する調整が行われています。これにより、低速な環境やユニプロセッサ環境でのテストのタイムアウトを防ぎつつ、テストの網羅性を維持しようとしています。
コミット
Author: Dmitriy Vyukov dvyukov@google.com Date: Thu Aug 1 18:25:36 2013 +0400 Subject: runtime: make new tests shorter in short mode
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d8bbbd2537551c0f6145c27747c7675b21a58f9f
元コミット内容
runtime: make new tests shorter in short mode
We see timeouts in these tests on some platforms,
but not on the others. The hypothesis is that
the problematic platforms are slow uniprocessors.
Stack traces do not suggest that the process
is completely hang, and it is able to schedule
the alarm goroutine. And if it actually hangs,
we still will be able to detect that.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/12253043
変更の背景
このコミットの主な背景は、Goランタイムの特定のテスト(TestGoroutineParallelism
, TestPreemption
, TestPreemptionGC
)が、一部のプラットフォーム、特に低速なユニプロセッサ環境でタイムアウトを引き起こしていたことです。コミットメッセージによると、これらのタイムアウトはテストが完全にハングしているわけではなく、アラームゴルーチン(テストのタイムアウトを検出するためのメカニズム)がスケジュール可能であることから、単にテストの実行に時間がかかりすぎていることが原因であると推測されています。
開発チームは、テストの実行時間を短縮することで、これらの環境でのタイムアウトを回避し、CI/CDパイプラインの安定性を向上させることを目指しました。同時に、テストが実際にハングするような深刻な問題が発生した場合には、引き続きそれを検出できるような設計を維持することも重要視されました。この目的を達成するために、Goのテストフレームワークが提供する testing.Short()
フラグを活用し、テストの実行回数や並列度を削減するアプローチが採用されました。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびテストに関する前提知識が必要です。
-
go test
コマンドとtesting.Short()
フラグ:go test
はGo言語のテストを実行するためのコマンドです。go test -short
は、テストの実行時間を短縮するためのフラグです。このフラグが指定されると、testing
パッケージのShort()
関数がtrue
を返します。開発者はこのShort()
関数の戻り値を利用して、時間のかかるテストの一部をスキップしたり、テストの規模を縮小したりするロジックをテストコード内に記述できます。これにより、開発中の迅速なフィードバックや、リソースが限られたCI環境での効率的なテスト実行が可能になります。
-
Goroutine (ゴルーチン):
- Go言語における軽量な実行スレッドです。OSのスレッドよりもはるかに少ないメモリ(数KB)で起動でき、数百万のゴルーチンを同時に実行することも可能です。
- Goランタイムがゴルーチンのスケジューリング、多重化、およびOSスレッドへのマッピングを管理します。
-
runtime.GOMAXPROCS
:- Goランタイムが同時に実行できるOSスレッドの最大数を設定する関数です。デフォルトでは、CPUの論理コア数に設定されます。
- この値は、GoスケジューラがゴルーチンをOSスレッドにどのように分散させるかに影響を与えます。
GOMAXPROCS
の値を変更することで、並列実行されるゴルーチンの数を制御し、テスト環境のCPUリソースをシミュレートすることができます。
-
Preemption (プリエンプション):
- Goランタイムのスケジューラは、実行中のゴルーチンを強制的に中断し、別のゴルーチンにCPUを割り当てる「プリエンプション」のメカニズムを持っています。これにより、長時間実行されるゴルーチンが他のゴルーチンの実行を妨げることを防ぎ、公平なスケジューリングとシステムの応答性を保証します。
- 初期のGoでは、関数呼び出し時やチャネル操作時など、特定のポイントでのみプリエンプションが発生していました。このコミットの時期(2013年)は、非協調的プリエンプション(non-cooperative preemption)がまだ完全に実装されていなかった頃であり、長時間計算を行うゴルーチンがCPUを占有し続ける可能性がありました。
-
Garbage Collection (GC) とそのプリエンプションへの影響:
- Goのガベージコレクタは、不要になったメモリを自動的に解放します。GCの実行中、一部のゴルーチンは一時的に停止されることがあります(Stop-the-Worldフェーズ)。
- GCの実行は、特に多くのメモリが割り当てられたり、多くのオブジェクトが生成されたりするテストにおいて、ゴルーチンのスケジューリングや実行時間に影響を与える可能性があります。
TestPreemptionGC
は、GCが実行中のゴルーチンをプリエンプトする能力をテストしています。
技術的詳細
このコミットの技術的な核心は、Goのテストフレームワークが提供する testing.Short()
関数を利用して、テストの実行パラメータを動的に調整する点にあります。
Goのテストは、通常、網羅性と堅牢性を確保するために、ある程度の時間とリソースを消費するように設計されています。しかし、開発サイクル中の頻繁なテスト実行や、リソースが限られたCI環境では、これらのテストがボトルネックとなることがあります。testing.Short()
は、このようなシナリオに対応するための標準的なメカニズムを提供します。
コミットでは、以下の3つのテスト関数が修正されています。
-
TestGoroutineParallelism
:- このテストは、複数のゴルーチンが並行して動作する際のスケジューリングと同期の振る舞いを検証します。
- 元のコードでは、
P
(並列度、GOMAXPROCS
の設定値) が4
、try
ループの回数が10
に固定されていました。 - 変更後、
testing.Short()
がtrue
の場合、P
は3
に、try
ループの回数N
は3
に削減されます。これにより、テストが実行するゴルーチンの総数と、テストの反復回数が減少し、実行時間が短縮されます。
-
TestPreemption
:- このテストは、関数呼び出しにおけるゴルーチンのプリエンプションが正しく機能するかを検証します。
- 元のコードでは、
N
(ゴルーチンの数または反復回数) が5
に固定されていました。 - 変更後、
testing.Short()
がtrue
の場合、N
は2
に削減されます。これにより、プリエンプションのテストにかかる時間が短縮されます。
-
TestPreemptionGC
:- このテストは、GCが実行中のゴルーチンをプリエンプトする能力を検証します。
- 元のコードでは、
P
(並列度) が5
、GC
ループの回数が10
に固定されていました。 - 変更後、
testing.Short()
がtrue
の場合、P
は3
に、GC
ループの回数N
は2
に削減されます。これにより、GCとプリエンプションの相互作用をテストする際の負荷が軽減され、実行時間が短縮されます。
これらの変更は、テストの網羅性を完全に損なうことなく、低速な環境でのタイムアウト問題を緩和するための実用的なアプローチです。testing.Short()
を使用することで、開発者は通常のテスト実行では完全なテストスイートを実行し、CI環境や迅速なチェックが必要な場合には短縮版のテストを実行するという柔軟な運用が可能になります。
コアとなるコードの変更箇所
変更は src/pkg/runtime/proc_test.go
ファイルに集中しています。
--- a/src/pkg/runtime/proc_test.go
+++ b/src/pkg/runtime/proc_test.go
@@ -94,9 +94,14 @@ func TestYieldLocked(t *testing.T) {
}
func TestGoroutineParallelism(t *testing.T) {
- const P = 4
+ P := 4
+ N := 10
+ if testing.Short() {
+ P = 3
+ N = 3
+ }
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(P))
- for try := 0; try < 10; try++ {
+ for try := 0; try < N; try++ {
done := make(chan bool)
x := uint32(0)
for p := 0; p < P; p++ {
@@ -194,7 +199,10 @@ var preempt = func() int {
func TestPreemption(t *testing.T) {
// Test that goroutines are preempted at function calls.
- const N = 5
+ N := 5
+ if testing.Short() {
+ N = 2
+ }
c := make(chan bool)
var x uint32
for g := 0; g < 2; g++ {
@@ -214,7 +222,12 @@ func TestPreemption(t *testing.T) {
func TestPreemptionGC(t *testing.T) {
// Test that pending GC preempts running goroutines.
- const P = 5
+ P := 5
+ N := 10
+ if testing.Short() {
+ P = 3
+ N = 2
+ }
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(P + 1))
var stop uint32
for i := 0; i < P; i++ {
@@ -224,7 +237,7 @@ func TestPreemptionGC(t *testing.T) {
}()
}\n-\tfor i := 0; i < 10; i++ {\n+\tfor i := 0; i < N; i++ {\n \t\truntime.Gosched()\n \t\truntime.GC()\n \t}\n```
## コアとなるコードの解説
各テスト関数で行われた変更は、基本的に同じパターンに従っています。
1. **定数から変数への変更**:
* `const` で定義されていた `P` (並列度) や `N` (反復回数) が、`var` (または短い宣言 `:=`) を使った変数宣言に変更されています。これは、`testing.Short()` の結果に基づいてこれらの値を動的に変更できるようにするためです。定数はコンパイル時に値が決定されるため、実行時の条件に基づいて変更することはできません。
2. **`testing.Short()` による条件分岐**:
* 各テストの冒頭に `if testing.Short() { ... }` という条件分岐が追加されています。
* このブロック内で、`P` や `N` の値が、より小さい値に再設定されます。
* `TestGoroutineParallelism`: `P` が `4` から `3` に、`N` が `10` から `3` に。
* `TestPreemption`: `N` が `5` から `2` に。
* `TestPreemptionGC`: `P` が `5` から `3` に、`N` が `10` から `2` に。
3. **ループ条件の変更**:
* `for` ループの条件が、元の固定値から、`N` 変数を使用するように変更されています。これにより、`testing.Short()` が `true` の場合にループの反復回数が減少し、テストの実行時間が短縮されます。
これらの変更により、`go test` が `-short` フラグなしで実行された場合(通常の完全なテスト実行)は、元のテストの規模と網羅性が維持されます。一方、`-short` フラグが指定された場合(迅速なテスト実行)は、テストの実行回数や並列度が削減され、低速な環境でのタイムアウトを防ぎつつ、基本的な機能の検証は行われるようになります。
このアプローチは、テストの実行時間と網羅性のバランスを取るための一般的なプラクティスであり、特にランタイムのような低レベルで複雑なコンポーネントのテストにおいて有効です。
## 関連リンク
* Go CL 12253043: [https://golang.org/cl/12253043](https://golang.org/cl/12253043)
## 参考にした情報源リンク
* Go言語の公式ドキュメント (特に `testing` パッケージと `runtime` パッケージに関する情報)
* Go言語のテストに関する一般的なプラクティス
* Goスケジューラとプリエンプションに関する技術記事 (コミット当時のGoのプリエンプションの状況を理解するため)