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

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

このコミットは、Go言語の標準ライブラリであるregexpパッケージ内のベンチマークテストの修正に関するものです。具体的には、exec_test.goファイル内の特定のベンチマークケースにおける入力サイズの指定ミスを修正しています。

コミット

commit c4aa9c5c4ef32cdc65d29ac7e7cfa96fdbf7d394
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Thu Aug 29 13:55:30 2013 -0700

    regexp: fix a benchmark case

    I noticed that this one benchmark in particular was very
    noisy.  Looking into it, I saw that the table was wrong
    and inconsistent with the lines above and below.

    R=golang-dev, crawshaw
    CC=golang-dev
    https://golang.org/cl/13393045

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

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

元コミット内容

このコミットの元の内容は以下の通りです。

「regexp: fix a benchmark case」

これは、regexpパッケージのベンチマークケースを修正するという簡潔な目的を示しています。コミットメッセージの本文では、特定のベンチマークが非常にノイズが多く、その原因がテーブルの誤りであり、上下の行と一貫性がなかったためであると説明されています。

変更の背景

Go言語の標準ライブラリは、そのパフォーマンスと効率性が重視されています。そのため、各パッケージには厳密なベンチマークテストが組み込まれており、コードの変更がパフォーマンスに与える影響を継続的に監視しています。ベンチマークテストは、特定の操作がどれくらいの時間やリソースを消費するかを測定し、回帰や改善を特定するために不可欠です。

このコミットの背景には、regexpパッケージのベンチマークテストにおいて、BenchmarkMatchMedium_32という特定のケースが「非常にノイズが多い(very noisy)」という問題がありました。ベンチマークがノイズを持つとは、測定結果が不安定で、実行ごとに大きく変動することを意味します。このような不安定なベンチマークは、実際のパフォーマンスの変化を正確に評価することを困難にし、開発者がパフォーマンスの回帰や改善を特定する際の妨げとなります。

コミットの作者であるBrad Fitzpatrick氏は、このノイズの原因を調査した結果、ベンチマークの入力サイズを定義している部分に誤りがあることを発見しました。具体的には、BenchmarkMatchMedium_32の入力サイズが、他の関連するベンチマークケースと論理的に一貫していなかったため、期待される動作と異なる結果が生じていたと考えられます。この不整合が、ベンチマークの不安定性(ノイズ)を引き起こしていた主要な原因です。

この修正は、ベンチマークの信頼性を向上させ、regexpパッケージのパフォーマンス評価をより正確に行えるようにすることを目的としています。

前提知識の解説

Go言語のベンチマークテスト

Go言語には、標準ライブラリとしてtestingパッケージが提供されており、ユニットテストだけでなくベンチマークテストもサポートしています。ベンチマーク関数はBenchmarkXxx(*testing.B)というシグネチャを持ち、go test -bench .コマンドで実行されます。

  • *testing.B: ベンチマーク関数に渡される構造体で、ベンチマークの実行回数(b.N)や時間計測の開始/停止(b.StartTimer(), b.StopTimer())などの機能を提供します。
  • b.N: ベンチマーク関数が実行されるループ回数を示します。testingパッケージは、ベンチマークが安定した結果を出すのに十分な回数だけ関数を実行するようにb.Nを自動的に調整します。
  • benchmark(b, pattern, size): このコミットで修正されているベンチマーク関数内で呼び出されているヘルパー関数です。patternは正規表現パターン、sizeはマッチング対象の入力文字列のサイズを指します。

ビットシフト演算子 (<<)

Go言語を含む多くのプログラミング言語で使われるビットシフト演算子です。 X << Y は、XのビットをYビットだけ左にシフトすることを意味します。これは実質的にX * (2^Y)と同じ効果を持ちます。

例:

  • 1 << 01 * (2^0) = 1 * 1 = 1
  • 1 << 101 * (2^10) = 1 * 1024 = 1024 (1KB)
  • 32 << 1032 * (2^10) = 32 * 1024 = 32768 (32KB)
  • 1 << 201 * (2^20) = 1 * 1048576 = 1048576 (1MB)
  • 32 << 2032 * (2^20) = 32 * 1048576 = 33554432 (32MB)

このコミットでは、ベンチマークの入力サイズをバイト単位で指定するためにこの演算子が使われています。例えば、1<<10は1KB、32<<10は32KBを意味します。

正規表現のマッチングパフォーマンス

正規表現のマッチングは、使用される正規表現の複雑さ、入力文字列の長さ、そして正規表現エンジンの実装によってパフォーマンスが大きく変動します。ベンチマークは、これらの要因がパフォーマンスにどのように影響するかを測定するために重要です。

技術的詳細

このコミットは、src/pkg/regexp/exec_test.goファイル内のBenchmarkMatchMedium_32というベンチマーク関数の引数を修正しています。

元のコードでは、BenchmarkMatchMedium_32benchmark(b, medium, 1<<0)と呼び出されていました。ここで、1<<01を意味します。つまり、このベンチマークは入力サイズが1バイトの文字列に対して実行されていました。

