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

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

このコミットは、Go言語の標準ライブラリ testing パッケージにおける RunParallel ベンチマークヘルパー関数の潜在的な誤用を診断するための変更を導入します。具体的には、RunParallel のボディ関数内で PB.Next() が一度も呼び出されない場合に、ベンチマークが誤った結果を出すことを防ぐために、致命的なエラーを報告するようになります。

コミット

  • コミットハッシュ: 1163127def254969c92c20ce0e535690f3b1de4c
  • Author: Dmitriy Vyukov dvyukov@google.com
  • Date: Mon Feb 24 20:32:28 2014 +0400
  • Commit Message:
    testing: diagnose a potential misuse of RunParallel
    
    LGTM=bradfitz
    R=golang-codereviews, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/68030043
    

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

https://github.com/golang/go/commit/1163127def254969c92c20ce0e535690f3b1de4c

元コミット内容

testing: diagnose a potential misuse of RunParallel

LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/68030043

変更の背景

Go言語のベンチマーク機能 testing.B には、並列ベンチマークを実行するための RunParallel メソッドがあります。このメソッドは、複数のゴルーチンを起動し、それぞれがベンチマーク対象のコードを並行して実行できるように設計されています。RunParallel の引数として渡される body 関数は、PB.Next() メソッドを呼び出すことで、次のイテレーションに進むべきであることを testing フレームワークに通知します。

しかし、body 関数内で PB.Next() が一度も呼び出されない場合、RunParallel は無限ループに陥るか、あるいはベンチマークのイテレーションが全く実行されないにもかかわらず、ベンチマークが正常に完了したかのように見えてしまうという問題がありました。これは、ベンチマーク結果の信頼性を損なう潜在的な誤用です。

このコミットは、このような誤用を検出し、開発者に早期に警告するために導入されました。PB.Next() が一度も呼び出されなかった場合に b.Fatal を呼び出すことで、ベンチマークの誤った実行を防ぎ、より正確なベンチマーク結果を保証することを目的としています。

前提知識の解説

Go言語のベンチマーク

Go言語には、コードのパフォーマンスを測定するための組み込みのベンチマーク機能が testing パッケージに用意されています。ベンチマーク関数は BenchmarkXxx(*testing.B) というシグネチャを持ち、go test -bench=. コマンドで実行されます。

testing.B

testing.B はベンチマーク関数に渡される構造体で、ベンチマークの実行を制御するためのメソッドを提供します。主なフィールドとメソッドには以下のようなものがあります。

  • b.N: ベンチマーク対象のコードが実行されるイテレーション回数。testing フレームワークが自動的に調整し、安定した測定結果が得られるようにします。
  • b.ResetTimer(): タイマーをリセットします。セットアップコードの時間を測定から除外するために使用されます。
  • b.StartTimer(): タイマーを開始します。
  • b.StopTimer(): タイマーを停止します。
  • b.RunParallel(body func(*PB)): ベンチマークを並列に実行します。

testing.PB

testing.PBRunParallel メソッドに渡される body 関数に提供される構造体です。並列ベンチマークの各ゴルーチンが次のイテレーションに進むために使用します。

  • pb.Next(): このメソッドを呼び出すことで、現在のゴルーチンが次のベンチマークイテレーションを実行する準備ができたことを testing フレームワークに通知します。RunParallelbody 関数は、通常、ループ内で pb.Next() を呼び出し、そのループ内でベンチマーク対象のコードを実行します。pb.Next()false を返すと、すべてのイテレーションが完了したことを意味し、ループを終了します。

RunParallel の動作原理

RunParallel は、GOMAXPROCS の値に基づいて複数のゴルーチンを起動します。これらのゴルーチンは、PB.Next() を呼び出すことで、ベンチマークのイテレーションを協調して実行します。PB.Next() は、すべてのゴルーチンが次のイテレーションに進む準備ができたことを確認し、必要に応じて待機します。これにより、並列実行における同期が取られ、正確な並列ベンチマークが可能になります。

sync.WaitGroup

sync.WaitGroup は、複数のゴルーチンの完了を待機するために使用される同期プリミティブです。このコミットの変更箇所には直接関係ありませんが、RunParallel の内部実装でゴルーチンの完了を待つために使用されています。

技術的詳細

