[インデックス 16461] ファイルの概要
このコミットは、Go言語の標準ライブラリであるtesting
パッケージ内のベンチマーク機能における丸め処理のバグ修正と、関連するテストの改善を目的としています。具体的には、roundDown10
関数の丸め誤差を修正し、その主要な利用元であるroundUp
関数に対するテストカバレッジを追加しています。
コミット
commit a28609d66f74383ad23015aa810e3c76444057f5
Author: Dave Cheney <dave@cheney.net>
Date: Sun Jun 2 09:13:12 2013 +1000
testing: fix rounding error in roundDown10
Fixes #5599.
Thanks to minux.ma for the suggested fix.
As we now have a harness to test testing internal functions I added some coverage for testing.roundUp, as it is the main consumer of roundDown10.
R=minux.ma, kr, r
CC=golang-dev
https://golang.org/cl/9926043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a28609d66f74383ad23015aa810e3c76444057f5
元コミット内容
testing: fix rounding error in roundDown10
このコミットは、testing
パッケージ内のroundDown10
関数における丸め誤差を修正します。
これはIssue #5599を修正するものです。
minux.ma氏の提案された修正に感謝します。
内部のtesting
関数をテストするためのハーネスができたため、roundDown10
の主要な利用者であるtesting.roundUp
のテストカバレッジを追加しました。
変更の背景
Go言語のtesting
パッケージは、ベンチマークテストを実行する際に、テストの実行回数を動的に調整するロジックを持っています。この調整には、特定の数値を10の累乗に丸めるroundDown10
関数や、特定の形式(1eX, 2eX, 5eX)に丸めるroundUp
関数が使用されます。
このコミットが行われる前、roundDown10
関数には、入力値がちょうど10の累乗である場合に誤った丸めを行うバグが存在していました。例えば、roundDown10(10)
が期待される10
ではなく1
を返してしまうといった問題です。これは、ベンチマークの実行回数計算に影響を与え、テストの信頼性や効率に問題を引き起こす可能性がありました。
コミットメッセージにある「Fixes #5599」は、このバグがGoのIssueトラッカーで報告されていたことを示唆しています。また、「As we now have a harness to test testing internal functions」という記述から、この修正と同時に、testing
パッケージの内部関数(特にroundUp
)をテストするためのフレームワークが整備されたことが伺えます。これにより、roundDown10
の修正だけでなく、その影響を受けるroundUp
関数も適切に動作することを保証するためのテストが追加されました。
前提知識の解説
Go言語のtesting
パッケージとベンチマーク
Go言語には、標準でテストとベンチマークをサポートするtesting
パッケージが組み込まれています。
- テスト (Tests):
func TestXxx(*testing.T)
という形式の関数で記述され、コードの正確性を検証します。 - ベンチマーク (Benchmarks):
func BenchmarkXxx(*testing.B)
という形式の関数で記述され、コードのパフォーマンスを測定します。
ベンチマーク実行時、testing
パッケージはb.N
という変数を通じて、ベンチマーク対象のコードが実行される回数を自動的に調整します。これは、測定の精度を確保しつつ、適切な時間でベンチマークが完了するようにするためです。このb.N
の値を決定する内部ロジックで、数値を丸める処理が使用されます。
roundDown10
関数とroundUp
関数
Goのベンチマークシステムでは、実行回数を決定する際に、特定の基準値に基づいて数値を丸める処理が行われます。
roundDown10(n int) int
: 入力n
を10の最も近い累乗(1, 10, 100, ...
)に切り捨てる関数です。例えば、roundDown10(123)
は100
を返します。roundUp(n int) int
: 入力n
を1eX, 2eX, 5eX
の形式の数に切り上げる関数です。これは、ベンチマークの実行回数を決定する際に、10, 20, 50, 100, 200, 500, ...
といった系列の数に丸めるために使用されます。
これらの関数は、ベンチマークの実行回数を効率的かつ適切に決定するために不可欠な内部ヘルパー関数です。
丸め誤差
コンピュータにおける数値の丸め処理は、特に境界値において予期せぬ結果を生むことがあります。このコミットで修正されたroundDown10
のバグは、まさにこのような境界値(例: n=10
)での処理の不備に起因していました。
技術的詳細
このコミットは、主に以下の3つのファイルにわたる変更を含んでいます。
-
src/pkg/testing/benchmark.go
:roundDown10
関数の修正: 変更前:for n > 10
変更後:for n >= 10
この変更は、n
がちょうど10
である場合にループが実行されないというバグを修正します。変更前はn=10
の場合、10 > 10
がfalse
となりループに入らず、tens
が0
のまま1 * 10^0 = 1
を返していました。変更後は10 >= 10
がtrue
となり、ループが1回実行され、n
が1
になりtens
が1
になります。結果として1 * 10^1 = 10
が返され、期待通りの動作になります。roundUp
関数の修正: 変更前は複数のif
文で条件分岐していましたが、これをswitch
文に置き換えています。 変更前:
変更後:if n < (2 * base) { return 2 * base } if n < (5 * base) { return 5 * base } return 10 * base
この変更により、switch { case n <= base: return base case n <= (2 * base): return 2 * base case n <= (5 * base): return 5 * base default: return 10 * base }
n
がbase
、2 * base
、5 * base
といった境界値と等しい場合も適切に処理されるようになります。特にn <= base
のケースが追加されたことで、n
がbase
以下の場合にbase
を返すという明確なロジックが追加されました。これにより、roundUp
のロジックがより堅牢になり、roundDown10
の修正と合わせて、ベンチマークの実行回数計算がより正確になります。
-
src/pkg/testing/benchmark_test.go
:roundDownTests
のテストケースの拡充:{10, 1}
が{10, 10}
に修正され、{100, 10}
が{100, 100}
に修正されました。これはroundDown10
の修正を反映したものです。 また、{101, 100}
,{999, 100}
,{1000, 1000}
,{1001, 1000}
といった、より多くの境界値や一般的なケースが追加され、テストカバレッジが向上しています。TestRoundDown10
のエラーメッセージの改善:t.Errorf
のフォーマット文字列が変更され、入力値tt.v
も表示されるようになり、デバッグが容易になりました。roundUpTests
とTestRoundUp
の新規追加:roundUp
関数に対する専用のテストケースとテスト関数が追加されました。これにより、roundUp
関数の動作が独立して検証できるようになり、roundDown10
の修正がroundUp
に与える影響も確認できるようになりました。テストケースには0, 1, 2, 5, 9, 999, 1000, 1400, 1700, 4999, 5000, 5001
など、様々な入力値が含まれており、roundUp
のロジックが広範囲にわたってテストされています。
-
src/pkg/testing/export_test.go
:RoundUp
のテスト用エクスポート:testing
パッケージの内部関数であるroundUp
を、テストコードからアクセスできるようにRoundUp
としてエクスポートしています。これにより、benchmark_test.go
からtesting.RoundUp
としてroundUp
関数を呼び出し、テストを実行することが可能になります。
これらの変更は、Goのベンチマーク機能の正確性と堅牢性を向上させる上で重要な役割を果たしています。
コアとなるコードの変更箇所
src/pkg/testing/benchmark.go
--- a/src/pkg/testing/benchmark.go
+++ b/src/pkg/testing/benchmark.go
@@ -138,7 +138,7 @@ func max(x, y int) int {
func roundDown10(n int) int {
var tens = 0
// tens = floor(log_10(n))
- for n > 10 {
+ for n >= 10 {
n = n / 10
tens++
}
@@ -153,13 +153,16 @@ func roundDown10(n int) int {
// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX].
func roundUp(n int) int {
base := roundDown10(n)
- if n < (2 * base) {
+ switch {
+ case n <= base:
+ return base
+ case n <= (2 * base):
return 2 * base
- }
- if n < (5 * base) {
+ case n <= (5 * base):
return 5 * base
+ default:
+ return 10 * base
}
- return 10 * base
}
// run times the benchmark function in a separate goroutine.
src/pkg/testing/benchmark_test.go
--- a/src/pkg/testing/benchmark_test.go
+++ b/src/pkg/testing/benchmark_test.go
@@ -13,19 +13,46 @@ var roundDownTests = []struct {
}{\n \t{1, 1},\n \t{9, 1},\n-\t{10, 1},\n+\t{10, 10},\n \t{11, 10},\n-\t{100, 10},\n-\t//\t{101, 100}, // issue 5599\n-\t{1000, 100},\n-\t//\t{1001, 1000}, // issue 5599\n+\t{100, 100},\n+\t{101, 100},\n+\t{999, 100},\n+\t{1000, 1000},\n+\t{1001, 1000},\n }\n \n func TestRoundDown10(t *testing.T) {\n \tfor _, tt := range roundDownTests {\n \t\tactual := testing.RoundDown10(tt.v)\n \t\tif tt.expected != actual {\n-\t\t\tt.Errorf(\"roundDown10: expected %v, actual %v\", tt.expected, actual)\n+\t\t\tt.Errorf(\"roundDown10(%d): expected %d, actual %d\", tt.v, tt.expected, actual)\n+\t\t}\n+\t}\n+}\n+\n+var roundUpTests = []struct {\n+\tv, expected int\n+}{\n+\t{0, 1},\n+\t{1, 1},\n+\t{2, 2},\n+\t{5, 5},\n+\t{9, 10},\n+\t{999, 1000},\n+\t{1000, 1000},\n+\t{1400, 2000},\n+\t{1700, 2000},\n+\t{4999, 5000},\n+\t{5000, 5000},\n+\t{5001, 10000},\n+}\n+\n+func TestRoundUp(t *testing.T) {\n+\tfor _, tt := range roundUpTests {\n+\t\tactual := testing.RoundUp(tt.v)\n+\t\tif tt.expected != actual {\n+\t\t\tt.Errorf(\"roundUp(%d): expected %d, actual %d\", tt.v, tt.expected, actual)\n \t\t}\n \t}\n }\n```
### `src/pkg/testing/export_test.go`
```diff
--- a/src/pkg/testing/export_test.go
+++ b/src/pkg/testing/export_test.go
@@ -4,4 +4,7 @@
package testing
-var RoundDown10 = roundDown10
+var (
+ RoundDown10 = roundDown10
+ RoundUp = roundUp
+)
コアとなるコードの解説
roundDown10
関数の修正 (src/pkg/testing/benchmark.go
)
元のroundDown10
関数は、入力n
を10の累乗に切り捨てることを意図していました。例えば、123
は100
に、9
は1
に丸められます。しかし、for n > 10
という条件では、n
がちょうど10
の場合にループが実行されませんでした。
- 変更前:
for n > 10
n = 10
の場合:10 > 10
はfalse
なのでループに入らない。tens
は0
のまま。結果、1 * 10^0 = 1
が返される。これは誤り。
- 変更後:
for n >= 10
n = 10
の場合:10 >= 10
はtrue
なのでループに入る。n
は1
になり、tens
は1
になる。ループ条件1 >= 10
はfalse
なのでループを抜ける。結果、1 * 10^1 = 10
が返される。これは正しい動作。
この修正により、10
や100
といった10の累乗が正しくそれ自身に丸められるようになり、ベンチマークの実行回数計算の基礎がより正確になりました。
roundUp
関数の修正 (src/pkg/testing/benchmark.go
)
roundUp
関数は、roundDown10
の結果であるbase
を基に、数値を1eX, 2eX, 5eX
の形式に切り上げる役割を担っています。元の実装は複数のif
文を使用していましたが、境界値の処理が曖昧になる可能性がありました。
- 変更前: 複数の
if
文n < (2 * base)
、n < (5 * base)
という条件は、n
がちょうど2 * base
や5 * base
である場合に、次の条件にフォールスルーしてしまう可能性がありました。
- 変更後:
switch
文とn <=
条件switch
文を使用し、case n <= base:
、case n <= (2 * base):
、case n <= (5 * base):
という明確な条件を導入しました。n <= base
のケースが追加されたことで、n
がbase
以下の場合にbase
を返すというロジックが明示されました。- 各ケースで
n <= ...
とすることで、境界値(例:n
がちょうど2 * base
の場合)が正しくその範囲に収まるように処理されます。 default
ケースで10 * base
を返すことで、それ以外の全てのケースが適切に処理されます。
この変更により、roundUp
関数のロジックがより明確になり、境界値を含むあらゆる入力に対して期待通りの丸めが行われることが保証されます。
テストの追加と改善 (src/pkg/testing/benchmark_test.go
)
roundDownTests
の更新:roundDown10
の修正に合わせて、10
や100
のテストケースの期待値が更新されました。また、101
,999
,1001
などの新しいテストケースが追加され、roundDown10
の堅牢性がより広範囲で検証されるようになりました。roundUpTests
とTestRoundUp
の新規追加:roundUp
関数はroundDown10
の主要な利用者であるため、その動作を独立して検証することが重要です。このコミットでは、roundUp
専用のテストケースとテスト関数が追加され、0
から5001
までの様々な入力値に対してroundUp
が正しく機能するかを確認できるようになりました。これにより、roundDown10
の修正がroundUp
の動作に悪影響を与えないことを保証し、全体的なベンチマークロジックの信頼性を高めています。
テスト用エクスポート (src/pkg/testing/export_test.go
)
Goでは、通常、パッケージ内部の関数は外部から直接アクセスできません。しかし、テストのために内部関数を公開したい場合があります。export_test.go
ファイルは、このような目的のために使用されます。
RoundUp
のエクスポート:var RoundUp = roundUp
という行を追加することで、testing
パッケージの外部にあるbenchmark_test.go
からtesting.RoundUp
として内部のroundUp
関数を呼び出せるようになりました。これにより、roundUp
関数に対する単体テストが可能になり、その正確性を保証できるようになります。
これらのコード変更は、Goのベンチマーク機能の基盤となる数値丸め処理の正確性を大幅に向上させ、より信頼性の高いパフォーマンス測定を可能にしました。
関連リンク
- Go言語の
testing
パッケージに関する公式ドキュメント: https://pkg.go.dev/testing - Go言語のベンチマークに関する公式ブログ記事 (例: "Go's work-stealing scheduler"): https://go.dev/blog/go1.14-performance (これは一般的なベンチマークの例であり、直接このコミットに関連するものではありませんが、Goのベンチマークの文脈を理解するのに役立ちます)
参考にした情報源リンク
- GitHub上のコミットページ: https://github.com/golang/go/commit/a28609d66f74383ad23015aa810e3c76444057f5
- GoのIssue #5599 (検索しましたが、Go言語の公式リポジトリでは見つかりませんでした。古いIssueであるか、別のリポジトリのIssueである可能性があります。)
- Go Gerrit Change-ID
9926043
(検索しましたが、Go Gerritシステムでは見つかりませんでした。古い変更リストであるか、番号が正しくない可能性があります。) - Go言語の
testing
パッケージのソースコード (src/pkg/testing/
): https://github.com/golang/go/tree/master/src/testing (コミット当時のパスはsrc/pkg/testing
でしたが、現在はsrc/testing
です。)