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

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

このコミットは、Go言語の標準ライブラリであるsrc/pkg/testing/benchmark.goに対する変更です。このファイルは、Goのベンチマークテストフレームワークの核心部分を担っており、ベンチマーク関数の実行ロジック、特にベンチマークの実行回数(イテレーション数)の決定と調整に関する処理が含まれています。Goのベンチマークは、testingパッケージが提供する*testing.B型を通じて行われ、このファイルはその*testing.B型の内部動作、特にb.Nの値がどのように決定され、ベンチマークがどのように実行されるかを定義しています。

コミット

undo CL 101970047 / 30307cc8bef2

makes windows-amd64-race benchmarks slower

««« original CL description
testing: make benchmarking faster

Allow the number of benchmark iterations to grow faster for fast benchmarks, and don't round up twice.

Using the default benchtime, this CL reduces wall clock time to run benchmarks:

net/http        49s   -> 37s   (-24%)
runtime         8m31s -> 5m55s (-30%)
bytes           2m37s -> 1m29s (-43%)
encoding/json   29s   -> 21s   (-27%)
strings         1m16s -> 53s   (-30%)

LGTM=crawshaw
R=golang-codereviews, crawshaw
CC=golang-codereviews
https://golang.org/cl/101970047
»»»

TBR=josharian
CC=golang-codereviews
https://golang.org/cl/105950044

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

https://github.com/golang/go/commit/50365666c7264ac0608220adeff2c4f503b8fb67

元コミット内容

このコミットは、以前のコミット CL 101970047 (ハッシュ 30307cc8bef2) を元に戻すものです。元となったコミットの目的は、Goのベンチマーク実行を高速化することでした。具体的には、高速なベンチマークに対してイテレーション数(b.N)の増加をより速くし、かつイテレーション数の丸め込み処理を二重に行わないようにすることで、ベンチマークの実行時間を短縮しようとしました。

元のコミットの説明には、デフォルトのbenchtime(ベンチマークの最小実行時間)を使用した場合に、いくつかのパッケージでベンチマークの実行時間が大幅に短縮された例が挙げられています。

  • net/http: 49秒 → 37秒 (-24%)
  • runtime: 8分31秒 → 5分55秒 (-30%)
  • bytes: 2分37秒 → 1分29秒 (-43%)
  • encoding/json: 29秒 → 21秒 (-27%)
  • strings: 1分16秒 → 53秒 (-30%)

これらの改善は、開発者がより迅速にベンチマークを実行し、パフォーマンスの回帰を早期に発見するのに役立つと期待されていました。

変更の背景

このコミットの背景には、パフォーマンス改善を目的とした先行コミット(CL 101970047)が、特定の環境で予期せぬ負の影響を与えたという問題があります。先行コミットは、ベンチマークのイテレーション数決定ロジックを最適化することで、全体的なベンチマーク実行時間の短縮を目指しました。しかし、その変更がwindows-amd64-raceビルド、すなわちWindowsオペレーティングシステム上でAMD64アーキテクチャ向けにビルドされ、かつGoのデータ競合検出器(Race Detector)が有効になっている環境で、ベンチマークの実行を遅くするという問題を引き起こしました。

Goのプロジェクトでは、様々なプラットフォームや設定(例えば、Race Detectorの有効/無効)で継続的なインテグレーション(CI)テストが実行されており、この問題はCIシステムによって検出されたと考えられます。特定の環境でのパフォーマンス回帰は、Goの品質と安定性を維持する上で許容できないため、先行コミットの変更を元に戻す(revertする)という決定がなされました。これは、パフォーマンスの最適化が、すべてのサポート対象環境で一貫した振る舞いを保証する必要があるというGo開発チームの原則を示しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語のベンチマークに関する前提知識が必要です。

Goのベンチマークシステム