testing.B.RunParallel メソッドは、ベンチマーク対象のコードを複数のゴルーチンで並行して実行するためのメカニズムを提供します。このメソッドは、body func(*PB) という関数を引数として受け取ります。この body 関数は、各並列実行ゴルーチンによって呼び出され、ベンチマークの実際の作業を行います。

RunParallel の内部では、PB.Next() メソッドが呼び出されるたびに、ベンチマークのイテレーションカウンターが進められます。このカウンターは、ベンチマークが b.N 回のイテレーションを完了したかどうかを追跡するために使用されます。

このコミット以前は、もしユーザーが RunParallel に渡す body 関数内で pb.Next() を一度も呼び出さなかった場合、testing フレームワークはベンチマークのイテレーションが全く実行されていないことを検出できませんでした。結果として、ベンチマークは0イテレーションで完了したと見なされ、誤った(通常は非常に速い)結果を報告する可能性がありました。これは、ユーザーが RunParallel の意図する使い方を理解していないか、あるいは単純なプログラミングミスによるものでした。

この変更は、この問題を解決するために、RunParallel の実行が終了した後に、実際に PB.Next() が呼び出された回数 (n 変数でカウントされる) をチェックするロジックを追加します。もし n が0であった場合、つまり PB.Next() が一度も呼び出されなかった場合、それは RunParallel の誤用であると判断し、b.Fatal を呼び出してベンチマークを失敗させます。b.Fatal は、テストまたはベンチマークを即座に停止し、エラーメッセージを出力するメソッドです。これにより、開発者はベンチマークの誤った設定にすぐに気づくことができます。

この診断は、ベンチマークの信頼性を向上させ、開発者がより正確なパフォーマンス測定を行えるようにするために重要です。

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

変更は src/pkg/testing/benchmark.go ファイルの RunParallel メソッド内で行われました。

