[インデックス 17833] ファイルの概要
このコミットは、Go言語のテストスイート内の test/mapnan.go ファイルに対する変更です。具体的には、マップにNaN(Not a Number)を挿入する際のパフォーマンスをテストする部分において、時間の計測方法を syscall.Getrusage から time.Now に変更しています。
コミット
commit 08a561459763181649c35eb7191c945b5a10c933
Author: Russ Cox <rsc@golang.org>
Date: Tue Oct 22 18:33:37 2013 -0400
test/mapnan: use time.Now instead of syscall.Getrusage
Avoids a dependency on a somewhat nonstandard part of package syscall.
R=golang-dev, dave, r
CC=golang-dev
https://golang.org/cl/15570046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/08a561459763181649c35eb7191c945b5a10c933
元コミット内容
test/mapnan: use time.Now instead of syscall.Getrusage
(test/mapnan: syscall.Getrusage の代わりに time.Now を使用)
Avoids a dependency on a somewhat nonstandard part of package syscall.
(syscall パッケージのやや非標準的な部分への依存を避ける)
変更の背景
このコミットの背景には、Go言語のテストのポータビリティと信頼性の向上が挙げられます。syscall.Getrusage は、プロセスやその子プロセスのリソース使用量(CPU時間、メモリ使用量など)を取得するためのシステムコールをGoの syscall パッケージ経由で呼び出すものです。しかし、このシステムコールはオペレーティングシステム(OS)によってその挙動やサポート状況が異なる場合があります。特に、Utime (ユーザーCPU時間) や Stime (システムCPU時間) の精度や利用可能性はOSに依存します。
test/mapnan.go は、Goのマップが NaN (Not a Number) をキーとして扱う際のパフォーマンス特性をテストするためのものです。IEEE 754浮動小数点標準では、NaN はそれ自身を含め、他の NaN とも等しくないと定義されています。Goのマップはキーの等価性に基づいて動作するため、NaN をキーとして使用すると、通常の数値とは異なる挙動を示す可能性があります。このテストは、NaN を多数挿入した際に、マップの操作が二次関数的に(quadratic)遅くならないことを確認することを目的としていました。
このようなパフォーマンス測定を行うテストにおいて、syscall.Getrusage を用いた時間計測は、OS間の差異や、システムコール自体のオーバーヘッド、あるいはユーザー時間とシステム時間の区別といった要因により、テスト結果の安定性や正確性に影響を与える可能性がありました。コミットメッセージにある「somewhat nonstandard part of package syscall」(syscall パッケージのやや非標準的な部分)という記述は、Getrusage がすべてのプラットフォームで一貫した、あるいは期待通りの振る舞いをしない可能性を指していると考えられます。
これに対し、time.Now() はGoの標準ライブラリ time パッケージが提供する関数であり、現在のローカル時刻を返します。time.Since() は time.Now() と組み合わせて、ある時点からの経過時間を計測するために用いられます。これらはOSのシステムコールに直接依存する Getrusage よりも、Goランタイムが提供する抽象化された、よりポータブルで安定した時間計測手段です。
したがって、この変更は、特定のOS依存のシステムコールへの依存を排除し、よりポータブルで信頼性の高い方法でテストのパフォーマンス測定を行うことを目的としています。これにより、異なるプラットフォーム上でのテストの安定性が向上し、Go言語全体の品質保証に貢献します。
前提知識の解説
-
NaN (Not a Number): IEEE 754浮動小数点標準で定義される特殊な浮動小数点値です。0による除算、無限大同士の減算、負の数の平方根など、数学的に未定義または表現不可能な演算の結果として生成されます。
NaNの最も重要な特性は、それ自身を含め、他のいかなる値とも等しくないという点です。つまり、NaN == NaNはfalseと評価されます。Go言語のmathパッケージにはmath.NaN()関数があり、NaN値を生成できます。 -
Go言語のマップ (map): Go言語のマップは、キーと値のペアを格納するハッシュテーブルの実装です。キーは一意であり、値にアクセスするために使用されます。マップのキーは比較可能でなければなりません。浮動小数点数(
float32,float64)は比較可能ですが、NaNの特殊な等価性ルールがマップの動作に影響を与えます。NaNをキーとしてマップに挿入すると、NaNはそれ自身と等しくないため、同じNaN値を複数回挿入しても、マップはそれらを異なるキーとして扱う可能性があります。この挙動は、マップのサイズが予期せず増大したり、パフォーマンスが低下したりする原因となることがあります。test/mapnan.goは、このNaNの特性がマップのパフォーマンスに二次関数的な影響を与えないことを確認するためのテストです。 -
syscallパッケージとsyscall.Getrusage:syscallパッケージは、Goプログラムから基盤となるオペレーティングシステムが提供するシステムコールに直接アクセスするための低レベルなインターフェースを提供します。これにより、ファイルシステム操作、ネットワーク通信、プロセス管理など、OSレベルの機能を利用できます。syscall.Getrusageは、現在のプロセスまたはその子プロセスのリソース使用量に関する情報を取得するための関数です。引数としてwho(どのプロセスのリソースを取得するか、例:0は現在のプロセス) と*Rusage構造体へのポインタを取ります。Rusage構造体には、ユーザーCPU時間 (Utime)、システムCPU時間 (Stime)、最大常駐セットサイズ (Maxrss) などの情報が含まれます。UtimeとStimeはsyscall.Timeval型で表され、秒とマイクロ秒のペアで構成されます。この関数はOSに依存するため、異なるOSでは利用できない、あるいは異なる情報を提供する可能性があります。 -
timeパッケージとtime.Now(),time.Since():timeパッケージは、Go言語で時間に関連する操作(時刻の取得、期間の計算、タイマー、日付のフォーマットなど)を行うための標準ライブラリです。time.Now(): 現在のローカル時刻を表すtime.Timeオブジェクトを返します。これは、システムクロックに基づいており、OSに依存しないGoランタイムの抽象化された時間表現です。time.Since(t time.Time): 引数tから現在までの経過時間をtime.Duration型で返します。これは、パフォーマンス測定やタイムアウト処理などで非常に便利です。
技術的詳細
このコミットの技術的な変更点は、test/mapnan.go 内の t := func(n int) time.Duration という匿名関数における時間計測ロジックの置き換えです。
変更前は、syscall.Getrusage を使用して、マップ操作の開始前と終了後のユーザーCPU時間を取得し、その差分を計算していました。
// 変更前
t := func(n int) time.Duration {
var u0 syscall.Rusage
if err := syscall.Getrusage(0, &u0); err != nil {
panic(err)
}
m := map[float64]int{}
nan := math.NaN()
for i := 0; i < n; i++ {
m[nan] = i
}
if len(m) != n {
panic("wrong size map after nan insertion")
}
var u1 syscall.Rusage
if err := syscall.Getrusage(0, &u1); err != nil {
panic(err)
}
return time.Duration(u1.Utime.Nano() - u0.Utime.Nano())
}
ここで、u0 と u1 は syscall.Rusage 構造体であり、Utime フィールドは syscall.Timeval 型です。Utime.Nano() は、syscall.Timeval をナノ秒単位の int64 に変換するメソッド(または同等の操作)を想定しています。この方法は、プロセスが実際にCPUを使用した時間を測定するため、システム全体の負荷や他のプロセスの影響を受けにくいという利点があります。しかし、前述の通り、OS依存性や精度、オーバーヘッドの問題がありました。
変更後は、time.Now() を使用してマップ操作の開始時刻を記録し、操作終了後に time.Since() を使って経過時間を直接計算しています。
// 変更後
t := func(n int) time.Duration {
t1 := time.Now() // 開始時刻を記録
m := map[float64]int{}
nan := math.NaN()
for i := 0; i < n; i++ {
m[nan] = i
}
if len(m) != n {
panic("wrong size map after nan insertion")
}
return time.Since(t1) // 経過時間を計算
}
この変更により、以下の点が改善されます。
- ポータビリティの向上:
time.Now()とtime.Since()はGoの標準ライブラリ関数であり、Goがサポートするすべてのプラットフォームで一貫して動作します。これにより、テストが異なるOS環境でも同じように実行され、信頼性の高い結果が得られるようになります。 - コードの簡潔化:
syscall.Getrusageの呼び出しとエラーハンドリングが不要になり、コードがより簡潔になりました。 - 依存関係の削減:
syscallパッケージへの直接的な依存がテストコードから削除されました。これにより、テストのビルドや実行時の複雑さが軽減されます。 - 測定対象の変更:
syscall.GetrusageがCPU使用時間(ユーザー時間)を測定するのに対し、time.Since()は壁時計時間(wall-clock time)を測定します。このテストの目的が「NaNをマップに挿入する際の操作が二次関数的に遅くならないこと」を確認することであるため、CPU時間だけでなく、実際の経過時間(壁時計時間)でパフォーマンスを評価する方が、ユーザーが体感するパフォーマンスに近い指標となります。ただし、壁時計時間はシステム全体の負荷やスケジューリングの影響を受けるため、純粋なCPUパフォーマンスを測定する場合には不向きな場合もあります。このテストの文脈では、マップ操作の全体的な応答時間を評価することが重要であるため、time.Sinceが適切と判断されたと考えられます。
この変更は、Goのテストインフラストラクチャをより堅牢で、プラットフォームに依存しないものにするための継続的な取り組みの一環です。
コアとなるコードの変更箇所
test/mapnan.go ファイルの以下の部分が変更されました。
--- a/test/mapnan.go
+++ b/test/mapnan.go
@@ -13,17 +13,13 @@ import (
"fmt"
"math"
"time"
- "syscall" // 削除
)
func main() {
// Test that NaNs in maps don't go quadratic.
t := func(n int) time.Duration {
- // 削除されたコード
- // var u0 syscall.Rusage
- // if err := syscall.Getrusage(0, &u0); err != nil {
- // panic(err)
- // }
+ t1 := time.Now() // 追加されたコード
m := map[float64]int{}
nan := math.NaN()
for i := 0; i < n; i++ {
@@ -32,11 +28,7 @@ func main() {
if len(m) != n {
panic("wrong size map after nan insertion")
}
- // 削除されたコード
- // var u1 syscall.Rusage
- // if err := syscall.Getrusage(0, &u1); err != nil {
- // panic(err)
- // }
- // return time.Duration(u1.Utime.Nano() - u0.Utime.Nano())
+ return time.Since(t1) // 変更された戻り値
}
// Depending on the machine and OS, this test might be too fast
コアとなるコードの解説
変更されたコードは、test/mapnan.go 内の匿名関数 t の実装です。この関数は、引数 n で指定された回数だけ NaN をマップに挿入し、その操作にかかる時間を time.Duration 型で返します。
変更前:
syscallパッケージがインポートされていました。t関数の冒頭でsyscall.Getrusage(0, &u0)を呼び出し、マップ操作開始前のリソース使用量(特にユーザーCPU時間)をu0に記録していました。エラーが発生した場合はpanicしていました。- マップ操作(
n回のNaN挿入)を実行していました。 - マップ操作後、再度
syscall.Getrusage(0, &u1)を呼び出し、終了時のリソース使用量をu1に記録していました。 u1.Utime.Nano() - u0.Utime.Nano()を計算し、ユーザーCPU時間の差分をナノ秒単位で取得し、それをtime.Durationに変換して返していました。
変更後:
syscallパッケージのインポートが削除されました。t関数の冒頭でt1 := time.Now()を呼び出し、マップ操作開始前の現在の時刻をt1に記録していました。- マップ操作(
n回のNaN挿入)は変更なく実行されます。 - マップ操作後、
time.Since(t1)を呼び出し、t1から現在までの経過時間を直接time.Duration型で取得し、それを返しています。
この変更により、時間計測のメカニズムが、OS依存のシステムコールから、Goの標準ライブラリが提供するより抽象化された、ポータブルな時間計測機能へと移行しました。これにより、テストの実行環境に起因する潜在的な問題が解消され、テストの信頼性と一貫性が向上します。
関連リンク
- Go言語の
timeパッケージ: https://pkg.go.dev/time - Go言語の
syscallパッケージ: https://pkg.go.dev/syscall - IEEE 754 浮動小数点標準 (NaNに関する情報): https://en.wikipedia.org/wiki/IEEE_754
- Go言語のマップに関するドキュメント: https://go.dev/blog/maps
参考にした情報源リンク
- Go言語の公式ドキュメント (time, syscall, map)
- IEEE 754 浮動小数点標準に関する一般的な情報源
- Go言語のコミット履歴と関連するコードレビュー (CL 15570046)
syscall.Getrusageの挙動に関する一般的なOSのドキュメント (例: Linux man pages for getrusage)time.Now()とtime.Since()の使用例に関するGoのチュートリアルやブログ記事- Go言語のテストのベストプラクティスに関する情報