Go言語には、testingパッケージを通じて組み込みのベンチマーク機能が提供されています。

  • ベンチマーク関数: func BenchmarkXxx(b *testing.B)というシグネチャを持つ関数で定義されます。
  • *testing.B: ベンチマーク実行のコンテキストを提供する構造体です。
    • b.N: ベンチマーク関数が実行されるイテレーション数(ループ回数)です。Goのベンチマークシステムは、指定されたbenchtime(デフォルト1秒)を満たすようにb.Nの値を自動的に調整します。最初は小さなb.Nで実行し、時間が足りなければb.Nを増やして再実行します。
    • b.Run(name string, f func(b *B)): サブベンチマークを定義するために使用されます。
    • b.ReportAllocs(): メモリ割り当てを報告するように設定します。
    • b.SetBytes(n int64): 1イテレーションあたりに処理されるバイト数を設定し、スループット(バイト/秒)を計算できるようにします。
    • b.ResetTimer(): タイマーをリセットします。
    • b.StartTimer(): タイマーを開始します。
    • b.StopTimer(): タイマーを停止します。

ベンチマークのイテレーション数決定ロジック

Goのベンチマークは、安定した測定結果を得るために、一定の時間(デフォルト1秒)以上実行されるように設計されています。このため、b.Nの値は動的に調整されます。

  1. 最初は小さなb.N(例: 1)でベンチマークを実行します。
  2. 実行にかかった時間と、目標とするbenchtimeを比較します。
  3. もし実行時間が短すぎる場合、次のb.Nの値を計算します。この計算は、前回の実行時間とb.Nから1イテレーションあたりの平均時間(nsPerOp)を推定し、目標時間に必要なイテレーション数を算出します。
  4. 算出されたb.Nは、前回のb.Nよりも大きく、かつ急激に増加しすぎないように調整されます。また、読みやすい値に丸められることもあります。
  5. このプロセスを繰り返し、ベンチマークが十分な時間実行されるまでb.Nを増やしていきます。

windows-amd64-raceビルド

  • Windows: Microsoft Windowsオペレーティングシステム。
  • AMD64: 64ビットのx86アーキテクチャ(Intel 64やAMD64など)。
  • Race Detector: Goに組み込まれているデータ競合検出器です。go run -racego build -racego test -raceのように-raceフラグを付けてビルド・実行することで有効になります。Race Detectorは、並行処理におけるデータ競合(複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つが書き込み操作である場合に発生するバグ)を検出するために、実行時に追加のインストゥルメンテーション(計測コード)を挿入します。このインストゥルメンテーションは、プログラムの実行速度を大幅に低下させる可能性があります(通常、2倍から20倍遅くなる)。

windows-amd64-raceビルドでベンチマークが遅くなったということは、Race Detectorが有効な環境でのベンチマークのイテレーション数調整ロジックに問題があったことを示唆しています。Race Detectorのオーバーヘッドは非常に大きいため、イテレーション数の計算がRace Detectorの存在を考慮せずに最適化されると、予期せぬパフォーマンスの低下を招くことがあります。

技術的詳細

このコミットは、src/pkg/testing/benchmark.go内の(*B).launch()メソッドにおけるベンチマークのイテレーション数nの計算ロジックを元に戻すものです。

元のコミット(CL 101970047)では、b.Nの次の値nを決定する際に、以下の変更が加えられました。

元のコミットでの変更点(このコミットで元に戻される部分):

--- a/src/pkg/testing/benchmark.go
+++ b/src/pkg/testing/benchmark.go
@@ -205,12 +205,10 @@ func (b *B) launch() {
 		} else {
 			n = int(d.Nanoseconds() / b.nsPerOp())
 		}
-		// If the last run was small, don't grow too fast.
-		if last < 1000 {
-			n = min(n, 100*last)
-		}
+		// Run more iterations than we think we'll need for a second (1.5x).
+		// Don't grow too fast in case we had timing errors previously.
 		// Be sure to run at least one more than last time.
-		n = max(n, last+1)
+		n = max(min(n+n/2, 100*last), last+1)
 		// Round up to something easy to read.
 		n = roundUp(n)
 		b.runN(n)

具体的には、以下の行が削除され、新しい行が追加されました。

削除された行(元のコミットで追加されたが、このコミットで削除される):

		// If the last run was small, don't grow too fast.
		if last < 1000 {
			n = min(n, 100*last)
		}
		// Be sure to run at least one more than last time.
		n = max(n, last+1)

追加された行(元のコミットで削除されたが、このコミットで復活する):

		// Run more iterations than we think we'll need for a second (1.5x).
		// Don't grow too fast in case we had timing errors previously.
		// Be sure to run at least one more than last time.
		n = max(min(n+n/2, 100*last), last+1)