--- a/src/pkg/testing/benchmark.go
+++ b/src/pkg/testing/benchmark.go
@@ -417,6 +417,9 @@ func (b *B) RunParallel(body func(*PB)) {
 	\t\t}()
 	\t}\n \twg.Wait()\n+\tif n == 0 {\n+\t\tb.Fatal(\"RunParallel body did not not call PB.Next\")\n+\t}\n }\n \n // SetParallelism sets the number of goroutines used by RunParallel to p*GOMAXPROCS.

具体的には、wg.Wait() の直後に以下の3行が追加されました。

	if n == 0 {
		b.Fatal("RunParallel body did not not call PB.Next")
	}

コアとなるコードの解説

追加されたコードは非常にシンプルですが、その影響は大きいです。

  1. if n == 0 { ... }:
    • nRunParallel メソッド内で、PB.Next() が呼び出されるたびにインクリメントされるカウンターです。
    • この条件は、RunParallel に渡された body 関数が、その実行中に一度も PB.Next() を呼び出さなかった場合に真となります。
  2. b.Fatal("RunParallel body did not not call PB.Next"):
    • もし n が0であれば、b.Fatal が呼び出されます。
    • b.Fatal は、現在のベンチマーク(またはテスト)を失敗としてマークし、指定されたメッセージを出力して、そのベンチマークの実行を即座に停止します。
    • 出力されるメッセージ "RunParallel body did not not call PB.Next" は、問題の具体的な原因を開発者に明確に伝えます。

この変更により、RunParallel を使用するベンチマークが正しく記述されていることを強制し、誤ったベンチマーク結果が報告されることを防ぎます。これは、Goの testing パッケージの堅牢性を高めるための重要な改善です。

関連リンク

  • Go CL 68030043: https://golang.org/cl/68030043
    • このコミットに対応するGoのコードレビューシステム (Gerrit) のチェンジリストです。詳細な議論やレビューコメントが確認できます。

参考にした情報源リンク

I have generated the detailed explanation based on the provided commit data and the required chapter structure. I have also included relevant links and explained the technical details and background. I did not need to use `google_web_search` as the commit message and my existing knowledge of Go's `testing` package were sufficient to provide a comprehensive explanation.# [インデックス 18615] ファイルの概要

このコミットは、Go言語の標準ライブラリ `testing` パッケージにおける `RunParallel` ベンチマークヘルパー関数の潜在的な誤用を診断するための変更を導入します。具体的には、`RunParallel` のボディ関数内で `PB.Next()` が一度も呼び出されない場合に、ベンチマークが誤った結果を出すことを防ぐために、致命的なエラーを報告するようになります。

## コミット

-   **コミットハッシュ**: 1163127def254969c92c20ce0e535690f3b1de4c
-   **Author**: Dmitriy Vyukov <dvyukov@google.com>
-   **Date**: Mon Feb 24 20:32:28 2014 +0400
-   **Commit Message**:
    ```
    testing: diagnose a potential misuse of RunParallel

    LGTM=bradfitz
    R=golang-codereviews, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/68030043
    ```

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

[https://github.com/golang/go/commit/1163127def254969c92c20ce0e535690f3b1de4c](https://github.com/golang/go/commit/1163127def254969c92c20ce0e535690f3b1de4c)

## 元コミット内容

testing: diagnose a potential misuse of RunParallel

LGTM=bradfitz R=golang-codereviews, bradfitz CC=golang-codereviews https://golang.org/cl/68030043


## 変更の背景

Go言語のベンチマーク機能 `testing.B` には、並列ベンチマークを実行するための `RunParallel` メソッドがあります。このメソッドは、複数のゴルーチンを起動し、それぞれがベンチマーク対象のコードを並行して実行できるように設計されています。`RunParallel` の引数として渡される `body` 関数は、`PB.Next()` メソッドを呼び出すことで、次のイテレーションに進むべきであることを `testing` フレームワークに通知します。

しかし、`body` 関数内で `PB.Next()` が一度も呼び出されない場合、`RunParallel` は無限ループに陥るか、あるいはベンチマークのイテレーションが全く実行されないにもかかわらず、ベンチマークが正常に完了したかのように見えてしまうという問題がありました。これは、ベンチマーク結果の信頼性を損なう潜在的な誤用です。

このコミットは、このような誤用を検出し、開発者に早期に警告するために導入されました。`PB.Next()` が一度も呼び出されなかった場合に `b.Fatal` を呼び出すことで、ベンチマークの誤った実行を防ぎ、より正確なベンチマーク結果を保証することを目的としています。

## 前提知識の解説

### Go言語のベンチマーク

Go言語には、コードのパフォーマンスを測定するための組み込みのベンチマーク機能が `testing` パッケージに用意されています。ベンチマーク関数は `BenchmarkXxx(*testing.B)` というシグネチャを持ち、`go test -bench=.` コマンドで実行されます。

### `testing.B`

`testing.B` はベンチマーク関数に渡される構造体で、ベンチマークの実行を制御するためのメソッドを提供します。主なフィールドとメソッドには以下のようなものがあります。

-   `b.N`: ベンチマーク対象のコードが実行されるイテレーション回数。`testing` フレームワークが自動的に調整し、安定した測定結果が得られるようにします。
-   `b.ResetTimer()`: タイマーをリセットします。セットアップコードの時間を測定から除外するために使用されます。
-   `b.StartTimer()`: タイマーを開始します。
-   `b.StopTimer()`: タイマーを停止します。
-   `b.RunParallel(body func(*PB))`: ベンチマークを並列に実行します。

### `testing.PB`

`testing.PB` は `RunParallel` メソッドに渡される `body` 関数に提供される構造体です。並列ベンチマークの各ゴルーチンが次のイテレーションに進むために使用します。

-   `pb.Next()`: このメソッドを呼び出すことで、現在のゴルーチンが次のベンチマークイテレーションを実行する準備ができたことを `testing` フレームワークに通知します。`RunParallel` の `body` 関数は、通常、ループ内で `pb.Next()` を呼び出し、そのループ内でベンチマーク対象のコードを実行します。`pb.Next()` が `false` を返すと、すべてのイテレーションが完了したことを意味し、ループを終了します。

### `RunParallel` の動作原理

`RunParallel` は、`GOMAXPROCS` の値に基づいて複数のゴルーチンを起動します。これらのゴルーチンは、`PB.Next()` を呼び出すことで、ベンチマークのイテレーションを協調して実行します。`PB.Next()` は、すべてのゴルーチンが次のイテレーションに進む準備ができたことを確認し、必要に応じて待機します。これにより、並列実行における同期が取られ、正確な並列ベンチマークが可能になります。

### `sync.WaitGroup`

`sync.WaitGroup` は、複数のゴルーチンの完了を待機するために使用される同期プリミティブです。このコミットの変更箇所には直接関係ありませんが、`RunParallel` の内部実装でゴルーチンの完了を待つために使用されています。

## 技術的詳細

`testing.B.RunParallel` メソッドは、ベンチマーク対象のコードを複数のゴルーチンで並行して実行するためのメカニズムを提供します。このメソッドは、`body func(*PB)` という関数を引数として受け取ります。この `body` 関数は、各並列実行ゴルーチンによって呼び出され、ベンチマークの実際の作業を行います。

`RunParallel` の内部では、`PB.Next()` メソッドが呼び出されるたびに、ベンチマークのイテレーションカウンターが進められます。このカウンターは、ベンチマークが `b.N` 回のイテレーションを完了したかどうかを追跡するために使用されます。

このコミット以前は、もしユーザーが `RunParallel` に渡す `body` 関数内で `pb.Next()` を一度も呼び出さなかった場合、`testing` フレームワークはベンチマークのイテレーションが全く実行されていないことを検出できませんでした。結果として、ベンチマークは0イテレーションで完了したと見なされ、誤った(通常は非常に速い)結果を報告する可能性がありました。これは、ユーザーが `RunParallel` の意図する使い方を理解していないか、あるいは単純なプログラミングミスによるものでした。

この変更は、この問題を解決するために、`RunParallel` の実行が終了した後に、実際に `PB.Next()` が呼び出された回数 (`n` 変数でカウントされる) をチェックするロジックを追加します。もし `n` が0であった場合、つまり `PB.Next()` が一度も呼び出されなかった場合、それは `RunParallel` の誤用であると判断し、`b.Fatal` を呼び出してベンチマークを失敗させます。`b.Fatal` は、テストまたはベンチマークを即座に停止し、エラーメッセージを出力するメソッドです。これにより、開発者はベンチマークの誤った設定にすぐに気づくことができます。

この診断は、ベンチマークの信頼性を向上させ、開発者がより正確なパフォーマンス測定を行えるようにするために重要です。

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

変更は `src/pkg/testing/benchmark.go` ファイルの `RunParallel` メソッド内で行われました。

```diff
--- a/src/pkg/testing/benchmark.go
+++ b/src/pkg/testing/benchmark.go
@@ -417,6 +417,9 @@ func (b *B) RunParallel(body func(*PB)) {
 	\t\t}()
 	\t}\n \twg.Wait()\n+\tif n == 0 {\n+\t\tb.Fatal("RunParallel body did not not call PB.Next")\n+\t}\n }\n \n // SetParallelism sets the number of goroutines used by RunParallel to p*GOMAXPROCS.

具体的には、wg.Wait() の直後に以下の3行が追加されました。

	if n == 0 {
		b.Fatal("RunParallel body did not not call PB.Next")
	}

コアとなるコードの解説

追加されたコードは非常にシンプルですが、その影響は大きいです。

  1. if n == 0 { ... }:
    • nRunParallel メソッド内で、PB.Next() が呼び出されるたびにインクリメントされるカウンターです。
    • この条件は、RunParallel に渡された body 関数が、その実行中に一度も PB.Next() を呼び出さなかった場合に真となります。
  2. b.Fatal("RunParallel body did not not call PB.Next"):
    • もし n が0であれば、b.Fatal が呼び出されます。
    • b.Fatal は、現在のベンチマーク(またはテスト)を失敗としてマークし、指定されたメッセージを出力して、そのベンチマークの実行を即座に停止します。
    • 出力されるメッセージ "RunParallel body did not not call PB.Next" は、問題の具体的な原因を開発者に明確に伝えます。

この変更により、RunParallel を使用するベンチマークが正しく記述されていることを強制し、誤ったベンチマーク結果が報告されることを防ぎます。これは、Goの testing パッケージの堅牢性を高めるための重要な改善です。

関連リンク

  • Go CL 68030043: https://golang.org/cl/68030043
    • このコミットに対応するGoのコードレビューシステム (Gerrit) のチェンジリストです。詳細な議論やレビューコメントが確認できます。

参考にした情報源リンク