[インデックス 15846] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet/http
パッケージに、ネットワーク層を介さずにHTTPサーバーのパフォーマンスを測定するための新しいベンチマークを追加するものです。特に、リクエスト処理あたりのメモリ割り当て(ガベージ生成)を詳細に分析できるように設計されています。
コミット
commit 1ce0d72fdf4bde690dad8e922ab7ae7588051964
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Tue Mar 19 23:32:28 2013 -0700
net/http: new benchmark to measure server without network
No net package involved. And with ReportAllocs we can see
how much garbage is created per request.
R=adg, dave
CC=golang-dev
https://golang.org/cl/7913044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1ce0d72fdf4bde690dad8e922ab7ae7588051964
元コミット内容
net/http
: ネットワークを介さないサーバー測定のための新しいベンチマーク
ネットワークパッケージは関与しません。そしてReportAllocs
を使用することで、リクエストごとにどれだけのガベージが生成されるかを確認できます。
変更の背景
Goのnet/http
パッケージは、ウェブアプリケーションやAPIサーバーを構築する上で非常に重要なコンポーネントです。サーバーのパフォーマンスを評価する際、通常はネットワークを介したエンドツーエンドのベンチマークが行われます。しかし、ネットワークI/OやOSのソケット操作といった外部要因がベンチマーク結果に影響を与え、純粋なHTTPサーバーのロジック(リクエストのパース、ハンドラの実行、レスポンスの書き込みなど)のパフォーマンス特性を正確に把握することが困難になる場合があります。
このコミットの背景には、以下の目的があったと考えられます。
- 純粋なサーバーロジックの測定: ネットワーク層のオーバーヘッドを排除し、
net/http
サーバーがリクエストを処理し、レスポンスを生成する際の純粋なCPUおよびメモリ使用量を測定したいというニーズがありました。これにより、サーバー内部のボトルネックをより正確に特定できます。 - メモリ割り当ての分析:
ReportAllocs
機能を使用して、各HTTPリクエスト処理中にどれだけのメモリが割り当てられ、ガベージコレクションの対象となるか("garbage created per request")を詳細に分析することを目指しています。これは、サーバーのメモリ効率を改善し、ガベージコレクションによる一時停止(GC pauses)を減らす上で非常に重要です。 - 最適化の指針: ネットワークを介さないベンチマークは、サーバーの内部処理における最適化の機会を特定するための明確な指針を提供します。例えば、リクエストやレスポンスのパース、ヘッダー処理、ハンドラ実行における不要なメモリ割り当てを特定し、削減することができます。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびHTTPに関する知識が必要です。
- Go言語のベンチマーク:
testing
パッケージ: Goには標準でベンチマーク機能が組み込まれており、go test -bench=.
コマンドで実行できます。ベンチマーク関数はBenchmarkXxx(*testing.B)
というシグネチャを持ちます。*testing.B
: ベンチマークのコンテキストを提供します。b.N
はベンチマークが実行されるイテレーション回数を示し、b.ReportAllocs()
はメモリ割り当ての統計を報告するように設定します。b.ResetTimer()
: ベンチマークの計測を開始する前に、セットアップコードの時間をリセットするために使用されます。b.StopTimer()
: ベンチマークの計測を一時停止します。
net/http
パッケージ:http.HandlerFunc
:func(ResponseWriter, *Request)
というシグネチャを持つ関数をhttp.Handler
インターフェースに適合させるためのアダプターです。http.ResponseWriter
: HTTPレスポンスを書き込むためのインターフェースです。ヘッダーの設定やボディの書き込みを行います。http.Request
: 受信したHTTPリクエストを表す構造体です。URL、ヘッダー、ボディなどの情報を含みます。http.Serve(l net.Listener, handler Handler)
: 指定されたnet.Listener
で接続を受け入れ、各接続に対して指定されたHandler
を使用してHTTPリクエストを処理するサーバーを開始します。
io.Reader
とio.Writer
: Goの標準ライブラリにおけるI/Oの基本的なインターフェースです。io.Reader
: データを読み込むためのインターフェースで、Read([]byte) (n int, err error)
メソッドを持ちます。io.Writer
: データを書き込むためのインターフェースで、Write([]byte) (n int, err error)
メソッドを持ちます。
bytes.Buffer
: 可変長のバイトバッファを実装する型で、io.Reader
およびio.Writer
インターフェースを満たします。メモリ内でデータを効率的に操作するのに便利です。- HTTP/1.0: HTTPプロトコルのバージョンの一つ。このベンチマークでは、
Connection: close
がデフォルトであるHTTP/1.0リクエストをシミュレートしています。これは、各リクエスト後に接続が閉じられることを意味します。 - ガベージコレクション (GC): Goのランタイムは自動的にメモリを管理し、不要になったメモリを解放します。このプロセスはガベージコレクションと呼ばれます。GCの頻度や時間は、プログラムのパフォーマンスに影響を与える可能性があります。
ReportAllocs
は、GCの負荷を評価するのに役立ちます。
技術的詳細
追加されたベンチマーク関数BenchmarkServerFakeConnNoKeepAlive
は、実際のネットワーク接続を確立せずに、net/http
サーバーのコアロジックをテストするために設計されています。これは、カスタムのnet.Conn
実装とnet.Listener
実装を使用することで実現されています。
-
testConn
構造体:readBuf *bytes.Buffer
: クライアントからのリクエストデータをシミュレートするためのバッファです。io.Reader
として機能します。writeBuf *bytes.Buffer
: サーバーからのレスポンスデータをキャプチャするためのバッファです。io.Writer
として機能します。closec chan bool
: 接続が閉じられたことを通知するためのチャネルです。Close()
メソッドが呼び出されたときにtrue
を送信します。Read(b []byte) (n int, err error)
:readBuf
からデータを読み込みます。Write(b []byte) (n int, err error)
:writeBuf
にデータを書き込みます。Close() error
:closec
チャネルにtrue
を送信し、接続が閉じられたことをシミュレートします。- その他の
net.Conn
インターフェースのメソッド(LocalAddr
,RemoteAddr
,SetDeadline
,SetReadDeadline
,SetWriteDeadline
)は、このベンチマークの目的には不要なため、ダミーの実装がされています。
-
oneConnListener
構造体:conn net.Conn
:Accept()
メソッドが呼び出されたときに返す単一のnet.Conn
インスタンスを保持します。Accept() (c net.Conn, err error)
:l.conn
を一度だけ返し、その後はエラーを返します。これにより、http.Serve
が単一の接続のみを処理するようにします。Close() error
: ダミーの実装です。Addr() net.Addr
: ダミーの実装です。
-
ベンチマークロジック:
b.ReportAllocs()
: メモリ割り当ての統計を有効にします。req := []byte(...)
: HTTP/1.0のGETリクエストのバイト列を定義します。strings.Replace
を使用して改行コードをCRLF (\r\n
) に変換しています。res := []byte("Hello world!\n")
: サーバーが返すシンプルなレスポンスボディを定義します。conn := &testConn{...}
:testConn
のインスタンスを作成します。handler := HandlerFunc(...)
:/
パスへのリクエストに対してContent-Type
ヘッダーを設定し、"Hello world!\n"
を書き込むシンプルなHTTPハンドラを定義します。ln := new(oneConnListener)
:oneConnListener
のインスタンスを作成します。for i := 0; i < b.N; i++
: ベンチマークのイテレーションループです。conn.readBuf.Reset()
: 各イテレーションの前に、リクエストバッファをリセットします。conn.writeBuf.Reset()
: 各イテレーションの前に、レスポンスバッファをリセットします。conn.readBuf.Write(req)
: 定義済みのHTTPリクエストをreadBuf
に書き込み、クライアントからの入力として準備します。ln.conn = conn
:oneConnListener
に現在のtestConn
インスタンスを設定します。Serve(ln, handler)
:http.Serve
関数を呼び出し、oneConnListener
とハンドラを渡します。これにより、http.Serve
はln.Accept()
を呼び出してconn
を取得し、その接続上でHTTPリクエストを処理します。<-conn.closec
:testConn
のClose()
メソッドが呼び出されるまでブロックします。http.Serve
はHTTP/1.0の接続処理が完了するとClose()
を呼び出すため、これによりリクエスト処理の完了を待ちます。
このベンチマークは、net.Conn
とnet.Listener
をモックすることで、実際のTCP/IPスタックやソケット操作をバイパスし、net/http
パッケージ内の純粋なHTTPプロトコル処理、リクエスト/レスポンスのパース、ハンドラ実行、レスポンス書き込みのパフォーマンスを測定します。ReportAllocs
により、これらの操作がどれだけのメモリを一時的に割り当てるかを詳細に分析できるため、メモリ効率の最適化に役立ちます。
コアとなるコードの変更箇所
--- a/src/pkg/net/http/serve_test.go
+++ b/src/pkg/net/http/serve_test.go
@@ -1620,3 +1620,34 @@ func BenchmarkServer(b *testing.B) {
b.Errorf("Test failure: %v, with output: %s", err, out)
}
}
+
+func BenchmarkServerFakeConnNoKeepAlive(b *testing.B) {
+ b.ReportAllocs()
+ req := []byte(strings.Replace(`GET / HTTP/1.0
+Host: golang.org
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+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
+Accept-Encoding: gzip,deflate,sdch
+Accept-Language: en-US,en;q=0.8
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
+
+`, "\n", "\r\n", -1))
+ res := []byte("Hello world!\n")
+
+ conn := &testConn{
+ closec: make(chan bool),
+ }
+ handler := HandlerFunc(func(rw ResponseWriter, r *Request) {
+ rw.Header().Set("Content-Type", "text/html; charset=utf-8")
+ rw.Write(res)
+ })
+ ln := new(oneConnListener)
+ for i := 0; i < b.N; i++ {
+ conn.readBuf.Reset()
+ conn.writeBuf.Reset()
+ conn.readBuf.Write(req)
+ ln.conn = conn
+ Serve(ln, handler)
+ <-conn.closec
+ }
+}
コアとなるコードの解説
追加されたBenchmarkServerFakeConnNoKeepAlive
関数は、src/pkg/net/http/serve_test.go
ファイルに定義されています。
func BenchmarkServerFakeConnNoKeepAlive(b *testing.B) {
// ベンチマーク中に発生するメモリ割り当て(ガベージ生成)を報告するように設定します。
// これにより、リクエスト処理あたりのメモリ効率を評価できます。
b.ReportAllocs()
// シミュレートするHTTP/1.0 GETリクエストのバイト列を定義します。
// Goの文字列リテラルはLF(\n)を使用するため、HTTPプロトコルで要求されるCRLF(\r\n)に変換しています。
req := []byte(strings.Replace(`GET / HTTP/1.0
Host: golang.org
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
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
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
`, "\n", "\r\n", -1))
// サーバーが返すシンプルなレスポンスボディを定義します。
res := []byte("Hello world!\n")
// ネットワーク接続をシミュレートするためのカスタム`testConn`インスタンスを作成します。
// `closec`チャネルは、接続が閉じられたことをベンチマークループに通知するために使用されます。
conn := &testConn{
closec: make(chan bool),
}
// リクエストを処理するHTTPハンドラを定義します。
// このハンドラは、Content-Typeヘッダーを設定し、事前に定義された`res`バイト列をレスポンスボディとして書き込みます。
handler := HandlerFunc(func(rw ResponseWriter, r *Request) {
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
rw.Write(res)
})
// 単一の接続を返すカスタム`net.Listener`をシミュレートするインスタンスを作成します。
ln := new(oneConnListener)
// ベンチマークのイテレーションループです。b.Nはベンチマークの実行回数を示します。
for i := 0; i < b.N; i++ {
// 各イテレーションの開始時に、リクエストとレスポンスのバッファをリセットします。
// これにより、前のイテレーションのデータが残らないようにし、クリーンな状態で測定できます。
conn.readBuf.Reset()
conn.writeBuf.Reset()
// シミュレートされたリクエストデータを`testConn`の読み込みバッファに書き込みます。
// これが`http.Serve`が読み込む入力となります。
conn.readBuf.Write(req)
// `oneConnListener`に現在の`testConn`インスタンスを設定します。
// `http.Serve`はこのリスナーからこの接続を取得します。
ln.conn = conn
// `http.Serve`関数を呼び出し、シミュレートされたリスナーとハンドラを渡します。
// これにより、ネットワーク層を介さずにHTTPサーバーの内部処理が実行されます。
Serve(ln, handler)
// `testConn`の`Close()`メソッドが呼び出されるまでブロックします。
// `http.Serve`はHTTP/1.0の接続処理が完了すると`Close()`を呼び出すため、
// これにより、現在のリクエスト処理が完全に終了するのを待ちます。
<-conn.closec
}
}
このコードは、net/http
パッケージの内部動作を、実際のネットワークI/Oから切り離してベンチマークするための巧妙な方法を示しています。これにより、サーバーの純粋な処理能力とメモリ効率を正確に測定することが可能になります。
関連リンク
- https://golang.org/cl/7913044 - このコミットに対応するGoのコードレビューシステム(Gerrit)のチェンジリスト。
参考にした情報源リンク
- Go言語の
testing
パッケージのドキュメント: https://pkg.go.dev/testing - Go言語の
net/http
パッケージのドキュメント: https://pkg.go.dev/net/http - Go言語の
bytes
パッケージのドキュメント: https://pkg.go.dev/bytes - Go言語の
io
パッケージのドキュメント: https://pkg.go.dev/io - HTTP/1.0 プロトコル仕様 (RFC 1945): https://datatracker.ietf.org/doc/html/rfc1945# [インデックス 15846] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet/http
パッケージに、ネットワーク層を介さずにHTTPサーバーのパフォーマンスを測定するための新しいベンチマークを追加するものです。特に、リクエスト処理あたりのメモリ割り当て(ガベージ生成)を詳細に分析できるように設計されています。
コミット
commit 1ce0d72fdf4bde690dad8e922ab7ae7588051964
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Tue Mar 19 23:32:28 2013 -0700
net/http: new benchmark to measure server without network
No net package involved. And with ReportAllocs we can see
how much garbage is created per request.
R=adg, dave
CC=golang-dev
https://golang.org/cl/7913044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1ce0d72fdf4bde690dad8e922ab7ae7588051964
元コミット内容
net/http
: ネットワークを介さないサーバー測定のための新しいベンチマーク
ネットワークパッケージは関与しません。そしてReportAllocs
を使用することで、リクエストごとにどれだけのガベージが生成されるかを確認できます。
変更の背景
Goのnet/http
パッケージは、ウェブアプリケーションやAPIサーバーを構築する上で非常に重要なコンポーネントです。サーバーのパフォーマンスを評価する際、通常はネットワークを介したエンドツーエンドのベンチマークが行われます。しかし、ネットワークI/OやOSのソケット操作といった外部要因がベンチマーク結果に影響を与え、純粋なHTTPサーバーのロジック(リクエストのパース、ハンドラの実行、レスポンスの書き込みなど)のパフォーマンス特性を正確に把握することが困難になる場合があります。
このコミットの背景には、以下の目的があったと考えられます。
- 純粋なサーバーロジックの測定: ネットワーク層のオーバーヘッドを排除し、
net/http
サーバーがリクエストを処理し、レスポンスを生成する際の純粋なCPUおよびメモリ使用量を測定したいというニーズがありました。これにより、サーバー内部のボトルネックをより正確に特定できます。 - メモリ割り当ての分析:
ReportAllocs
機能を使用して、各HTTPリクエスト処理中にどれだけのメモリが割り当てられ、ガベージコレクションの対象となるか("garbage created per request")を詳細に分析することを目指しています。これは、サーバーのメモリ効率を改善し、ガベージコレクションによる一時停止(GC pauses)を減らす上で非常に重要です。 - 最適化の指針: ネットワークを介さないベンチマークは、サーバーの内部処理における最適化の機会を特定するための明確な指針を提供します。例えば、リクエストやレスポンスのパース、ヘッダー処理、ハンドラ実行における不要なメモリ割り当てを特定し、削減することができます。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびHTTPに関する知識が必要です。
- Go言語のベンチマーク:
testing
パッケージ: Goには標準でベンチマーク機能が組み込まれており、go test -bench=.
コマンドで実行できます。ベンチマーク関数はBenchmarkXxx(*testing.B)
というシグネチャを持ちます。*testing.B
: ベンチマークのコンテキストを提供します。b.N
はベンチマークが実行されるイテレーション回数を示し、b.ReportAllocs()
はメモリ割り当ての統計を報告するように設定します。b.ResetTimer()
: ベンチマークの計測を開始する前に、セットアップコードの時間をリセットするために使用されます。b.StopTimer()
: ベンチマークの計測を一時停止します。
net/http
パッケージ:http.HandlerFunc
:func(ResponseWriter, *Request)
というシグネチャを持つ関数をhttp.Handler
インターフェースに適合させるためのアダプターです。http.ResponseWriter
: HTTPレスポンスを書き込むためのインターフェースです。ヘッダーの設定やボディの書き込みを行います。http.Request
: 受信したHTTPリクエストを表す構造体です。URL、ヘッダー、ボディなどの情報を含みます。http.Serve(l net.Listener, handler Handler)
: 指定されたnet.Listener
で接続を受け入れ、各接続に対して指定されたHandler
を使用してHTTPリクエストを処理するサーバーを開始します。
io.Reader
とio.Writer
: Goの標準ライブラリにおけるI/Oの基本的なインターフェースです。io.Reader
: データを読み込むためのインターフェースで、Read([]byte) (n int, err error)
メソッドを持ちます。io.Writer
: データを書き込むためのインターフェースで、Write([]byte) (n int, err error)
メソッドを持ちます。
bytes.Buffer
: 可変長のバイトバッファを実装する型で、io.Reader
およびio.Writer
インターフェースを満たします。メモリ内でデータを効率的に操作するのに便利です。- HTTP/1.0: HTTPプロトコルのバージョンの一つ。このベンチマークでは、
Connection: close
がデフォルトであるHTTP/1.0リクエストをシミュレートしています。これは、各リクエスト後に接続が閉じられることを意味します。 - ガベージコレクション (GC): Goのランタイムは自動的にメモリを管理し、不要になったメモリを解放します。このプロセスはガベージコレクションと呼ばれます。GCの頻度や時間は、プログラムのパフォーマンスに影響を与える可能性があります。
ReportAllocs
は、GCの負荷を評価するのに役立ちます。
技術的詳細
追加されたベンチマーク関数BenchmarkServerFakeConnNoKeepAlive
は、実際のネットワーク接続を確立せずに、net/http
サーバーのコアロジックをテストするために設計されています。これは、カスタムのnet.Conn
実装とnet.Listener
実装を使用することで実現されています。
-
testConn
構造体:readBuf *bytes.Buffer
: クライアントからのリクエストデータをシミュレートするためのバッファです。io.Reader
として機能します。writeBuf *bytes.Buffer
: サーバーからのレスポンスデータをキャプチャするためのバッファです。io.Writer
として機能します。closec chan bool
: 接続が閉じられたことを通知するためのチャネルです。Close()
メソッドが呼び出されたときにtrue
を送信します。Read(b []byte) (n int, err error)
:readBuf
からデータを読み込みます。Write(b []byte) (n int, err error)
:writeBuf
にデータを書き込みます。Close() error
:closec
チャネルにtrue
を送信し、接続が閉じられたことをシミュレートします。- その他の
net.Conn
インターフェースのメソッド(LocalAddr
,RemoteAddr
,SetDeadline
,SetReadDeadline
,SetWriteDeadline
)は、このベンチマークの目的には不要なため、ダミーの実装がされています。
-
oneConnListener
構造体:conn net.Conn
:Accept()
メソッドが呼び出されたときに返す単一のnet.Conn
インスタンスを保持します。Accept() (c net.Conn, err error)
:l.conn
を一度だけ返し、その後はエラーを返します。これにより、http.Serve
が単一の接続のみを処理するようにします。Close() error
: ダミーの実装です。Addr() net.Addr
: ダミーの実装です。
-
ベンチマークロジック:
b.ReportAllocs()
: メモリ割り当ての統計を有効にします。req := []byte(...)
: HTTP/1.0のGETリクエストのバイト列を定義します。strings.Replace
を使用して改行コードをCRLF (\r\n
) に変換しています。res := []byte("Hello world!\n")
: サーバーが返すシンプルなレスポンスボディを定義します。conn := &testConn{...}
:testConn
のインスタンスを作成します。handler := HandlerFunc(...)
:/
パスへのリクエストに対してContent-Type
ヘッダーを設定し、"Hello world!\n"
を書き込むシンプルなHTTPハンドラを定義します。ln := new(oneConnListener)
:oneConnListener
のインスタンスを作成します。for i := 0; i < b.N; i++
: ベンチマークのイテレーションループです。conn.readBuf.Reset()
: 各イテレーションの前に、リクエストバッファをリセットします。conn.writeBuf.Reset()
: 各イテレーションの前に、レスポンスバッファをリセットします。conn.readBuf.Write(req)
: 定義済みのHTTPリクエストをreadBuf
に書き込み、クライアントからの入力として準備します。ln.conn = conn
:oneConnListener
に現在のtestConn
インスタンスを設定します。Serve(ln, handler)
:http.Serve
関数を呼び出し、oneConnListener
とハンドラを渡します。これにより、http.Serve
はln.Accept()
を呼び出してconn
を取得し、その接続上でHTTPリクエストを処理します。<-conn.closec
:testConn
のClose()
メソッドが呼び出されるまでブロックします。http.Serve
はHTTP/1.0の接続処理が完了するとClose()
を呼び出すため、これによりリクエスト処理の完了を待ちます。
このベンチマークは、net.Conn
とnet.Listener
をモックすることで、実際のTCP/IPスタックやソケット操作をバイパスし、net/http
パッケージ内の純粋なHTTPプロトコル処理、リクエスト/レスポンスのパース、ハンドラ実行、レスポンス書き込みのパフォーマンスを測定します。ReportAllocs
により、これらの操作がどれだけのメモリを一時的に割り当てるかを詳細に分析できるため、メモリ効率の最適化に役立ちます。
コアとなるコードの変更箇所
--- a/src/pkg/net/http/serve_test.go
+++ b/src/pkg/net/http/serve_test.go
@@ -1620,3 +1620,34 @@ func BenchmarkServer(b *testing.B) {
b.Errorf("Test failure: %v, with output: %s", err, out)
}
}
+
+func BenchmarkServerFakeConnNoKeepAlive(b *testing.B) {
+ b.ReportAllocs()
+ req := []byte(strings.Replace(`GET / HTTP/1.0
+Host: golang.org
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+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
+Accept-Encoding: gzip,deflate,sdch
+Accept-Language: en-US,en;q=0.8
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
+
+`, "\n", "\r\n", -1))
+ res := []byte("Hello world!\n")
+
+ conn := &testConn{
+ closec: make(chan bool),
+ }
+ handler := HandlerFunc(func(rw ResponseWriter, r *Request) {
+ rw.Header().Set("Content-Type", "text/html; charset=utf-8")
+ rw.Write(res)
+ })
+ ln := new(oneConnListener)
+ for i := 0; i < b.N; i++ {
+ conn.readBuf.Reset()
+ conn.writeBuf.Reset()
+ conn.readBuf.Write(req)
+ ln.conn = conn
+ Serve(ln, handler)
+ <-conn.closec
+ }
+}
コアとなるコードの解説
追加されたBenchmarkServerFakeConnNoKeepAlive
関数は、src/pkg/net/http/serve_test.go
ファイルに定義されています。
func BenchmarkServerFakeConnNoKeepAlive(b *testing.B) {
// ベンチマーク中に発生するメモリ割り当て(ガベージ生成)を報告するように設定します。
// これにより、リクエスト処理あたりのメモリ効率を評価できます。
b.ReportAllocs()
// シミュレートするHTTP/1.0 GETリクエストのバイト列を定義します。
// Goの文字列リテラルはLF(\n)を使用するため、HTTPプロトコルで要求されるCRLF(\r\n)に変換しています。
req := []byte(strings.Replace(`GET / HTTP/1.0
Host: golang.org
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
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
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
`, "\n", "\r\n", -1))
// サーバーが返すシンプルなレスポンスボディを定義します。
res := []byte("Hello world!\n")
// ネットワーク接続をシミュレートするためのカスタム`testConn`インスタンスを作成します。
// `closec`チャネルは、接続が閉じられたことをベンチマークループに通知するために使用されます。
conn := &testConn{
closec: make(chan bool),
}
// リクエストを処理するHTTPハンドラを定義します。
// このハンドラは、Content-Typeヘッダーを設定し、事前に定義された`res`バイト列をレスポンスボディとして書き込みます。
handler := HandlerFunc(func(rw ResponseWriter, r *Request) {
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
rw.Write(res)
})
// 単一の接続を返すカスタム`net.Listener`をシミュレートするインスタンスを作成します。
ln := new(oneConnListener)
// ベンチマークのイテレーションループです。b.Nはベンチマークの実行回数を示します。
for i := 0; i < b.N; i++ {
// 各イテレーションの開始時に、リクエストとレスポンスのバッファをリセットします。
// これにより、前のイテレーションのデータが残らないようにし、クリーンな状態で測定できます。
conn.readBuf.Reset()
conn.writeBuf.Reset()
// シミュレートされたリクエストデータを`testConn`の読み込みバッファに書き込みます。
// これが`http.Serve`が読み込む入力となります。
conn.readBuf.Write(req)
// `oneConnListener`に現在の`testConn`インスタンスを設定します。
// `http.Serve`はこのリスナーからこの接続を取得します。
ln.conn = conn
// `http.Serve`関数を呼び出し、シミュレートされたリスナーとハンドラを渡します。
// これにより、ネットワーク層を介さずにHTTPサーバーの内部処理が実行されます。
Serve(ln, handler)
// `testConn`の`Close()`メソッドが呼び出されるまでブロックします。
// `http.Serve`はHTTP/1.0の接続処理が完了すると`Close()`を呼び出すため、
// これにより、現在のリクエスト処理が完全に終了するのを待ちます。
<-conn.closec
}
}
このコードは、net/http
パッケージの内部動作を、実際のネットワークI/Oから切り離してベンチマークするための巧妙な方法を示しています。これにより、サーバーの純粋な処理能力とメモリ効率を正確に測定することが可能になります。
関連リンク
- https://golang.org/cl/7913044 - このコミットに対応するGoのコードレビューシステム(Gerrit)のチェンジリスト。
参考にした情報源リンク
- Go言語の
testing
パッケージのドキュメント: https://pkg.go.dev/testing - Go言語の
net/http
パッケージのドキュメント: https://pkg.go.dev/net/http - Go言語の
bytes
パッケージのドキュメント: https://pkg.go.dev/bytes - Go言語の
io
パッケージのドキュメント: https://pkg.go.dev/io - HTTP/1.0 プロトコル仕様 (RFC 1945): https://datatracker.ietf.org/doc/html/rfc1945