この変更の核心は、nの計算方法です。

  • 元のコミットのロジック(削除された方):

    • n = int(d.Nanoseconds() / b.nsPerOp()) で、目標時間(d)と1操作あたりのナノ秒(b.nsPerOp())から理想的なイテレーション数を算出します。
    • if last < 1000 { n = min(n, 100*last) }: 前回のイテレーション数lastが1000未満の場合、nlastの100倍を超えないように制限します。これは、イテレーション数が急激に増えすぎるのを防ぐためのものです。
    • n = max(n, last+1): nが少なくとも前回のイテレーション数lastより1多くなることを保証します。これにより、ベンチマークが進行し、最終的に十分なイテレーション数に達するようにします。
  • このコミットで復活するロジック(追加された方):

    • n = int(d.Nanoseconds() / b.nsPerOp()) で理想的なイテレーション数を算出するところは同じです。
    • n = max(min(n+n/2, 100*last), last+1): この一行で、イテレーション数の調整を行います。
      • n+n/2: 算出されたnの1.5倍を計算します。これは「1秒に必要なイテレーション数よりも多く実行する(1.5倍)」というコメントに対応します。
      • min(n+n/2, 100*last): 上記の1.5倍されたnと、lastの100倍の小さい方を取ります。これにより、イテレーション数の急激な増加をlastの100倍に制限します。
      • max(..., last+1): 最終的なnが、少なくとも前回のイテレーション数lastより1多くなることを保証します。

元のコミットの意図は、last < 1000という条件分岐をなくし、nの計算をよりシンプルかつ効率的にすることでした。しかし、この変更がwindows-amd64-race環境で問題を引き起こしたため、元のロジックに戻されました。Race Detectorが有効な場合、nsPerOpの計算が不安定になったり、ベンチマークの実行時間が非線形に増加したりする可能性があり、その結果、イテレーション数の調整ロジックがうまく機能しなかったと考えられます。特に、last < 1000のような特定の閾値での挙動の違いが、Race Detectorのオーバーヘッドと組み合わさって、パフォーマンスの回帰を引き起こした可能性があります。

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

diff --git a/src/pkg/testing/benchmark.go b/src/pkg/testing/benchmark.go
index 43bbecbdc6..1fbf5c8615 100644
--- a/src/pkg/testing/benchmark.go
+++ b/src/pkg/testing/benchmark.go
@@ -205,12 +205,10 @@ func (b *B) launch() {
 		} else {\n \t\t\tn = int(d.Nanoseconds() / b.nsPerOp())\n \t\t}\n-\t\t// If the last run was small, don\'t grow too fast.\n-\t\tif last < 1000 {\n-\t\t\tn = min(n, 100*last)\n-\t\t}\n+\t\t// Run more iterations than we think we\'ll need for a second (1.5x).\n+\t\t// Don\'t grow too fast in case we had timing errors previously.\n \t\t// Be sure to run at least one more than last time.\n-\t\tn = max(n, last+1)\n+\t\tn = max(min(n+n/2, 100*last), last+1)\n \t\t// Round up to something easy to read.\n \t\tn = roundUp(n)\n \t\tb.runN(n)\n```

## コアとなるコードの解説

この変更は、`src/pkg/testing/benchmark.go`ファイルの`(*B).launch()`メソッド内で行われています。このメソッドは、ベンチマークの実行ループを管理し、次のイテレーション数`n`を決定する役割を担っています。

変更点は以下の通りです。

- **削除された行**:
    ```go
    -		// If the last run was small, don't grow too fast.
    -		if last < 1000 {
    -			n = min(n, 100*last)
    -		}
    -		// Be sure to run at least one more than last time.
    -		n = max(n, last+1)
    ```
    これらの行は、前回のイテレーション数`last`が1000未満の場合に`n`の増加を制限し(`last`の100倍まで)、かつ`n`が常に`last+1`以上であることを保証するロジックでした。これは、元のコミットでベンチマークを高速化するために削除された部分です。

