[インデックス 15177] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/http パッケージに、HTTPリクエストのパース性能を測定するための新しいベンチマーク BenchmarkReadRequest を追加するものです。このベンチマークは、felixge/node-http-perf プロジェクトから取得した実際のHTTPリクエストデータをフィクスチャとして使用しています。
コミット
commit cccc96b8e9f2e526d7b22cfc4af17569c47e8c31
Author: Dave Cheney <dave@cheney.net>
Date: Sat Feb 9 07:04:07 2013 +1100
net/http: add BenchmarkReadRequest
Add benchmark for request parsing. Fixture data is taken from https://github.com/felixge/node-http-perf
% go version
go version devel +28966b7b2f0c Thu Feb 07 20:26:12 2013 -0800 linux/amd64
% go test -run=nil -bench=ReadRequest -benchtime=10s
PASS
BenchmarkReadRequest 2000000 9900 ns/op 61.71 MB/s 3148 B/op 32 allocs/op
ok net/http 12.180s
R=golang-dev, bradfitz, minux.ma, haimuiba
CC=golang-dev
https://golang.org/cl/7313048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cccc96b8e9f2e526d7b22cfc4af17569c47e8c31
元コミット内容
net/http: BenchmarkReadRequest を追加
リクエストパースのベンチマークを追加。フィクスチャデータは https://github.com/felixge/node-http-perf から取得。
% go version
go version devel +28966b7b2f0c Thu Feb 07 20:26:12 2013 -0800 linux/amd64
% go test -run=nil -bench=ReadRequest -benchtime=10s
PASS
BenchmarkReadRequest 2000000 9900 ns/op 61.71 MB/s 3148 B/op 32 allocs/op
ok net/http 12.180s
R=golang-dev, bradfitz, minux.ma, haimuiba CC=golang-dev https://golang.org/cl/7313048
変更の背景
このコミットの主な背景は、Go言語の net/http パッケージにおけるHTTPリクエストのパース処理の性能を測定し、将来的な最適化の基盤を築くことにあります。HTTPサーバーの性能は、リクエストの受信と解析の効率に大きく依存します。そのため、この重要な処理のボトルネックを特定し、改善するためには、信頼性の高いベンチマークが必要とされます。
コミットメッセージに記載されているように、ベンチマークのフィクスチャデータとして https://github.com/felixge/node-http-perf が使用されています。node-http-perf は、HTTP/Sサーバーのパフォーマンスをテストするために設計されたツールであり、実際のHTTPリクエストパターンを生成することができます。これにより、Goの net/http パッケージが現実世界のシナリオでどのように動作するかをより正確に評価することが可能になります。
具体的なベンチマーク結果(9900 ns/op、61.71 MB/s、3148 B/op、32 allocs/op)は、リクエスト1つあたりの処理時間、スループット、メモリ割り当て、および割り当て回数を示しており、これらの数値は将来の性能改善の目標設定や、回帰テストの基準として機能します。
前提知識の解説
Go言語のベンチマーク
Go言語には、標準ライブラリの testing パッケージにベンチマーク機能が組み込まれています。ベンチマーク関数は BenchmarkXxx(*testing.B) というシグネチャを持ち、go test -bench=. コマンドで実行されます。
*testing.B: ベンチマーク実行のためのコンテキストを提供します。b.N: ベンチマーク関数が実行されるイテレーション回数。Goのテストフレームワークが自動的に調整し、安定した測定結果が得られるようにします。b.SetBytes(n int64): 1回の操作で処理されるバイト数を設定します。これにより、結果に「X MB/s」のようなスループットが表示されます。b.ReportAllocs(): ベンチマーク中に発生したメモリ割り当ての回数とバイト数を報告するように設定します。b.ResetTimer(): ベンチマークのタイマーをリセットします。セットアップコードの実行時間を測定から除外するために使用されます。
net/http パッケージ
Goの net/http パッケージは、HTTPクライアントとサーバーの実装を提供します。このコミットでは、特にHTTPリクエストのパース(解析)に関連する部分が対象となります。クライアントから送られてきた生のリクエストデータ(バイト列)を、Goの http.Request 構造体に変換する処理がパースです。
bufio.NewReader と io.Reader インターフェース
io.Readerインターフェース: Goにおけるデータの読み込み操作を抽象化する基本的なインターフェースです。Read([]byte) (int, error)メソッドを持ち、バイトスライスにデータを読み込み、読み込んだバイト数とエラーを返します。bufio.NewReader:io.Readerをラップし、バッファリングされた読み込みを提供します。これにより、基となるio.Readerへの呼び出し回数を減らし、I/O性能を向上させることができます。HTTPリクエストのパースでは、効率的な読み込みが不可欠です。
infiniteReader の目的
ベンチマークでは、テスト対象の関数が繰り返し実行されるため、入力データも繰り返し提供される必要があります。infiniteReader は、特定のバイト列(この場合はHTTPリクエストの生データ)を無限にループして提供するカスタムの io.Reader 実装です。これにより、ベンチマーク中にデータが枯渇することなく、ReadRequest 関数が継続的にテストされることを保証します。
技術的詳細
このコミットでは、src/pkg/net/http/request_test.go ファイルに以下の要素が追加されています。
-
benchmarkReadRequest(b *testing.B, request string)関数:- これは、実際のベンチマークロジックをカプセル化するヘルパー関数です。
b.SetBytes(int64(len(request))): 1回の操作で処理されるバイト数として、HTTPリクエスト文字列の長さを設定します。これにより、ベンチマーク結果にスループット(MB/s)が表示されるようになります。r := bufio.NewReader(&infiniteReader{buf: []byte(request)}):infiniteReaderのインスタンスを作成し、それをbufio.NewReaderでラップして、バッファリングされたリーダーを作成します。これにより、ReadRequestが効率的にデータを読み込めるようになります。b.ReportAllocs(): メモリ割り当ての統計を報告するように設定します。b.ResetTimer(): セットアップコード(infiniteReaderの初期化など)の時間をベンチマークの測定から除外するためにタイマーをリセットします。for i := 0; i < b.N; i++: ベンチマークのメインループです。b.N回だけReadRequestを呼び出します。_, err := ReadRequest(r):net/httpパッケージのReadRequest関数を呼び出し、HTTPリクエストをパースします。if err != nil: エラーが発生した場合はベンチマークを失敗させます。
-
infiniteReader構造体とReadメソッド:infiniteReaderは、io.Readerインターフェースを満たすカスタム型です。buf []byte: 無限にループさせる元のバイト列(HTTPリクエストデータ)を保持します。offset int:buf内の現在の読み込み位置を追跡します。Read(b []byte) (int, error)メソッド:n := copy(b, r.buf[r.offset:]):r.bufの現在のオフセットから、引数bにデータをコピーします。r.offset = (r.offset + n) % len(r.buf): オフセットを更新し、bufの長さを超えた場合は先頭に戻ることで、無限ループを実現します。return n, nil: 読み込んだバイト数とnilエラーを返します。
-
min(a, b int) int関数:- これは単純なヘルパー関数で、2つの整数のうち小さい方を返します。
infiniteReaderのReadメソッド内でcopyの長さを計算する際に使用される可能性がありますが、このコミットのinfiniteReader.Readの実装では直接使用されていません。これはおそらく、将来的な拡張や、以前のイテレーションで検討されたが最終的に採用されなかったコードの名残かもしれません。
- これは単純なヘルパー関数で、2つの整数のうち小さい方を返します。
-
BenchmarkReadRequest(b *testing.B)関数:- これは
go testコマンドによって自動的に発見され、実行される実際のベンチマークエントリポイントです。 const request = ...:https://github.com/felixge/node-http-perf/blob/master/fixtures/get.httpから取得した実際のHTTP GETリクエストの生データが文字列リテラルとして定義されています。benchmarkReadRequest(b, strings.Replace(request, "\\n", "\\r\\n", -1)): 定義されたHTTPリクエスト文字列の改行コードをLF (\n) からCRLF (\r\n) に変換し、それをヘルパー関数benchmarkReadRequestに渡してベンチマークを実行します。HTTP/1.1ではヘッダーの区切りにCRLFが使用されるため、これは正しいHTTPリクエスト形式をシミュレートするために重要です。
- これは
コアとなるコードの変更箇所
diff --git a/src/pkg/net/http/request_test.go b/src/pkg/net/http/request_test.go
index bd757920b7..189184795e 100644
--- a/src/pkg/net/http/request_test.go
+++ b/src/pkg/net/http/request_test.go
@@ -401,3 +401,52 @@ Content-Disposition: form-data; name="textb"\n ` + textbValue + `\n --MyBoundary--\n `
+\n+func benchmarkReadRequest(b *testing.B, request string) {\n+\tb.SetBytes(int64(len(request)))\n+\tr := bufio.NewReader(&infiniteReader{buf: []byte(request)})\n+\tb.ReportAllocs()\n+\tb.ResetTimer()\n+\tfor i := 0; i < b.N; i++ {\n+\t\t_, err := ReadRequest(r)\n+\t\tif err != nil {\n+\t\t\tb.Fatalf(\"failed to read request: %v\", err)\n+\t\t}\n+\t}\n+}\n+\n+// infiniteReader satisfies Read requests as if the contents of buf\n+// loop indefinitely.\n+type infiniteReader struct {\n+\tbuf []byte\n+\toffset int\n+}\n+\n+func (r *infiniteReader) Read(b []byte) (int, error) {\n+\tn := copy(b, r.buf[r.offset:])\n+\tr.offset = (r.offset + n) % len(r.buf)\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+\t// https://github.com/felixge/node-http-perf/blob/master/fixtures/get.http\n+\tconst request = `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+User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17\n+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+\tbenchmarkReadRequest(b, strings.Replace(request, \"\\n\", \"\\r\\n\", -1))\n+}\n```
## コアとなるコードの解説
追加されたコードは、`net/http` パッケージの `request_test.go` ファイルに、HTTPリクエストのパース性能を測定するためのベンチマーク機能を提供します。
1. **`benchmarkReadRequest` 関数**:
この関数は、実際のベンチマーク実行ロジックを抽象化しています。
* `b.SetBytes(int64(len(request)))`: ベンチマークの出力にスループット(MB/s)を表示させるために、処理するバイト数を設定します。
* `r := bufio.NewReader(&infiniteReader{buf: []byte(request)})`: `infiniteReader` を作成し、それを `bufio.NewReader` でラップしています。`infiniteReader` は、与えられたHTTPリクエストのバイト列を無限に提供するカスタムリーダーです。これにより、ベンチマーク中に `ReadRequest` が常にデータを読み込める状態を保ちます。`bufio.NewReader` は、バッファリングによって読み込み効率を向上させます。
* `b.ReportAllocs()`: メモリ割り当ての回数とバイト数をベンチマーク結果に含めるように指示します。これは、ガベージコレクションのオーバーヘッドを評価する上で重要です。
* `b.ResetTimer()`: ベンチマークのセットアップにかかる時間を測定から除外するために、タイマーをリセットします。
* `for i := 0; i < b.N; i++ { ... }`: `b.N` はGoのテストフレームワークが自動的に調整するイテレーション回数です。このループ内で、`http.ReadRequest(r)` を呼び出し、HTTPリクエストのパース処理を繰り返し実行します。
2. **`infiniteReader` 構造体と `Read` メソッド**:
* `infiniteReader` は、`io.Reader` インターフェースを実装するカスタム型です。
* `buf []byte`: 無限に読み出される元のデータ(HTTPリクエストのバイト列)を保持します。
* `offset int`: `buf` 内の現在の読み込み位置を示します。
* `Read(b []byte) (int, error)`: このメソッドが `io.Reader` インターフェースの要件を満たします。
* `n := copy(b, r.buf[r.offset:])`: 要求されたバイト数 `len(b)` または `r.buf` の残りのバイト数のうち小さい方を `b` にコピーします。
* `r.offset = (r.offset + n) % len(r.buf)`: オフセットを更新し、`buf` の末尾に達した場合は先頭に戻ることで、データが無限にループして提供されるようにします。これにより、ベンチマークが長期間実行されても入力データが枯渇することはありません。
3. **`min` 関数**:
* これは2つの整数のうち小さい方を返すシンプルなヘルパー関数です。このコミットの `infiniteReader.Read` の実装では直接使用されていませんが、一般的なユーティリティ関数として追加されたか、以前の検討段階で必要とされた可能性があります。
4. **`BenchmarkReadRequest` 関数**:
* これは `go test -bench` コマンドで実行されるエントリポイントとなるベンチマーク関数です。
* `const request = ...`: `felixge/node-http-perf` プロジェクトのフィクスチャから取得した、実際のHTTP GETリクエストの生データが文字列として定義されています。このデータは、現実的なリクエストパースのシナリオをシミュレートするために使用されます。
* `benchmarkReadRequest(b, strings.Replace(request, "\\n", "\\r\\n", -1))`: 定義されたリクエスト文字列の改行コードを、HTTPプロトコルで一般的に使用されるCRLF (`\r\n`) に変換してから、`benchmarkReadRequest` ヘルパー関数に渡してベンチマークを実行します。
これらの変更により、`net/http` パッケージのHTTPリクエストパース処理の性能を客観的に測定し、将来的な性能改善のための基準点を提供することが可能になります。
## 関連リンク
* GitHubコミットページ: [https://github.com/golang/go/commit/cccc96b8e9f2e526d7b22cfc4af17569c47e8c31](https://github.com/golang/go/commit/cccc96b8e9f2e526d7b22cfc4af17569c47e8c31)
* `felixge/node-http-perf` GitHubリポジトリ: [https://github.com/felixge/node-http-perf](https://github.com/felixge/node-http-perf)
* Go言語のベンチマークに関する公式ドキュメント (Go 1.20): [https://pkg.go.dev/testing#hdr-Benchmarks](https://pkg.go.dev/testing#hdr-Benchmarks)
## 参考にした情報源リンク
* `felixge/node-http-perf` GitHubリポジトリ: [https://github.com/felixge/node-http-perf](https://github.com/felixge/node-http-perf)
* Go言語の `testing` パッケージドキュメント: [https://pkg.go.dev/testing](https://pkg.go.dev/testing)
* Go言語の `io` パッケージドキュメント: [https://pkg.go.dev/io](https://pkg.go.dev/io)
* Go言語の `bufio` パッケージドキュメント: [https://pkg.go.dev/bufio](https://pkg.go.dev/bufio)