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

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

このコミットは、Go言語の標準ライブラリである net/http パッケージのベンチマークテストファイル src/pkg/net/http/request_test.go に変更を加えています。具体的には、HTTPリクエストの読み込み性能を測定するためのベンチマークテストが追加・修正されています。

コミット

net/http: more request benchmarks

Add benchmarks for common http benchmarking tools. The intent is to catch optimisations which favor synthetic benchmarks that do not show improvements for real clients like Chrome.

BenchmarkReadRequestChrome        200000             10133 ns/op          60.29 MB/s        3148 B/op         32 allocs/op
BenchmarkReadRequestCurl          500000              4314 ns/op          18.08 MB/s         905 B/op         15 allocs/op
BenchmarkReadRequestApachebench   500000              4363 ns/op          18.79 MB/s         956 B/op         16 allocs/op
BenchmarkReadRequestSiege         500000              6408 ns/op          24.19 MB/s        1397 B/op         22 allocs/op
BenchmarkReadRequestWrk          1000000              2838 ns/op          14.09 MB/s         757 B/op         11 allocs/op

R=golang-dev, bradfitz
CC=golang-dev, haimuiba
https://golang.org/cl/7300075

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

https://github.com/golang/go/commit/6ce3e99af0af2c3d643bb61cb26e639282a142bc

元コミット内容

net/http: more request benchmarks

このコミットは、一般的なHTTPベンチマークツール(Chrome、Curl、ApacheBench、Siege、Wrk)を模倣したリクエストベンチマークを追加します。その目的は、合成ベンチマークでは改善が見られるものの、Chromeのような実際のクライアントでは改善が見られない最適化を特定することです。

コミットメッセージには、追加された各ベンチマークの実行結果の概要も含まれています。

変更の背景

この変更の背景には、Goの net/http パッケージの性能評価において、より現実的なシナリオを反映させるという意図があります。従来のベンチマークは、特定の最適化を有利にするような「合成的」なリクエストパターンに偏りがちでした。しかし、実際のWebブラウザ(Chromeなど)や一般的なHTTPクライアントツール(Curl、ApacheBench、Siege、Wrk)が生成するリクエストは、ヘッダーの構成や改行コードの扱いなど、合成的なベンチマークとは異なる特性を持つことがあります。

このコミットの目的は、そうした「実際のクライアント」からのリクエスト処理性能を正確に測定し、合成ベンチマークでは見過ごされがちな性能ボトルネックや非効率性を特定することにあります。これにより、GoのHTTPスタックがより広範な実世界のユースケースで高いパフォーマンスを発揮できるよう、改善の方向性を見出すことが可能になります。

前提知識の解説

1. HTTPプロトコルとリクエストの構造

HTTP (Hypertext Transfer Protocol) は、Web上でデータを交換するためのプロトコルです。クライアント(ブラウザなど)がサーバーに情報を要求する際に「HTTPリクエスト」を送信します。HTTPリクエストは、通常、以下の要素で構成されます。

  • リクエストライン: メソッド (GET, POSTなど)、リクエストURI、HTTPバージョン (HTTP/1.1など)。
  • ヘッダーフィールド: クライアントやリクエストに関する追加情報(User-Agent, Accept, Host, Connectionなど)。各ヘッダーは Key: Value の形式で、行末は CRLF (Carriage Return + Line Feed, \r\n) で区切られます。
  • 空行: ヘッダーフィールドの終わりを示す CRLF のみからなる行。
  • メッセージボディ: POSTリクエストなどで送信されるデータ(フォームデータ、JSONなど)。

特に重要なのは、HTTPプロトコルでは行の終端に CRLF (\r\n) を使用するという点です。多くのプログラミング言語やOSでは、単なる改行 (\n) が使われることがありますが、HTTPリクエストを正確にパースするためには \r\n の扱いが重要になります。

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

net/http は、Go言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。Webアプリケーションの構築やHTTP通信を行う上で中心的な役割を果たすパッケージです。このパッケージは、HTTPリクエストのパース、レスポンスの生成、ルーティング、ミドルウェアのサポートなど、HTTP通信に必要な多くの機能を提供します。

3. Go言語のベンチマークテスト (testing パッケージ)