- **追加された行**:
    ```go
    +		// Run more iterations than we think we'll need for a second (1.5x).
    +		// Don't grow too fast in case we had timing errors previously.
    +		// Be sure to run at least one more than last time.
    +		n = max(min(n+n/2, 100*last), last+1)
    ```
    この一行は、削除された複数の行の機能を置き換えるものです。
    - `n+n/2`: 算出された理想的なイテレーション数`n`を1.5倍します。これは、目標時間(1秒)よりも少し多めに実行することで、より安定した測定結果を得るためのヒューリスティックです。
    - `min(n+n/2, 100*last)`: 1.5倍された`n`と、前回のイテレーション数`last`の100倍のうち、小さい方を選択します。これにより、イテレーション数が急激に増加しすぎるのを防ぎます。これは、削除された`if last < 1000`ブロックの目的と似ています。
    - `max(..., last+1)`: 上記の結果と`last+1`のうち、大きい方を選択します。これにより、`n`が常に前回のイテレーション数より少なくとも1多くなることを保証し、ベンチマークが進行するようにします。

このコミットは、ベンチマークのイテレーション数調整ロジックを、元のコミットが導入する前の状態に戻すことで、`windows-amd64-race`ビルドでのパフォーマンス回帰を修正しています。これは、特定の最適化がすべての環境で期待通りに機能するとは限らないという現実と、GoのCIシステムがそのような問題を効果的に検出していることを示しています。

## 関連リンク

- **このコミットのGo Gerrit上のChange-ID**: `https://golang.org/cl/105950044`
- **元に戻されたコミットのGo Gerrit上のChange-ID**: `https://golang.org/cl/101970047`

## 参考にした情報源リンク

