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

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

このコミットは、Go言語の標準ライブラリ math/big パッケージ内のテストスイートのパフォーマンス改善に関するものです。特に、go test -short フラグが指定された場合に、math/big/rat_test.go のテスト実行時間を大幅に短縮することを目的としています。

コミット

このコミットは、math/big パッケージのテスト、特に rat_test.go ファイル内のテストの実行時間を最適化します。go test -short コマンドが使用された際に、一部の時間がかかるテストケースをスキップすることで、テストスイート全体の実行時間を短縮します。これにより、開発サイクルにおけるフィードバックループが高速化され、CI/CDパイプラインの効率も向上します。

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

https://github.com/golang/go/commit/19bb42d637eb57d24ff905047ce468c034a9a0cf

元コミット内容

math/big: make tests faster by reducing coverage in --test.short mode.

The time to test all of math/big is now:
 default      => ~3min
 --test.short => 150ms

R=rsc
CC=golang-dev
https://golang.org/cl/7223054

変更の背景

math/big パッケージは、任意精度演算を提供するGo言語の重要なライブラリです。このパッケージのテストスイートは、その性質上、非常に多くのエッジケースや大規模な数値に対する検証を行うため、実行に時間がかかる傾向がありました。コミットメッセージによると、math/big パッケージ全体のテスト実行には約3分を要していました。

開発者が日常的にテストを実行する際、特に変更が小規模である場合や、CI/CD環境での迅速なフィードバックが必要な場合、このような長いテスト時間は生産性を低下させる要因となります。Goのテストフレームワークには、go test -short というフラグがあり、これを使用することで、時間がかかるテストをスキップし、より高速なテスト実行を可能にするメカニズムが提供されています。

このコミットの背景には、この go test -short フラグを math/big パッケージのテストに効果的に適用し、テスト時間を大幅に短縮するという明確な目的がありました。具体的には、デフォルトで約3分かかっていたテスト時間を、--test.short モードでは150ミリ秒にまで短縮することを目指しています。これにより、開発者はより迅速にテスト結果を得られるようになり、開発効率が向上します。

前提知識の解説

Go言語の math/big パッケージ

math/big パッケージは、Go言語で任意精度(多倍長)の整数、有理数、浮動小数点数を扱うための機能を提供します。標準の intfloat64 型では表現できない非常に大きな数値や、高い精度が要求される計算に用いられます。例えば、暗号通貨、科学計算、金融アプリケーションなどで利用されます。

  • big.Int: 任意精度の整数型。
  • big.Rat: 任意精度の有理数型(分数)。分子と分母が big.Int で表現されます。
  • big.Float: 任意精度の浮動小数点数型。

これらの型は、標準の組み込み型よりもメモリを多く消費し、演算も複雑になるため、計算に時間がかかる傾向があります。特に、文字列から数値への変換(例: Rat.SetString)や、非常に大きな数値の分布をテストするようなケースでは、その計算コストが顕著になります。

Go言語のテストと go test -short

Go言語には、標準で強力なテストフレームワークが組み込まれています。テストファイルは通常、テスト対象のファイルと同じディレクトリに _test.go というサフィックスを付けて配置されます。テスト関数は Test で始まり、*testing.T 型の引数を取ります。

go test コマンドは、これらのテストを実行します。 go test -v は詳細なテスト結果を表示します。

重要なのが go test -short フラグです。 このフラグは、テスト関数内で testing.Short() 関数を呼び出すことで利用できます。testing.Short() は、go test -short が指定されている場合に true を返します。これにより、開発者は時間がかかるテストやリソースを大量に消費するテストを、このフラグが指定された場合にのみスキップするロジックを実装できます。

import "testing"

func TestMyFeature(t *testing.T) {
    if testing.Short() {
        t.Skip("Skipping long test in short mode")
    }
    // 時間のかかるテストロジック
}

このメカニズムは、開発者が迅速なフィードバックを得るための「クイックテスト」と、網羅的な「フルテスト」を使い分けることを可能にします。

技術的詳細

このコミットの技術的な核心は、math/big/rat_test.go ファイル内の特定のテストケースを testing.Short() フラグと連携させることで、テストの実行時間を最適化することにあります。