Go言語には、標準でベンチマークテストをサポートする testing パッケージが組み込まれています。

  • go test -bench=.: このコマンドを実行すると、_test.go ファイル内に定義されたベンチマーク関数が実行されます。
  • ベンチマーク関数: BenchmarkXxx(*testing.B) というシグネチャを持つ関数です。
  • *testing.B: ベンチマーク実行のコンテキストを提供します。
    • b.N: ベンチマーク関数が実行されるイテレーション回数。Goのテストフレームワークが自動的に調整し、統計的に有意な結果が得られるようにします。
    • b.SetBytes(int64(len(request))): 処理されたバイト数を設定します。これにより、ベンチマーク結果に「MB/s」のようなスループットの指標が表示されるようになります。
    • b.ReportAllocs(): メモリ割り当て(アロケーション)の回数とバイト数を報告するように設定します。これにより、ベンチマーク実行中にどれだけメモリが確保されたか、その効率性を評価できます。
    • b.ResetTimer(): ベンチマークのタイマーをリセットします。セットアップコードの実行時間を測定から除外するために使用されます。

4. HTTPベンチマークツール

コミットメッセージで言及されているツールは、それぞれ異なる特性を持つHTTPベンチマークツールです。

  • Chrome: 一般的なWebブラウザ。実際のユーザーがWebサイトを閲覧する際のリクエストパターンを代表します。ヘッダーが豊富で、Connection: keep-alive など、持続的な接続を前提としたリクエストを生成します。
  • Curl: コマンドラインのデータ転送ツール。非常に柔軟で、HTTPリクエストを手動で構築・送信する際によく使われます。デフォルトのリクエストは比較的シンプルです。
  • ApacheBench (ab): Apache HTTP Serverに付属するベンチマークツール。単一のURLに対して並列リクエストを送信し、サーバーの負荷耐性を測定します。HTTP/1.0リクエストを生成することがあります。
  • Siege: HTTP負荷テストおよびベンチマークユーティリティ。Webサーバーの性能を測定し、特定のURLへの同時アクセスをシミュレートできます。User-Agent ヘッダーに独自の文字列を含みます。
  • Wrk: 高速なHTTPベンチマークツール。Luaスクリプトでリクエスト生成をカスタマイズできるのが特徴です。比較的シンプルなリクエストを生成します。

これらのツールは、それぞれ異なるHTTPヘッダー、リクエストボディ、接続管理のパターンを持つため、Goの net/http パッケージがこれらの多様なリクエストに対してどのように応答するかを評価することは、現実的な性能改善に繋がります。

技術的詳細

このコミットの技術的詳細な変更点は、主に src/pkg/net/http/request_test.go ファイル内のベンチマーク関数の修正と追加にあります。

  1. benchmarkReadRequest 関数の修正:

    • 既存の benchmarkReadRequest 関数に、ベンチマーク対象のHTTPリクエスト文字列を正規化する処理が追加されました。
    • 具体的には、request = request + "\\n" でリクエスト文字列の末尾に改行を追加し、request = strings.Replace(request, "\\n", "\\r\\n", -1) で、文字列内のすべての \n (LF) を \r\n (CRLF) に置換しています。
    • これは、Goの文字列リテラルで \n を使用して改行を表現することが一般的である一方で、HTTPプロトコルでは行末に CRLF (\r\n) を厳密に要求するため、ベンチマークの入力として与えられるリクエスト文字列をHTTPプロトコルに準拠させるための重要な変更です。これにより、Goの net/http パッケージが実際のネットワークトラフィックで遭遇するであろう形式のリクエストを正確にパースする性能を測定できます。
  2. min 関数の削除:

    • 以前のコードにあった min(a, b int) int ヘルパー関数が削除されました。この関数は、BenchmarkReadRequest 関数内で strings.Replace の結果を処理する際に使用されていた可能性がありますが、新しいベンチマーク構造では不要になったため削除されました。これはコードの簡素化と、不要なユーティリティ関数の排除を意味します。
  3. 新しいベンチマーク関数の追加:

    • BenchmarkReadRequestChrome
    • BenchmarkReadRequestCurl
    • BenchmarkReadRequestApachebench
    • BenchmarkReadRequestSiege
    • BenchmarkReadRequestWrk これらの関数は、それぞれ対応するHTTPクライアントツールが生成する典型的なHTTPリクエストの文字列を定義し、それらを benchmarkReadRequest 関数に渡してベンチマークを実行します。
    • 各ベンチマーク関数は、コメントでどのツールを模倣しているかを示しており、そのツールの特徴的なヘッダー(例: User-Agent, Accept-Encoding, Connection など)を含むリクエスト文字列を使用しています。
    • これにより、Goの net/http パッケージが、異なるクライアントからの多様なリクエストパターン(ヘッダーの数、種類、値など)に対して、どの程度の性能でリクエストを読み込み、パースできるかを詳細に評価できるようになります。

