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

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

このコミットは、Go言語の標準ライブラリ net/http パッケージ内のテスト TestResponseWriterWriteStringAllocs における、アロケーション(メモリ割り当て)テストの不安定性(flakiness)を解消することを目的としています。具体的には、testing.AllocsPerRun 関数で実行されるテストのイテレーション回数を25回から50回に増やすことで、特定の環境で発生していたテストの失敗を修正しています。

コミット

commit 542415c9df03e40a99ef9eb1005e43ba2cadc46a
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Tue Mar 4 09:59:07 2014 -0800

    net/http: deflake another alloc test
    
    I have one machine where this 25 test run is flaky
    and fails ("21 >= 21"), but 50 works everywhere.
    
    LGTM=josharian
    R=josharian
    CC=golang-codereviews
    https://golang.org/cl/67870053

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

https://github.com/golang/go/commit/542415c9df03e40a99ef9eb1005e43ba2cadc46a

元コミット内容

net/http: deflake another alloc test

I have one machine where this 25 test run is flaky
and fails ("21 >= 21"), but 50 works everywhere.

LGTM=josharian
R=josharian
CC=golang-codereviews
https://golang.org/cl/67870053

変更の背景

この変更の背景には、Go言語の標準ライブラリ net/http パッケージに含まれる TestResponseWriterWriteStringAllocs というテストが、特定の環境で不安定な挙動を示していたという問題があります。コミットメッセージによると、開発者の一人が使用しているマシンで、このテストが25回の実行(testing.AllocsPerRun(25, ...))では「21 >= 21」という条件で失敗することがあったと報告されています。これは、WriteString メソッドと Write メソッドのアロケーション数を比較するテストにおいて、期待されるアロケーション数の差が十分に現れず、テストが誤って失敗するケースがあったことを示唆しています。

テストの不安定性(flakiness)は、CI/CDパイプラインにおいて大きな問題となります。本来コードに問題がないにも関わらずテストが失敗するため、開発者はその原因究明に時間を費やしたり、テスト結果の信頼性が低下したりします。このコミットは、このような不安定なテストを修正し、テストスイート全体の信頼性を向上させることを目的としています。イテレーション回数を増やすことで、より統計的に安定したアロケーション数を測定できるようになり、テストの再現性が向上すると考えられます。

前提知識の解説

Go言語の net/http パッケージ

net/http パッケージは、Go言語でHTTPクライアントおよびサーバーを実装するための標準ライブラリです。Webアプリケーションの構築において中心的な役割を果たし、HTTPリクエストの処理、レスポンスの生成、ルーティング、ミドルウェアの適用など、多岐にわたる機能を提供します。

testing パッケージと testing.AllocsPerRun

Go言語の testing パッケージは、ユニットテスト、ベンチマークテスト、例(Example)テストなど、様々な種類のテストを記述するためのフレームワークを提供します。

testing.AllocsPerRun 関数は、特定の関数やコードブロックが実行される際に発生するメモリ割り当て(アロケーション)の平均回数を測定するために使用されます。この関数は、指定された回数だけ関数を実行し、その間に発生したアロケーションの総数を計測し、1回あたりの平均アロケーション数を返します。

  • 目的: コードのパフォーマンス最適化、特にメモリ使用量の削減において、特定操作がどれだけメモリを割り当てているかを定量的に把握するために重要です。
  • 使い方: testing.AllocsPerRun(runs int, f func()) float64 の形式で呼び出されます。runs は関数 f を実行する回数、f は測定対象の関数です。戻り値は1回あたりの平均アロケーション数です。
  • 不安定性(Flakiness): AllocsPerRun は、Goランタイムの内部的な動作(ガベージコレクションのタイミング、スケジューリング、JITコンパイルなど)や、OSの挙動、ハードウェアの特性など、様々な要因によって測定結果が変動する可能性があります。特に実行回数が少ない場合、これらの外部要因の影響を受けやすく、テスト結果が不安定になることがあります。

テストの不安定性(Flakiness)

テストの不安定性(Flakiness)とは、同じコードベースに対して同じテストを複数回実行した際に、成功したり失敗したりする現象を指します。これは、テストが非決定的な要素(例: 時間、並行処理のタイミング、外部リソースの可用性、環境設定、ランダム性など)に依存している場合に発生しやすいです。

不安定なテストは、以下のような問題を引き起こします。

  • 信頼性の低下: テスト結果が信用できなくなり、開発者がテストの失敗を無視するようになる可能性があります。
  • 開発効率の低下: 開発者がテストの失敗がコードのバグによるものなのか、それともテストの不安定性によるものなのかを判断するために時間を費やすことになります。
  • CI/CDパイプラインの阻害: 不安定なテストがCI/CDパイプラインで頻繁に失敗すると、デプロイが遅延したり、手動での介入が必要になったりします。

このコミットのように、testing.AllocsPerRun の実行回数を増やすことは、統計的な平均値をより正確に測定することで、ランタイムの微細な変動による影響を軽減し、テストの不安定性を解消する一般的な手法の一つです。

技術的詳細

TestResponseWriterWriteStringAllocs テストは、net/http パッケージの ResponseWriter インターフェースにおける WriteString メソッドと Write メソッドのメモリ割り当て効率を比較することを目的としています。理想的には、WriteString は文字列を直接書き込むため、バイトスライスに変換して書き込む Write よりもアロケーションが少ない、または同等であるべきです。