具体的には、以下の2つの主要な変更が行われています。

  1. float64inputs 配列の変更と TestFloat64SpecialCases の調整:

    • float64inputs は、Rat.SetString メソッドのテスト入力として使用される文字列のスライスです。以前は、非常に時間がかかる入力に対して slow: というプレフィックスが付与されていました。
    • このコミットでは、slow: プレフィックスが long: に変更されました。
    • TestFloat64SpecialCases 関数内で、入力文字列が long: で始まる場合、testing.Short()true であれば、そのテストケースは continue ステートメントによってスキップされるようになりました。これにより、--test.short モードでは、これらの時間のかかる Rat.SetString のテストが実行されなくなります。
    • コメントも更新され、long: プレフィックスが --test.short モードでスキップされることを明示しています。
  2. TestFloat64Distribution のパラメータ調整:

    • TestFloat64Distribution は、浮動小数点数の分布をテストするための関数で、その性質上、多くの反復処理を伴い、実行に時間がかかります。
    • このテストには winceinc という2つのパラメータがあり、これらがテストの網羅性と実行時間に影響を与えます。
    • 変更前は、コメントアウトされた行で「quick test (<1s)」と「soak test (~75s)」のパラメータが示されていましたが、コードとしては常に「quick test」のパラメータが使用されていました。
    • このコミットでは、winceinc の値が testing.Short() の結果に基づいて動的に設定されるようになりました。
      • testing.Short()true の場合(--test.short モード)、winc = 10, einc = 500 が設定されます。これは「quick test」に相当し、実行時間は約12ミリ秒と非常に短くなります。
      • testing.Short()false の場合(デフォルトモード)、winc = 1, einc = 1 が設定されます。これは「soak test」に相当し、実行時間は約75秒と長くなりますが、より網羅的なテストが実行されます。

これらの変更により、go test -short を実行した際には、Rat.SetString の一部のテストがスキップされ、TestFloat64Distribution のテストがより少ない反復回数で実行されるため、テストスイート全体の実行時間が劇的に短縮されます。

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

変更は src/pkg/math/big/rat_test.go ファイルに集中しています。

--- a/src/pkg/math/big/rat_test.go
+++ b/src/pkg/math/big/rat_test.go
@@ -500,8 +500,8 @@ func TestIssue3521(t *testing.T) {
 	}
 }