これらの変更は、GoのHTTPスタックのベンチマークをより現実的かつ包括的なものにし、実際のアプリケーション環境での性能特性をより正確に把握するためのものです。

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

--- a/src/pkg/net/http/request_test.go
+++ b/src/pkg/net/http/request_test.go
@@ -403,6 +403,8 @@ Content-Disposition: form-data; name="textb"\n `\n \n func benchmarkReadRequest(b *testing.B, request string) {\n+\trequest = request + "\\n"                             // final \\n\n+\trequest = strings.Replace(request, "\\n", "\\r\\n", -1) // expand \\n to \\r\\n\n \tb.SetBytes(int64(len(request)))\n \tr := bufio.NewReader(&infiniteReader{buf: []byte(request)})\n \tb.ReportAllocs()\n@@ -428,16 +430,9 @@ func (r *infiniteReader) Read(b []byte) (int, error) {\n \treturn n, nil\n }\n \n-func min(a, b int) int {\n-\tif a > b {\n-\t\treturn b\n-\t}\n-\treturn a\n-}\n-\n-func BenchmarkReadRequest(b *testing.B) {\n+func BenchmarkReadRequestChrome(b *testing.B) {\n \t// https://github.com/felixge/node-http-perf/blob/master/fixtures/get.http\n-\tconst request = `GET / HTTP/1.1\n+\tbenchmarkReadRequest(b, `GET / HTTP/1.1\n Host: localhost:8080\n Connection: keep-alive\n Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n@@ -446,7 +441,41 @@ Accept-Encoding: gzip,deflate,sdch\n Accept-Language: en-US,en;q=0.8\n Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3\n Cookie: __utma=1.1978842379.1323102373.1323102373.1323102373.1; EPi:NumberOfVisits=1,2012-02-28T13:42:18; CrmSession=5b707226b9563e1bc69084d07a107c98; plushContainerWidth=100%25; plushNoTopMenu=0; hudson_auto_refresh=false\n+`)\n+}\n \n-`\n-\tbenchmarkReadRequest(b, strings.Replace(request, \"\\n\", \"\\r\\n\", -1))\n+func BenchmarkReadRequestCurl(b *testing.B) {\n+\t// curl http://localhost:8080/\n+\tbenchmarkReadRequest(b, `GET / HTTP/1.1\n+User-Agent: curl/7.27.0\n+Host: localhost:8080\n+Accept: */*\n+`)\n+}\n+\n+func BenchmarkReadRequestApachebench(b *testing.B) {\n+\t// ab -n 1 -c 1 http://localhost:8080/\n+\tbenchmarkReadRequest(b, `GET / HTTP/1.0\n+Host: localhost:8080\n+User-Agent: ApacheBench/2.3\n+Accept: */*\n+`)\n+}\n+\n+func BenchmarkReadRequestSiege(b *testing.B) {\n+\t// siege -r 1 -c 1 http://localhost:8080/\n+\tbenchmarkReadRequest(b, `GET / HTTP/1.1\n+Host: localhost:8080\n+Accept: */*\n+Accept-Encoding: gzip\n+User-Agent: JoeDog/1.00 [en] (X11; I; Siege 2.70)\n+Connection: keep-alive\n+`)\n+}\n+\n+func BenchmarkReadRequestWrk(b *testing.B) {\n+\t// wrk -t 1 -r 1 -c 1 http://localhost:8080/\n+\tbenchmarkReadRequest(b, `GET / HTTP/1.1\n+Host: localhost:8080\n+`)\n }\n```

## コアとなるコードの解説

このコミットの核となる変更は、`benchmarkReadRequest` ヘルパー関数の強化と、それを利用した複数の新しいベンチマーク関数の導入です。

1.  **`benchmarkReadRequest` の改行コード正規化**:
    ```go
    func benchmarkReadRequest(b *testing.B, request string) {
    	request = request + "\\n"                             // final \\n
    	request = strings.Replace(request, "\\n", "\\r\\n", -1) // expand \\n to \\r\\n
    ```
    この部分が最も重要な変更点です。Goの文字列リテラルでは `\n` が改行として扱われますが、HTTPプロトコルでは行末に `CRLF` (`\r\n`) が必要です。このコードは、ベンチマークに渡されるHTTPリクエスト文字列が、Goの文字列リテラルで記述されていても、実際のHTTPプロトコルに準拠した `CRLF` 形式に変換されるようにします。これにより、`net/http` パッケージがネットワークから受信するであろう正確な形式のリクエストをパースする性能を測定できます。末尾に `\n` を追加しているのは、HTTPリクエストの最後の空行を保証するためです。

2.  **`min` 関数の削除**:
    ```diff
    -func min(a, b int) int {
    -...
    -}
    ```
    以前の `BenchmarkReadRequest` 関数で使用されていた `min` 関数が削除されました。これは、コードの簡素化と、新しいベンチマーク構造においてこの関数が不要になったためです。

3.  **新しいベンチマーク関数の追加**:
    ```go
    func BenchmarkReadRequestChrome(b *testing.B) {
    	// ... Chromeのリクエスト文字列 ...
    	benchmarkReadRequest(b, `GET / HTTP/1.1
    Host: localhost:8080
    Connection: keep-alive
    // ... その他のヘッダー ...
    `)
    }

    func BenchmarkReadRequestCurl(b *testing.B) {
    	// ... Curlのリクエスト文字列 ...
    	benchmarkReadRequest(b, `GET / HTTP/1.1
    User-Agent: curl/7.27.0
    Host: localhost:8080
    Accept: */*
    `)
    }
    // ... Apachebench, Siege, Wrk のベンチマーク関数も同様に続く ...
    ```
    これらの新しいベンチマーク関数は、それぞれ特定のHTTPクライアントツール(Chrome、Curl、ApacheBench、Siege、Wrk)が生成する典型的なHTTPリクエストの生文字列を定義しています。これらの文字列は、各ツールの `User-Agent` や `Accept` ヘッダーなど、特徴的なヘッダーを含んでいます。これらのリクエスト文字列は、前述の `benchmarkReadRequest` 関数に渡され、Goの `net/http` パッケージがこれらの多様なリクエストをどれだけ効率的に読み込み、パースできるかを測定します。