テストのロジックは以下のようになっています。

  1. http.HandlerFunc を定義し、ResponseWriter.WriteResponseWriter.WriteString をそれぞれ使用する2つの異なるパス (//s) を設定します。
  2. testing.AllocsPerRun を使用して、GET / HTTP/1.0 リクエスト(Write を使用)と GET /s HTTP/1.0 リクエスト(WriteString を使用)それぞれのアロケーション数を測定します。
  3. WriteString を使用した after のアロケーション数が、Write を使用した before のアロケーション数以上である場合、テストは失敗します。これは WriteStringWrite よりも効率的であるという期待に反するためです。

このコミットでは、testing.AllocsPerRun の第一引数である runs の値を25から50に増やしています。

-	before := testing.AllocsPerRun(25, func() { ht.rawResponse("GET / HTTP/1.0") })
-	after := testing.AllocsPerRun(25, func() { ht.rawResponse("GET /s HTTP/1.0") })
+	before := testing.AllocsPerRun(50, func() { ht.rawResponse("GET / HTTP/1.0") })
+	after := testing.AllocsPerRun(50, func() { ht.rawResponse("GET /s HTTP/1.0") })

この変更の技術的な意図は、testing.AllocsPerRun が返す平均アロケーション数の測定精度を向上させることにあります。

  • 統計的安定性: AllocsPerRun は、指定された回数だけ操作を繰り返し、その平均アロケーション数を計算します。実行回数が少ない場合、個々の実行におけるランタイムの微細な変動(例: ガベージコレクションのタイミング、CPUキャッシュの状態、OSのスケジューリングなど)が結果に大きく影響し、測定値が不安定になることがあります。実行回数を増やすことで、これらの短期的な変動が平均化され、より統計的に安定した、再現性の高いアロケーション数を測定できるようになります。
  • ノイズの低減: メモリ割り当ての測定は、非常に低レベルな操作であり、システム上の他のプロセスやランタイムの内部的な動作によって「ノイズ」が混入しやすい性質があります。実行回数を増やすことは、このノイズの影響を相対的に低減し、真のアロケーション特性をより正確に捉えるのに役立ちます。
  • 特定の環境での再現性: コミットメッセージにあるように、特定の環境で「21 >= 21」という失敗が発生していたのは、25回の実行では beforeafter のアロケーション数の差が、その環境のランタイムの変動範囲内に収まってしまい、期待される after < before という条件が満たされなかったためと考えられます。50回に増やすことで、この変動範囲を乗り越え、より明確な差が測定できるようになり、テストが安定して成功するようになったと推測されます。

この修正は、コードのロジック自体を変更するものではなく、テストの測定方法を改善することで、テストの信頼性と再現性を高める「テストの堅牢化」の一例です。

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

src/pkg/net/http/serve_test.go ファイルの以下の行が変更されています。

--- a/src/pkg/net/http/serve_test.go
+++ b/src/pkg/net/http/serve_test.go
@@ -2235,8 +2235,8 @@ func TestResponseWriterWriteStringAllocs(t *testing.T) {
 		w.Write([]byte("Hello world"))
 	}))
-	before := testing.AllocsPerRun(25, func() { ht.rawResponse("GET / HTTP/1.0") })
-	after := testing.AllocsPerRun(25, func() { ht.rawResponse("GET /s HTTP/1.0") })
+	before := testing.AllocsPerRun(50, func() { ht.rawResponse("GET / HTTP/1.0") })
+	after := testing.AllocsPerRun(50, func() { ht.rawResponse("GET /s HTTP/1.0") })
 	if int(after) >= int(before) {
 		t.Errorf("WriteString allocs of %v >= Write allocs of %v", after, before)
 	}

コアとなるコードの解説

変更は TestResponseWriterWriteStringAllocs 関数内で行われています。このテストは、http.ResponseWriterWriteString メソッドと Write メソッドのメモリ割り当て効率を比較するものです。

  • before 変数には、ht.rawResponse("GET / HTTP/1.0") を25回(変更前)または50回(変更後)実行した際の平均アロケーション数が格納されます。このリクエストは、テスト内で w.Write([]byte("Hello world")) を呼び出すハンドラに対応しています。
  • after 変数には、ht.rawResponse("GET /s HTTP/1.0") を25回(変更前)または50回(変更後)実行した際の平均アロケーション数が格納されます。このリクエストは、テスト内で w.WriteString("Hello world") を呼び出すハンドラに対応しています。

変更点は、testing.AllocsPerRun 関数の第一引数である実行回数を 25 から 50 に増やしたことです。

  • before := testing.AllocsPerRun(25, ...) から before := testing.AllocsPerRun(50, ...)
  • after := testing.AllocsPerRun(25, ...) から after := testing.AllocsPerRun(50, ...)

この変更により、beforeafter のアロケーション数の測定がより多くのサンプルに基づいて行われるようになり、測定結果の統計的な信頼性が向上します。結果として、特定の環境で発生していたテストの不安定性が解消され、テストが安定して成功するようになりました。これは、テストの堅牢性を高めるための一般的なプラクティスであり、特にパフォーマンスやアロケーションに関するテストで有効です。

関連リンク

参考にした情報源リンク

  • Go言語の testing パッケージドキュメント: https://pkg.go.dev/testing
  • Go言語の net/http パッケージドキュメント: https://pkg.go.dev/net/http
  • Go言語の testing.AllocsPerRun の使用例や解説(一般的な情報源)
  • テストの不安定性(Flakiness)に関する一般的な情報源