しかし、このベンチマーク関数はBenchmarkMatchMedium_32という名前であり、他の関連するベンチマーク関数(例: BenchmarkMatchMedium_1K, BenchmarkMatchMedium_32K, BenchmarkMatchMedium_1Mなど)の命名規則や、その入力サイズのパターンから逸脱していました。他のBenchmarkMatchMedium_X系のベンチマークは、Xの部分が入力サイズ(KBやMB)を示しており、32という数字は通常32KBや32MBといったより大きなサイズを連想させます。

コミットメッセージによると、この不整合がベンチマークの「ノイズ」の原因となっていました。入力サイズが1バイトという非常に小さい値であったため、ベンチマークの実行時間が短すぎたり、システムの状態(キャッシュ、スケジューリングなど)に過度に影響されたりして、安定した測定結果が得られなかった可能性があります。

修正後のコードでは、BenchmarkMatchMedium_32benchmark(b, medium, 32<<0)と呼び出されています。ここで、32<<032を意味します。これにより、入力サイズが32バイトの文字列に対してベンチマークが実行されるようになります。

この変更により、BenchmarkMatchMedium_32は、その名前が示す「32」という数値と、他のBenchmarkMatchMedium_X系のベンチマークの入力サイズパターンにより一貫するようになりました。入力サイズが1バイトから32バイトに増えることで、ベンチマークの実行時間がわずかに長くなり、より安定した、信頼性の高い測定結果が得られることが期待されます。

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

--- a/src/pkg/regexp/exec_test.go
+++ b/src/pkg/regexp/exec_test.go
@@ -689,7 +689,7 @@ func BenchmarkMatchEasy1_1K(b *testing.B)   { benchmark(b, easy1, 1<<10) }\
 func BenchmarkMatchEasy1_32K(b *testing.B)  { benchmark(b, easy1, 32<<10) }\
 func BenchmarkMatchEasy1_1M(b *testing.B)   { benchmark(b, easy1, 1<<20) }\
 func BenchmarkMatchEasy1_32M(b *testing.B)  { benchmark(b, easy1, 32<<20) }\
-func BenchmarkMatchMedium_32(b *testing.B)  { benchmark(b, medium, 1<<0) }\
+func BenchmarkMatchMedium_32(b *testing.B)  { benchmark(b, medium, 32<<0) }\
 func BenchmarkMatchMedium_1K(b *testing.B)  { benchmark(b, medium, 1<<10) }\
 func BenchmarkMatchMedium_32K(b *testing.B) { benchmark(b, medium, 32<<10) }\
 func BenchmarkMatchMedium_1M(b *testing.B)  { benchmark(b, medium, 1<<20) }\

コアとなるコードの解説

変更はsrc/pkg/regexp/exec_test.goファイルの1行のみです。

  • 変更前: func BenchmarkMatchMedium_32(b *testing.B) { benchmark(b, medium, 1<<0) }
    • benchmark関数に渡される第3引数(入力サイズ)が1<<0、つまり1でした。これは、正規表現のマッチングテストが1バイトの入力文字列に対して行われることを意味します。
  • 変更後: func BenchmarkMatchMedium_32(b *testing.B) { benchmark(b, medium, 32<<0) }
    • benchmark関数に渡される第3引数(入力サイズ)が32<<0、つまり32に変更されました。これにより、正規表現のマッチングテストが32バイトの入力文字列に対して行われるようになります。

この修正は、ベンチマーク関数の名前BenchmarkMatchMedium_32と、その入力サイズがより整合的になるように行われました。また、コミットメッセージにあるように、この変更によってベンチマークの「ノイズ」が減少し、より安定した測定結果が得られるようになることが期待されます。これは、非常に小さい入力サイズでのベンチマークは、オーバーヘッドやシステムの状態に影響されやすく、結果が不安定になりがちであるためです。入力サイズを32バイトに増やすことで、より意味のあるパフォーマンス測定が可能になります。

関連リンク

  • Go言語のregexpパッケージのドキュメント: https://pkg.go.dev/regexp
  • Go言語のtestingパッケージのドキュメント: https://pkg.go.dev/testing
  • Go言語のベンチマークに関する公式ブログ記事 (例: "Go's work-stealing scheduler"): https://go.dev/blog/go11bench (これは一般的なベンチマークの概念を理解するのに役立ちますが、直接このコミットに関連するものではありません)

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特にsrc/pkg/regexp/exec_test.goの周辺コード)
  • ビットシフト演算子に関する一般的なプログラミング知識
  • ベンチマークテストの概念に関する一般的な知識
  • コミットメッセージに記載されているGo CL (Code Review) リンク: https://golang.org/cl/13393045 (これはコミットの直接的な情報源です)
  • GitHubのコミットページ: https://github.com/golang/go/commit/c4aa9c5c4ef32cdc65d29ac7e7cfa96fdbf7d394