この変更により、Goの `net/http` パッケージの性能評価が、より現実世界のシナリオに即したものとなり、実際のクライアントからのリクエスト処理における潜在的なボトルネックを特定し、最適化を進めるための貴重なデータを提供できるようになります。

## 関連リンク

*   Go CL 7300075: [https://golang.org/cl/7300075](https://golang.org/cl/7300075)
*   felixge/node-http-perf: [https://github.com/felixge/node-http-perf/blob/master/fixtures/get.http](https://github.com/felixge/node-http-perf/blob/master/fixtures/get.http) (Chromeベンチマークのリクエスト元)

## 参考にした情報源リンク

*   Go 言語のベンチマークテスト: [https://pkg.go.dev/testing](https://pkg.go.dev/testing)
*   HTTP/1.1 Message Format: [https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html](https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html)
*   Curl 公式サイト: [https://curl.se/](https://curl.se/)
*   ApacheBench (ab) ドキュメント: [https://httpd.apache.org/docs/2.4/programs/ab.html](https://httpd.apache.org/docs/2.4/programs/ab.html)
*   Siege 公式サイト: [https://www.joedog.org/siege-home/](https://www.joedog.org/siege-home/)
*   Wrk GitHubリポジトリ: [https://github.com/wg/wrk](https://github.com/wg/wrk)
*   Goの `strings.Replace` 関数: [https://pkg.go.dev/strings#Replace](https://pkg.go.dev/strings#Replace)
*   Goの `bufio.NewReader`: [https://pkg.go.dev/bufio#NewReader](https://pkg.go.dev/bufio#NewReader)
*   Goの `io.Reader` インターフェース: [https://pkg.go.dev/io#Reader](https://pkg.go.dev/io#Reader)
*   Goの `net/http` パッケージ: [https://pkg.go.dev/net/http](https://pkg.go.dev/net/http)
*   Goの `testing.B` 型: [https://pkg.go.dev/testing#B](https://pkg.go.dev/testing#B)
*   Goの `b.SetBytes` メソッド: [https://pkg.go.dev/testing#B.SetBytes](https://pkg.go.dev/testing#B.SetBytes)
*   Goの `b.ReportAllocs` メソッド: [https://pkg.go.dev/testing#B.ReportAllocs](https://pkg.go.dev/testing#B.ReportAllocs)
*   Goの `b.N` フィールド: [https://pkg.go.dev/testing#B.N](https://pkg.go.dev/testing#B.N)