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

[インデックス 13881] ファイルの概要

このコミットは、Go言語のmisc/cgo/test/issue1560.goファイルに対する変更を記録しています。具体的には、TestParallelSleepテスト関数内でCPUを意図的に消費するwasteCPU関数の実行を、ARMアーキテクチャでのみ行うように条件付けしています。これにより、ARM環境以外でのテスト実行時の不要なCPUリソース消費を抑制しつつ、ARM環境で発生していたテストのタイムアウト問題を解決しています。

コミット

commit 81a9cc31c46aada8ec4ff7e262b8b0a46e2ce57a
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Thu Sep 20 12:06:08 2012 +1000

    misc/cgo/test: do not run wasteCPU during TestParallelSleep unless on arm
    
    R=golang-dev, r, minux.ma, dave, bradfitz
    CC=golang-dev
    https://golang.org/cl/6532052

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/81a9cc31c46aada8ec4ff7e262b8b0a46e2ce57a

元コミット内容

misc/cgo/test: do not run wasteCPU during TestParallelSleep unless on arm

R=golang-dev, r, minux.ma, dave, bradfitz
CC=golang-dev
https://golang.org/cl/6532052

変更の背景

この変更の背景には、Go言語のテストスイート、特にmisc/cgo/test/issue1560.go内のTestParallelSleepテストが、ARMアーキテクチャ上で頻繁に失敗するという問題がありました。テストのコメントに「on ARM, the 1.3s deadline is frequently missed, and burning cpu seems to help」とあるように、ARM環境では特定の時間的制約(1.3秒のデッドライン)が守られず、テストがタイムアウトしてしまう事象が発生していました。

以前のコードでは、このテストの実行時に常にwasteCPU()関数を呼び出し、CPUを意図的に消費していました。これは、CPUを「燃焼」させることで、スケジューリングやタイマーの精度に影響を与え、結果的にテストが成功するようになるという、一種のワークアラウンドとして導入されていたと考えられます。しかし、このwasteCPUの実行は、ARM以外の環境では不要なCPUリソースの消費につながり、テストの実行時間を延ばす可能性がありました。

このコミットは、このwasteCPUによるCPU消費をARMアーキテクチャに限定することで、ARM環境でのテストの安定性を維持しつつ、他のアーキテクチャでのテスト効率を改善することを目的としています。

前提知識の解説

Go言語のテストとtestingパッケージ

Go言語には、標準ライブラリとしてtestingパッケージが提供されており、ユニットテストやベンチマークテストを簡単に記述できます。テスト関数はTestで始まり、*testing.T型の引数を取ります。テストの失敗はt.Error()t.Fatal()などで報告されます。

runtimeパッケージとruntime.GOMAXPROCS

runtimeパッケージは、Goランタイムとのインタフェースを提供します。 runtime.GOMAXPROCS(n int)関数は、Goスケジューラが同時に実行できるOSスレッドの最大数を設定します。nが0の場合、現在の設定が変更されずに返されます。この関数は、並行処理の挙動をテストする際などに、Goランタイムが利用するCPUコア数を制御するために使用されることがあります。このコミットではruntime.GOMAXPROCS(2)と設定されており、Goスケジューラが最大2つのOSスレッドを使用するように制限しています。これは、並行処理のテストにおいて、特定の競合条件やスケジューリングの挙動を再現・検証するために行われることがあります。

defer

Go言語のdefer文は、その関数がリターンする直前に実行される関数呼び出しをスケジュールします。複数のdefer文がある場合、それらはLIFO(後入れ先出し)の順序で実行されます。このコミットでは、defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))defer close(wasteCPU())が使用されており、テスト関数が終了する際にこれらの処理が実行されることを保証しています。

cgo

cgoは、GoプログラムからC言語のコードを呼び出すためのGoのツールです。また、C言語のコードからGoの関数を呼び出すことも可能です。misc/cgo/testディレクトリにあることから、このテストはcgoに関連する機能、おそらくGoとCの間の並行処理や同期の挙動を検証していると考えられます。

ARMアーキテクチャの特性(一般的な傾向)

ARM(Advanced RISC Machine)アーキテクチャは、主にモバイルデバイスや組み込みシステムで広く使用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。一般的に、デスクトップやサーバーで主流のx86アーキテクチャと比較して、消費電力が低く、命令セットがシンプルであるという特徴があります。

パフォーマンス特性としては、特定のワークロードにおいて、x86とは異なるスケジューリングやキャッシュの挙動を示すことがあります。特に、リアルタイム性や厳密なタイミングが要求される処理において、OSのスケジューラやハードウェアの割り込み処理のオーバーヘッドが、テストのデッドラインに影響を与える可能性があります。このコミットの背景にある「1.3s deadline is frequently missed」という問題は、ARM環境におけるこれらの特性が影響している可能性を示唆しています。CPUを「燃焼」させることで、OSのスケジューラに常に実行可能なタスクがある状態を作り出し、アイドル状態を減らすことで、タイマーの精度やスレッドの切り替え頻度に影響を与え、結果的にデッドラインの遵守に寄与したと考えられます。

