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

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

このコミットは、Go言語の標準ライブラリであるtestingパッケージにおけるベンチマーク実行時間指定フラグ-test.benchtimeの型をfloat64からflag.Durationに変更するものです。これにより、ベンチマーク実行時間の指定がより柔軟かつ直感的になり、秒単位だけでなくミリ秒やナノ秒といったより細かい単位での指定が可能になります。

コミット

commit f8b5838123585fc74d8463ff3b99a9780b0517b9
Author: David Symonds <dsymonds@golang.org>
Date:   Tue Oct 9 08:57:29 2012 +1100

    testing: change -test.benchtime to a flag.Duration.
    
    Fixes #3902.
    
    R=golang-dev, minux.ma, rsc, r
    CC=golang-dev
    https://golang.org/cl/6611059

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

https://github.com/golang/go/commit/f8b5838123585fc74d8463ff3b99a9780b0517b9

元コミット内容

misc/zsh/go                              | 2 +-\
src/cmd/go/doc.go                        | 4 ++--
src/cmd/go/test.go                       | 4 ++--
src/cmd/go/testflag.go                   | 2 +-\
src/pkg/go/doc/testdata/benchmark.go     | 4 ++--
src/pkg/go/doc/testdata/testing.1.golden | 2 +-\
src/pkg/net/http/serve_test.go           | 2 +-\
src/pkg/testing/benchmark.go             | 4 ++--
test/fixedbugs/bug369.go                 | 4 ++--
9 files changed, 14 insertions(+), 14 deletions(-)

変更の背景

この変更は、GoのIssue #3902「go test -benchtime should accept duration strings」を解決するために行われました。

従来の-test.benchtimeフラグは、ベンチマークを実行する時間を秒単位の浮動小数点数(float64)で指定していました。例えば、1.5秒や0.1秒といった指定です。しかし、この形式では以下のような問題がありました。

  1. 単位の不明瞭さ: 数値だけでは単位が秒であることが自明ではなく、特に小数点で指定する場合に直感的ではありませんでした。
  2. 柔軟性の欠如: ミリ秒やマイクロ秒といったより細かい時間単位での指定が直接できず、常に秒に換算する必要がありました。例えば、100ミリ秒を指定するには0.1と書く必要があり、これは可読性を損ねます。
  3. Goのイディオムとの不一致: Goにはtime.Durationという時間間隔を表現するための強力な型があり、これは"1s", "100ms", "1h30m"のような人間が読みやすい文字列形式でのパースをサポートしています。ベンチマークの時間指定もこのイディオムに合わせることで、一貫性と使いやすさが向上します。

これらの問題を解決し、よりGoらしい、柔軟で直感的な時間指定を可能にするために、-test.benchtimeの型をflag.Durationに変更する決定がなされました。

前提知識の解説

1. Goのflagパッケージ

Go言語の標準ライブラリであるflagパッケージは、コマンドライン引数をパースするための機能を提供します。プログラム内でflag.StringVar, flag.IntVar, flag.BoolVarなどの関数を使ってフラグを定義し、flag.Parse()を呼び出すことで、コマンドラインから渡された値を対応する変数に格納します。

このパッケージには、基本的な型(文字列、整数、真偽値など)だけでなく、time.Duration型を直接パースするためのflag.DurationVarflag.Duration関数も用意されています。これにより、ユーザーは"1s", "500ms", "2h30m"といった人間が読みやすい形式で時間間隔を指定できるようになります。

2. time.Duration

time.Durationは、Go言語のtimeパッケージで定義されている型で、時間間隔を表します。これはint64のエイリアスであり、ナノ秒単位で時間間隔を保持します。

time.Durationの大きな特徴は、そのリテラル表現と文字列パース能力です。

  • リテラル: time.Second, time.Millisecond, time.Minuteなどの定数と組み合わせて、1 * time.Second (1秒), 500 * time.Millisecond (500ミリ秒) のように記述できます。
  • 文字列パース: time.ParseDuration("1h30m")のように、"1h30m", "1.5s", "200ms"といった文字列をtime.Duration型に変換できます。flagパッケージは、このパース機能を内部的に利用しています。

3. Goのベンチマークと-test.benchtime

Goのtestingパッケージは、ユニットテストだけでなく、ベンチマークテストの機能も提供しています。ベンチマーク関数はBenchmarkXxx(*testing.B)というシグネチャを持ち、b.N回ループを実行して処理時間を計測します。

go test -bench .コマンドでベンチマークを実行できます。この際、-test.benchtimeフラグは、各ベンチマーク関数が実行される「最小時間」を指定します。Goのベンチマークは、指定された時間(デフォルトは1秒)が経過するか、またはb.Nが十分に大きくなるまで反復処理を続けます。これにより、短時間で終わる処理でも統計的に意味のある結果を得られるように設計されています。

例えば、-test.benchtime=5sと指定すると、各ベンチマークは少なくとも5秒間実行されます。

技術的詳細