- Go `testing`パッケージのドキュメント: [https://pkg.go.dev/testing](https://pkg.go.dev/testing)
- Go Race Detectorのドキュメント: [https://go.dev/doc/articles/race_detector](https://go.dev/doc/articles/race_detector)
- Goのベンチマークに関するブログ記事やチュートリアル(一般的な知識として)
- Goのソースコード(`src/pkg/testing/benchmark.go`)
- Go Gerrit (Go Code Review) のコミット履歴# [インデックス 19538] ファイルの概要

このコミットは、Go言語の標準ライブラリである`src/pkg/testing/benchmark.go`に対する変更です。このファイルは、Goのベンチマークテストフレームワークの核心部分を担っており、ベンチマーク関数の実行ロジック、特にベンチマークの実行回数(イテレーション数)の決定と調整に関する処理が含まれています。Goのベンチマークは、`testing`パッケージが提供する`*testing.B`型を通じて行われ、このファイルはその`*testing.B`型の内部動作、特に`b.N`の値がどのように決定され、ベンチマークがどのように実行されるかを定義しています。

## コミット

undo CL 101970047 / 30307cc8bef2

makes windows-amd64-race benchmarks slower

««« original CL description testing: make benchmarking faster

Allow the number of benchmark iterations to grow faster for fast benchmarks, and don't round up twice.

Using the default benchtime, this CL reduces wall clock time to run benchmarks:

net/http 49s -> 37s (-24%) runtime 8m31s -> 5m55s (-30%) bytes 2m37s -> 1m29s (-43%) encoding/json 29s -> 21s (-27%) strings 1m16s -> 53s (-30%)

LGTM=crawshaw R=golang-codereviews, crawshaw CC=golang-codereviews https://golang.org/cl/101970047 »»»

TBR=josharian CC=golang-codereviews https://golang.org/cl/105950044


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

[https://github.com/golang/go/commit/50365666c7264ac0608220adeff2c4f503b8fb67](https://github.com/golang/go/commit/50365666c7264ac0608220adeff2c4f503b8fb67)

## 元コミット内容

このコミットは、以前のコミット `CL 101970047` (ハッシュ `30307cc8bef2`) を元に戻すものです。元となったコミットの目的は、Goのベンチマーク実行を高速化することでした。具体的には、高速なベンチマークに対してイテレーション数(`b.N`)の増加をより速くし、かつイテレーション数の丸め込み処理を二重に行わないようにすることで、ベンチマークの実行時間を短縮しようとしました。

元のコミットの説明には、デフォルトの`benchtime`(ベンチマークの最小実行時間)を使用した場合に、いくつかのパッケージでベンチマークの実行時間が大幅に短縮された例が挙げられています。

- `net/http`: 49秒 → 37秒 (-24%)
- `runtime`: 8分31秒 → 5分55秒 (-30%)
- `bytes`: 2分37秒 → 1分29秒 (-43%)
- `encoding/json`: 29秒 → 21秒 (-27%)
- `strings`: 1分16秒 → 53秒 (-30%)

これらの改善は、開発者がより迅速にベンチマークを実行し、パフォーマンスの回帰を早期に発見するのに役立つと期待されていました。

## 変更の背景

このコミットの背景には、パフォーマンス改善を目的とした先行コミット(`CL 101970047`)が、特定の環境で予期せぬ負の影響を与えたという問題があります。先行コミットは、ベンチマークのイテレーション数決定ロジックを最適化することで、全体的なベンチマーク実行時間の短縮を目指しました。しかし、その変更が`windows-amd64-race`ビルド、すなわちWindowsオペレーティングシステム上でAMD64アーキテクチャ向けにビルドされ、かつGoのデータ競合検出器(Race Detector)が有効になっている環境で、ベンチマークの実行を遅くするという問題を引き起こしました。

Goのプロジェクトでは、様々なプラットフォームや設定(例えば、Race Detectorの有効/無効)で継続的なインテグレーション(CI)テストが実行されており、この問題はCIシステムによって検出されたと考えられます。特定の環境でのパフォーマンス回帰は、Goの品質と安定性を維持する上で許容できないため、先行コミットの変更を元に戻す(revertする)という決定がなされました。これは、パフォーマンスの最適化が、すべてのサポート対象環境で一貫した振る舞いを保証する必要があるというGo開発チームの原則を示しています。

## 前提知識の解説

このコミットを理解するためには、以下のGo言語のベンチマークに関する前提知識が必要です。

### Goのベンチマークシステム

Go言語には、`testing`パッケージを通じて組み込みのベンチマーク機能が提供されています。
- **ベンチマーク関数**: `func BenchmarkXxx(b *testing.B)`というシグネチャを持つ関数で定義されます。
- **`*testing.B`**: ベンチマーク実行のコンテキストを提供する構造体です。
    - **`b.N`**: ベンチマーク関数が実行されるイテレーション数(ループ回数)です。Goのベンチマークシステムは、指定された`benchtime`(デフォルト1秒)を満たすように`b.N`の値を自動的に調整します。最初は小さな`b.N`で実行し、時間が足りなければ`b.N`を増やして再実行します。
    - **`b.Run(name string, f func(b *B))`**: サブベンチマークを定義するために使用されます。
    - **`b.ReportAllocs()`**: メモリ割り当てを報告するように設定します。
    - **`b.SetBytes(n int64)`**: 1イテレーションあたりに処理されるバイト数を設定し、スループット(バイト/秒)を計算できるようにします。
    - **`b.ResetTimer()`**: タイマーをリセットします。
    - **`b.StartTimer()`**: タイマーを開始します。
    - **`b.StopTimer()`**: タイマーを停止します。

### ベンチマークのイテレーション数決定ロジック

Goのベンチマークは、安定した測定結果を得るために、一定の時間(デフォルト1秒)以上実行されるように設計されています。このため、`b.N`の値は動的に調整されます。
1.  最初は小さな`b.N`(例: 1)でベンチマークを実行します。
2.  実行にかかった時間と、目標とする`benchtime`を比較します。
3.  もし実行時間が短すぎる場合、次の`b.N`の値を計算します。この計算は、前回の実行時間と`b.N`から1イテレーションあたりの平均時間(`nsPerOp`)を推定し、目標時間に必要なイテレーション数を算出します。
4.  算出された`b.N`は、前回の`b.N`よりも大きく、かつ急激に増加しすぎないように調整されます。また、読みやすい値に丸められることもあります。
5.  このプロセスを繰り返し、ベンチマークが十分な時間実行されるまで`b.N`を増やしていきます。

### `windows-amd64-race`ビルド

- **Windows**: Microsoft Windowsオペレーティングシステム。
- **AMD64**: 64ビットのx86アーキテクチャ(Intel 64やAMD64など)。
- **Race Detector**: Goに組み込まれているデータ競合検出器です。`go run -race`や`go build -race`、`go test -race`のように`-race`フラグを付けてビルド・実行することで有効になります。Race Detectorは、並行処理におけるデータ競合(複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つが書き込み操作である場合に発生するバグ)を検出するために、実行時に追加のインストゥルメンテーション(計測コード)を挿入します。このインストゥルメンテーションは、プログラムの実行速度を大幅に低下させる可能性があります(通常、2倍から20倍遅くなる)。

`windows-amd64-race`ビルドでベンチマークが遅くなったということは、Race Detectorが有効な環境でのベンチマークのイテレーション数調整ロジックに問題があったことを示唆しています。Race Detectorのオーバーヘッドは非常に大きいため、イテレーション数の計算がRace Detectorの存在を考慮せずに最適化されると、予期せぬパフォーマンスの低下を招くことがあります。

## 技術的詳細

このコミットは、`src/pkg/testing/benchmark.go`内の`(*B).launch()`メソッドにおけるベンチマークのイテレーション数`n`の計算ロジックを元に戻すものです。

元のコミット(`CL 101970047`)では、`b.N`の次の値`n`を決定する際に、以下の変更が加えられました。

**元のコミットでの変更点(このコミットで元に戻される部分):**

```diff
--- a/src/pkg/testing/benchmark.go
+++ b/src/pkg/testing/benchmark.go
@@ -205,12 +205,10 @@ func (b *B) launch() {
 		} else {
 			n = int(d.Nanoseconds() / b.nsPerOp())
 		}
-		// If the last run was small, don't grow too fast.
-		if last < 1000 {
-			n = min(n, 100*last)
-		}
+		// Run more iterations than we think we'll need for a second (1.5x).
+		// Don't grow too fast in case we had timing errors previously.
 		// Be sure to run at least one more than last time.
-		n = max(n, last+1)
+		n = max(min(n+n/2, 100*last), last+1)
 		// Round up to something easy to read.
 		n = roundUp(n)
 		b.runN(n)

具体的には、以下の行が削除され、新しい行が追加されました。

削除された行(元のコミットで追加されたが、このコミットで削除される):

		// If the last run was small, don't grow too fast.
		if last < 1000 {
			n = min(n, 100*last)
		}
		// Be sure to run at least one more than last time.
		n = max(n, last+1)

追加された行(元のコミットで削除されたが、このコミットで復活する):

		// Run more iterations than we think we'll need for a second (1.5x).
		// Don't grow too fast in case we had timing errors previously.
		// Be sure to run at least one more than last time.
		n = max(min(n+n/2, 100*last), last+1)

この変更の核心は、nの計算方法です。

  • 元のコミットのロジック(削除された方):

    • n = int(d.Nanoseconds() / b.nsPerOp()) で、目標時間(d)と1操作あたりのナノ秒(b.nsPerOp())から理想的なイテレーション数を算出します。
    • if last < 1000 { n = min(n, 100*last) }: 前回のイテレーション数lastが1000未満の場合、nlastの100倍を超えないように制限します。これは、イテレーション数が急激に増えすぎるのを防ぐためのものです。
    • n = max(n, last+1): nが少なくとも前回のイテレーション数lastより1多くなることを保証します。これにより、ベンチマークが進行し、最終的に十分なイテレーション数に達するようにします。
  • このコミットで復活するロジック(追加された方):

    • n = int(d.Nanoseconds() / b.nsPerOp()) で理想的なイテレーション数を算出するところは同じです。
    • n = max(min(n+n/2, 100*last), last+1): この一行で、イテレーション数の調整を行います。
      • n+n/2: 算出されたnの1.5倍を計算します。これは「1秒に必要なイテレーション数よりも多く実行する(1.5倍)」というコメントに対応します。
      • min(n+n/2, 100*last): 上記の1.5倍されたnと、lastの100倍の小さい方を取ります。これにより、イテレーション数の急激な増加をlastの100倍に制限します。
      • max(..., last+1): 最終的なnが、少なくとも前回のイテレーション数lastより1多くなることを保証します。

元のコミットの意図は、last < 1000という条件分岐をなくし、nの計算をよりシンプルかつ効率的にすることでした。しかし、この変更がwindows-amd64-race環境で問題を引き起こしたため、元のロジックに戻されました。Race Detectorが有効な場合、nsPerOpの計算が不安定になったり、ベンチマークの実行時間が非線形に増加したりする可能性があり、その結果、イテレーション数の調整ロジックがうまく機能しなかったと考えられます。特に、last < 1000のような特定の閾値での挙動の違いが、Race Detectorのオーバーヘッドと組み合わさって、パフォーマンスの回帰を引き起こした可能性があります。

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

diff --git a/src/pkg/testing/benchmark.go b/src/pkg/testing/benchmark.go
index 43bbecbdc6..1fbf5c8615 100644
--- a/src/pkg/testing/benchmark.go
+++ b/src/pkg/testing/benchmark.go
@@ -205,12 +205,10 @@ func (b *B) launch() {
 		} else {\n \t\t\tn = int(d.Nanoseconds() / b.nsPerOp())\n \t\t}\n-\t\t// If the last run was small, don\'t grow too fast.\n-\t\tif last < 1000 {\n-\t\t\tn = min(n, 100*last)\n-\t\t}\n+\t\t// Run more iterations than we think we\'ll need for a second (1.5x).\n+\t\t// Don\'t grow too fast in case we had timing errors previously.\n \t\t// Be sure to run at least one more than last time.\n-\t\tn = max(n, last+1)\n+\t\tn = max(min(n+n/2, 100*last), last+1)\n \t\t// Round up to something easy to read.\n \t\tn = roundUp(n)\n \t\tb.runN(n)\n```

## コアとなるコードの解説

この変更は、`src/pkg/testing/benchmark.go`ファイルの`(*B).launch()`メソッド内で行われています。このメソッドは、ベンチマークの実行ループを管理し、次のイテレーション数`n`を決定する役割を担っています。

変更点は以下の通りです。

- **削除された行**:
    ```go
    -		// If the last run was small, don't grow too fast.
    -		if last < 1000 {
    -			n = min(n, 100*last)
    -		}
    -		// Be sure to run at least one more than last time.
    -		n = max(n, last+1)
    ```
    これらの行は、前回のイテレーション数`last`が1000未満の場合に`n`の増加を制限し(`last`の100倍まで)、かつ`n`が常に`last+1`以上であることを保証するロジックでした。これは、元のコミットでベンチマークを高速化するために削除された部分です。

- **追加された行**:
    ```go
    +		// Run more iterations than we think we'll need for a second (1.5x).
    +		// Don't grow too fast in case we had timing errors previously.
    +		// Be sure to run at least one more than last time.
    +		n = max(min(n+n/2, 100*last), last+1)
    ```
    この一行は、削除された複数の行の機能を置き換えるものです。
    - `n+n/2`: 算出された理想的なイテレーション数`n`を1.5倍します。これは、目標時間(1秒)よりも少し多めに実行することで、より安定した測定結果を得るためのヒューリスティックです。
    - `min(n+n/2, 100*last)`: 1.5倍された`n`と、前回のイテレーション数`last`の100倍のうち、小さい方を選択します。これにより、イテレーション数が急激に増加しすぎるのを防ぎます。これは、削除された`if last < 1000`ブロックの目的と似ています。
    - `max(..., last+1)`: 上記の結果と`last+1`のうち、大きい方を選択します。これにより、`n`が常に前回のイテレーション数より少なくとも1多くなることを保証し、ベンチマークが進行するようにします。

このコミットは、ベンチマークのイテレーション数調整ロジックを、元のコミットが導入する前の状態に戻すことで、`windows-amd64-race`ビルドでのパフォーマンス回帰を修正しています。これは、特定の最適化がすべての環境で期待通りに機能するとは限らないという現実と、GoのCIシステムがそのような問題を効果的に検出していることを示しています。

## 関連リンク

- **このコミットのGo Gerrit上のChange-ID**: `https://golang.org/cl/105950044`
- **元に戻されたコミットのGo Gerrit上のChange-ID**: `https://golang.org/cl/101970047`

## 参考にした情報源リンク

- Go `testing`パッケージのドキュメント: [https://pkg.go.dev/testing](https://pkg.go.dev/testing)
- Go Race Detectorのドキュメント: [https://go.dev/doc/articles/race_detector](https://go.go.dev/doc/articles/race_detector)
- Goのベンチマークに関するブログ記事やチュートリアル(一般的な知識として)
- Goのソースコード(`src/pkg/testing/benchmark.go`)
- Go Gerrit (Go Code Review) のコミット履歴