技術的詳細

TestParallelSleepテストは、並行してスリープ処理を行う際の挙動を検証していると考えられます。このテストでは、sleepSec := 1と設定されており、1秒間のスリープが期待されます。しかし、ARM環境では、このスリープ処理が期待通りに1秒で完了せず、テストが設定されたデッドライン(1.3秒)を超過して失敗するという問題が発生していました。

この問題を回避するために、以前はwasteCPU()関数が常に実行されていました。wasteCPU()は、無限ループでCPUを消費し続けるゴルーチンを起動し、そのゴルーチンを停止するためのチャネルを返します。defer close(wasteCPU())とすることで、テスト関数が終了する際にこのCPU消費ゴルーチンが停止されるようにしていました。

この「CPUを燃焼させる」というアプローチは、一見すると非効率に見えますが、特定の条件下ではテストの安定化に役立つことがあります。考えられる理由は以下の通りです。

  1. スケジューラの挙動への影響: CPUを常に使用することで、OSのスケジューラはアイドル状態になることなく、常に実行可能なタスク(wasteCPUゴルーチン)を持つことになります。これにより、他のゴルーチン(TestParallelSleep内のスリープ処理)のスケジューリングがより予測可能になったり、タイマーの精度が向上したりする可能性があります。特に、ARMのようなリソースが限られた環境や、特定のOSのバージョンでは、アイドル状態からの復帰や割り込み処理のオーバーヘッドが、厳密なタイミングを要求するテストに影響を与えることがあります。
  2. キャッシュのウォームアップ: CPUを継続的に使用することで、プロセッサのキャッシュが常に「ウォームアップ」された状態になり、メモリへのアクセス遅延が減少する可能性があります。
  3. 割り込み処理の頻度: CPUが常にビジーであることで、OSのタイマー割り込みなどの頻度が安定し、時間計測の精度に寄与する可能性があります。

しかし、このwasteCPUの実行は、ARM環境以外では不要なオーバーヘッドとなります。そのため、今回の変更では、runtime.GOARCH == "arm"という条件を追加し、ARMアーキテクチャでのみwasteCPUを実行するようにしました。これにより、ARM環境でのテストの安定性を維持しつつ、他のアーキテクチャでのテスト実行時のリソース消費を最適化しています。

コアとなるコードの変更箇所

変更はmisc/cgo/test/issue1560.goファイルにあります。

--- a/misc/cgo/test/issue1560.go
+++ b/misc/cgo/test/issue1560.go
@@ -56,8 +56,12 @@ func wasteCPU() chan struct{} {
 }
 
 func testParallelSleep(t *testing.T) {
-	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
-	defer close(wasteCPU())
+	if runtime.GOARCH == "arm" {
+		// on ARM, the 1.3s deadline is frequently missed,
+		// and burning cpu seems to help
+		defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
+		defer close(wasteCPU())
+	}
 
 	sleepSec := 1
 	start := time.Now()

コアとなるコードの解説

変更の中心は、testParallelSleep関数内のdefer文のブロックをif runtime.GOARCH == "arm" { ... }という条件文で囲んだ点です。

  • 変更前:

    func testParallelSleep(t *testing.T) {
        defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
        defer close(wasteCPU())
        // ...
    }
    

    このコードでは、testParallelSleepが呼び出されるたびに、runtime.GOMAXPROCSが2に設定され、wasteCPU()が実行されてCPUが消費されます。これは、どのアーキテクチャでテストが実行されても一律に適用されていました。

  • 変更後:

    func testParallelSleep(t *testing.T) {
        if runtime.GOARCH == "arm" {
            // on ARM, the 1.3s deadline is frequently missed,
            // and burning cpu seems to help
            defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
            defer close(wasteCPU())
        }
        // ...
    }
    

    変更後では、runtime.GOARCHというビルド定数(Goプログラムが実行されているシステムのアーキテクチャを示す文字列)が"arm"である場合にのみ、runtime.GOMAXPROCS(2)の設定とwasteCPU()の実行が行われるようになりました。 これにより、ARMアーキテクチャ以外のシステム(例: x86、amd64など)でこのテストが実行される際には、不要なCPU消費が回避され、テストの実行効率が向上します。同時に、ARM環境でのテストの安定性(デッドラインの遵守)は維持されます。

この変更は、特定のアーキテクチャに依存するパフォーマンス特性やタイミングの問題に対する、実用的な解決策を示しています。

関連リンク

参考にした情報源リンク