このコミットの技術的な核心は、testingパッケージ内部で-test.benchtimeフラグを処理する方法の変更と、それに伴う関連ファイルの更新です。

  1. src/pkg/testing/benchmark.goの変更:

    • benchTime変数の定義がflag.Float64からflag.Durationに変更されました。
      // 変更前
      var benchTime = flag.Float64("test.benchtime", 1, "approximate run time for each benchmark, in seconds")
      // 変更後
      var benchTime = flag.Duration("test.benchtime", 1*time.Second, "approximate run time for each benchmark")
      
      これにより、benchTimeは直接time.Duration型の値を保持するようになります。デフォルト値も1(秒)から1*time.Secondというtime.Durationリテラルに変更されています。
    • ベンチマークの実行時間を計算するロジックが簡素化されました。
      // 変更前
      d := time.Duration(*benchTime * float64(time.Second))
      // 変更後
      d := *benchTime
      
      変更前は、float64型の*benchTime(秒数)をfloat64(time.Second)(ナノ秒単位の秒数)と乗算してtime.Durationに変換していました。変更後は、*benchTimeが既にtime.Duration型であるため、直接その値を使用できるようになりました。
  2. src/cmd/go/testflag.goの変更:

    • go testコマンドの-benchtimeフラグのデフォルト値の記述が1から1sに変更されました。これは、新しいflag.Durationのパース形式に合わせたものです。
      // 変更前
      -benchtime=1: passes -test.benchtime to test
      // 変更後
      -benchtime=1s: passes -test.benchtime to test
      
  3. ドキュメントとテストデータの更新:

    • src/cmd/go/doc.gosrc/cmd/go/test.go内の-test.benchtimeの説明が、「n seconds」から「t」(duration)に変更され、より一般的な時間指定を許容する記述になりました。
    • misc/zsh/goのZsh補完スクリプトでも、-benchtimeのヘルプメッセージが更新されました。
    • src/pkg/net/http/serve_test.goのコメント内の例が15から15sに変更され、新しい使用法を示しています。
    • test/fixedbugs/bug369.goでは、テスト内で-test.benchtimeに渡す値が"0.1"から"100ms"に変更され、新しい形式でのフラグの動作を検証しています。

これらの変更により、ユーザーはgo test -benchtime=500msのように、より直感的で柔軟な時間指定が可能になりました。

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

このコミットの最も重要な変更は、src/pkg/testing/benchmark.goファイル内のbenchTime変数の定義と、その値を使用する箇所です。

--- a/src/pkg/testing/benchmark.go
+++ b/src/pkg/testing/benchmark.go
@@ -14,7 +14,7 @@ import (
 )
 
 var matchBenchmarks = flag.String("test.bench", "", "regular expression to select benchmarks to run")
-var benchTime = flag.Float64("test.benchtime", 1, "approximate run time for each benchmark, in seconds")
+var benchTime = flag.Duration("test.benchtime", 1*time.Second, "approximate run time for each benchmark")
 var benchmarkMemory = flag.Bool("test.benchmem", false, "print memory allocations for benchmarks")
 
 // Global lock to ensure only one benchmark runs at a time.
@@ -178,7 +178,7 @@ func (b *B) launch() {
 
 	b.runN(n)
 	// Run the benchmark for at least the specified amount of time.
-	d := time.Duration(*benchTime * float64(time.Second))
+	d := *benchTime
 	for !b.failed && b.duration < d && n < 1e9 {
 		last := n
 		// Predict iterations/sec.

また、src/pkg/go/doc/testdata/benchmark.goも同様の変更を含んでおり、これはgo docコマンドのテストデータとして使用されるため、ドキュメント生成の正確性を保証します。

--- a/src/pkg/go/doc/testdata/benchmark.go
+++ b/src/pkg/go/doc/testdata/benchmark.go
@@ -13,7 +13,7 @@ import (
 )
 
 var matchBenchmarks = flag.String("test.bench", "", "regular expression to select benchmarks to run")
-var benchTime = flag.Float64("test.benchtime", 1, "approximate run time for each benchmark, in seconds")
+var benchTime = flag.Duration("test.benchtime", 1*time.Second, "approximate run time for each benchmark")
 
 // An internal type but exported because it is cross-package; part of the implementation
 // of go test.
@@ -151,7 +151,7 @@ func (b *B) launch() {
 
 	b.runN(n)
 	// Run the benchmark for at least the specified amount of time.
-	d := time.Duration(*benchTime * float64(time.Second))
+	d := *benchTime
 	for !b.failed && b.duration < d && n < 1e9 {
 		last := n
 		// Predict iterations/sec.

コアとなるコードの解説

src/pkg/testing/benchmark.go

  1. var benchTimeの型変更:

    • 変更前: var benchTime = flag.Float64("test.benchtime", 1, ...)
      • これは-test.benchtimeフラグがfloat64型の値をパースすることを意味していました。デフォルト値は1で、これは1秒を表します。
    • 変更後: var benchTime = flag.Duration("test.benchtime", 1*time.Second, ...)
      • flag.Duration関数を使用することで、benchTimeは直接time.Duration型のポインタを返すようになります。これにより、"1s", "500ms"のような文字列が自動的にtime.Durationに変換され、*benchTimeでその値を取得できるようになります。デフォルト値も1*time.Secondというtime.Durationリテラルで指定され、より明確になりました。
  2. ベンチマーク実行時間計算の簡素化:

    • 変更前: d := time.Duration(*benchTime * float64(time.Second))
      • *benchTimefloat64型の秒数でした。これをfloat64(time.Second)time.Secondtime.Duration型ですが、float64にキャストすることでナノ秒単位の秒数を浮動小数点数として取得できます)と乗算し、その結果をtime.Durationにキャストしていました。これは、秒数をナノ秒に変換する複雑な計算でした。
    • 変更後: d := *benchTime
      • *benchTimeは既にtime.Duration型であるため、追加の変換や計算は不要になりました。これにより、コードがより簡潔で読みやすくなりました。

この変更により、Goのベンチマークシステムは、時間指定に関してよりGoのイディオムに沿った、柔軟で堅牢なものになりました。

関連リンク

参考にした情報源リンク