-// Test inputs to Rat.SetString.  The optional prefix "slow:" skips
-// checks found to be slow for certain large rationals.
+// Test inputs to Rat.SetString.  The prefix "long:" causes the test
+// to be skipped in --test.short mode.  (The threshold is about 500us.)
 var float64inputs = []string{
 	//
 	// Constants plundered from strconv/testfp.txt.
@@ -630,8 +630,8 @@ var float64inputs = []string{
 	"-1e310",
 	"1e400",
 	"-1e400",
-	"1e400000",
-	"-1e400000",
+	"long:1e400000",
+	"long:-1e400000",

 	// denormalized
 	"1e-305",
@@ -649,10 +649,10 @@ var float64inputs = []string{
 	"2e-324",
 	// way too small
 	"1e-350",
-	"slow:1e-400000",
+	"long:1e-400000",
 	// way too small, negative
 	"-1e-350",
-	"slow:-1e-400000",
+	"long:-1e-400000",

 	// try to overflow exponent
 	// [Disabled: too slow and memory-hungry with rationals.]
@@ -672,7 +672,7 @@ var float64inputs = []string{

 	// A different kind of very large number.
 	"22.222222222222222",
-	"2." + strings.Repeat("2", 4000) + "e+1",
+	"long:2." + strings.Repeat("2", 4000) + "e+1",

 	// Exactly halfway between 1 and math.Nextafter(1, 2).
 	// Round to even (down).
@@ -682,7 +682,7 @@ var float64inputs = []string{
 	// Slightly higher; round up.
 	"1.00000000000000011102230246251565404236316680908203126",
 	// Slightly higher, but you have to read all the way to the end.
-	"slow:1.00000000000000011102230246251565404236316680908203125" + strings.Repeat("0", 10000) + "1",
+	"long:1.00000000000000011102230246251565404236316680908203125" + strings.Repeat("0", 10000) + "1",

 	// Smallest denormal, 2^(-1022-52)
 	"4.940656458412465441765687928682213723651e-324",
@@ -705,9 +705,11 @@ var float64inputs = []string{

 func TestFloat64SpecialCases(t *testing.T) {
 	for _, input := range float64inputs {
-		slow := strings.HasPrefix(input, "slow:")
-		if slow {
-			input = input[len("slow:"):]
+		if strings.HasPrefix(input, "long:") {
+			if testing.Short() {
+				continue
+			}
+			input = input[len("long:"):]
 		}

 		r, ok := new(Rat).SetString(input)
@@ -736,7 +738,7 @@ func TestFloat64SpecialCases(t *testing.T) {
 			}
 		}

-		if !isFinite(f) || slow {
+		if !isFinite(f) {
 			continue
 		}

@@ -769,8 +771,11 @@ func TestFloat64Distribution(t *testing.T) {
 		9,
 		11,
 	}
-	const winc, einc = 5, 100 // quick test (<1s)
-	//const winc, einc = 1, 1 // soak test (~75s)
+	var winc, einc = uint64(1), int(1) // soak test (~75s on x86-64)
+	if testing.Short() {
+		winc, einc = 10, 500 // quick test (~12ms on x86-64)
+	}
+
 	for _, sign := range "+-" {
 		for _, a := range add {
 			for wid := uint64(0); wid < 60; wid += winc {

コアとなるコードの解説

float64inputsTestFloat64SpecialCases の変更

  • float64inputs 配列の変更:

    • 以前は slow: というプレフィックスが使われていましたが、これを long: に変更しています。これは単なる命名の変更ですが、テストの意図をより明確にするためと考えられます。long は「時間がかかる」ことを直接的に示唆します。
    • 例: "-1e400000"long:-1e400000" に変更されています。これらの数値は非常に大きいため、Rat.SetString で正確な有理数に変換する際に多くの計算リソースを必要とします。
  • TestFloat64SpecialCases 関数内のロジック変更:

    func TestFloat64SpecialCases(t *testing.T) {
        for _, input := range float64inputs {
            // 変更前:
            // slow := strings.HasPrefix(input, "slow:")
            // if slow {
            //     input = input[len("slow:"):]
            // }
    
            // 変更後:
            if strings.HasPrefix(input, "long:") {
                if testing.Short() { // ここが重要
                    continue // --test.short モードならスキップ
                }
                input = input[len("long:"):] // プレフィックスを除去
            }
            // ... 続くテストロジック
        }
    }
    
    • この変更により、float64inputs の各要素をループする際に、まず long: プレフィックスの有無をチェックします。
    • もし long: プレフィックスがあり、かつ testing.Short()true(つまり go test -short が実行されている)であれば、continue ステートメントによって現在のループのイテレーション(つまり、その特定のテストケース)がスキップされます。
    • これにより、時間がかかることが分かっている Rat.SetString のテストケースが、短縮モードでは実行されなくなり、テスト時間が短縮されます。
    • また、if !isFinite(f) || slow { continue } の行から || slow が削除されています。これは、slow のチェックが既に if strings.HasPrefix(input, "long:") ブロック内で処理されているため、冗長になったためです。

TestFloat64Distribution のパラメータ変更

func TestFloat64Distribution(t *testing.T) {
    // ...
    // 変更前:
    // const winc, einc = 5, 100 // quick test (<1s)
    // //const winc, einc = 1, 1 // soak test (~75s)

    // 変更後:
    var winc, einc = uint64(1), int(1) // soak test (~75s on x86-64)
    if testing.Short() {
        winc, einc = 10, 500 // quick test (~12ms on x86-64)
    }
    // ... 続くテストロジック
}
  • この変更は、TestFloat64Distribution のテストの網羅性を testing.Short() の状態に応じて動的に調整します。
  • デフォルト(testing.Short()false)では、winc = 1, einc = 1 が設定されます。これは「soak test」と呼ばれる、より網羅的で時間のかかるテスト設定です(約75秒)。
  • go test -short が実行された場合(testing.Short()true)、winc = 10, einc = 500 が設定されます。これは「quick test」と呼ばれる、より高速なテスト設定です(約12ミリ秒)。
  • winceinc は、テストループのステップサイズや反復回数を制御する変数であり、これらの値を大きくすることでテストの網羅性は低下しますが、実行時間は大幅に短縮されます。

これらの変更により、開発者は必要に応じて迅速なテスト実行と網羅的なテスト実行を切り替えることができ、開発ワークフローの効率が向上します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特に src/pkg/math/big/rat_test.go)
  • Go言語のテストに関する一般的な知識と慣習