[インデックス 15985] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージにおけるHTTPサーバーの応答ヘッダー処理に関する改善とテストの追加を目的としています。具体的には、http.ResponseWriter
の Write
, WriteHeader
, Header
, Flush
メソッドの呼び出し順序と、それらがHTTP応答ヘッダーに与える影響について、より堅牢なテストを追加し、関連するコードのコメントを改善しています。さらに、特定のシナリオにおけるメモリアロケーションを削減する最適化も含まれています。
コミット
commit 972cb4b442d6348868051c71304b348992519f4a
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Thu Mar 28 11:35:24 2013 -0700
net/http: more tests, better comments, remove an allocation
Add more tests around the various orders handlers can access
and flush response headers.
Also clarify the documentation on fields of response and
chunkWriter.
While there, remove an allocation (a header clone) for simple
handlers.
benchmark old ns/op new ns/op delta
BenchmarkServerFakeConnWithKeepAliveLite 15245 14966 -1.83%
benchmark old allocs new allocs delta
BenchmarkServerFakeConnWithKeepAliveLite 24 23 -4.17%
benchmark old bytes new bytes delta
BenchmarkServerFakeConnWithKeepAliveLite 1717 1668 -2.85%
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/8101043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/972cb4b442d6348868051c71304b348992519f4a
元コミット内容
net/http: more tests, better comments, remove an allocation
このコミットは、net/http
パッケージに対して以下の変更を行います。
- ハンドラが応答ヘッダーにアクセスし、フラッシュする様々な順序に関するテストを追加します。
response
およびchunkWriter
構造体のフィールドに関するドキュメントを明確化します。- その過程で、シンプルなハンドラの場合にヘッダーのクローン作成というアロケーションを削除します。
ベンチマーク結果:
BenchmarkServerFakeConnWithKeepAliveLite
ns/op
(操作あたりのナノ秒): 15245 -> 14966 (-1.83%)allocs
(アロケーション数): 24 -> 23 (-4.17%)bytes
(割り当てられたバイト数): 1717 -> 1668 (-2.85%)
変更の背景
このコミットの背景には、Go 1.0 と Go 1.1 における http.ResponseWriter
の WriteHeader
メソッドの挙動の違いがあります。
Go 1.0 では、rw.WriteHeader
が呼び出されると、応答ヘッダーが即座にワイヤー(ネットワーク)にフラッシュされていました。これは、ハンドラが WriteHeader
の呼び出し後にヘッダーを変更した場合、その変更がクライアントに送信される応答には反映されないことを意味します。
しかし、Go 1.1 では、実際のワイヤーへのフラッシュが遅延されるように変更されました。この変更の意図は、サーバーがより多くの(またはすべての)出力を見てから Content-Length
やより適切な Content-Type
を追加できるようにするためです。例えば、ハンドラが rw.Write
を呼び出すまで Content-Length
が不明な場合、Go 1.1 では最初の Write
が行われるまでヘッダーの送信を遅延させることで、正確な Content-Length
を含めることが可能になります。
この遅延フラッシュの挙動はパフォーマンスや柔軟性の向上に寄与しますが、Go 1.0 との互換性を維持するためには注意が必要です。特に、ハンドラが(誤って)最初の Write
の後にヘッダーを変更した場合でも、WriteHeader
が呼び出された時点でのヘッダーの状態が保持され、それがワイヤーに送信されるようにする必要があります。このコミットは、この互換性の問題に対処し、ヘッダーのライフサイクルをより明確にするためのものです。
また、net/http
パッケージはGo言語のウェブアプリケーション開発の基盤であり、そのパフォーマンスは非常に重要です。不要なメモリアロケーションを削減することは、特に高負荷な環境においてサーバーの効率を向上させる上で役立ちます。このコミットは、シンプルなハンドラにおけるヘッダーのクローン作成というアロケーションを削除することで、このパフォーマンス改善にも貢献しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の net/http
パッケージに関する知識が必要です。
-
http.ResponseWriter
インターフェース:- HTTPハンドラがクライアントに応答を書き込むために使用するインターフェースです。
- 主なメソッド:
Header() Header
: 応答ヘッダーを表すhttp.Header
マップを返します。このマップを変更することで、応答ヘッダーを設定できます。WriteHeader(statusCode int)
: HTTPステータスコードを書き込み、ヘッダーの送信を開始します。このメソッドが呼び出されると、通常、ヘッダーはワイヤーにフラッシュされます。Write(data []byte) (int, error)
: 応答ボディにデータを書き込みます。WriteHeader
がまだ呼び出されていない場合、Write
は暗黙的にWriteHeader(http.StatusOK)
を呼び出してからボディを書き込みます。
-
http.Header
型:map[string][]string
のエイリアスであり、HTTPヘッダーのキーと値のペアを表現します。キーは大文字・小文字を区別しません。
-
HTTP応答のライフサイクル:
- HTTPハンドラがリクエストを受け取ると、
http.ResponseWriter
と*http.Request
が渡されます。 - ハンドラは
rw.Header().Set("Key", "Value")
のようにヘッダーを設定します。 - ハンドラは
rw.WriteHeader(statusCode)
を呼び出してステータスコードを設定し、ヘッダーの送信をトリガーします。 - ハンドラは
rw.Write([]byte("body"))
を呼び出して応答ボディを書き込みます。 WriteHeader
が明示的に呼び出されない場合でも、最初のWrite
呼び出し時に暗黙的にWriteHeader(http.StatusOK)
が呼び出されます。
- HTTPハンドラがリクエストを受け取ると、
-
http.Flusher
インターフェース:http.ResponseWriter
がこのインターフェースを実装している場合、Flush()
メソッドを呼び出すことで、バッファリングされている応答データを強制的にクライアントに送信できます。これは、ストリーミング応答などで役立ちます。
-
Content-Length
とTransfer-Encoding: chunked
:- HTTP応答のボディの長さをクライアントに伝えるためのヘッダーです。
Content-Length
は、ボディの長さが事前にわかっている場合に使用されます。Transfer-Encoding: chunked
は、ボディの長さが事前にわからない場合や、データをストリーミングする場合に使用されます。この場合、ボディはチャンク(塊)に分割され、各チャンクの前にその長さが記述されます。
-
メモリアロケーションとパフォーマンス:
- Go言語では、オブジェクトがヒープに割り当てられると、ガベージコレクションの対象となります。ガベージコレクションはオーバーヘッドを伴うため、不要なアロケーションを削減することはパフォーマンス向上につながります。
- 特にHTTPサーバーのような高頻度でリクエストを処理するシステムでは、小さなアロケーションの削減でも全体のスループットに大きな影響を与えることがあります。
技術的詳細
このコミットの技術的詳細は、主に net/http
パッケージ内の response
構造体と chunkWriter
構造体の内部挙動、およびそれらがHTTP応答ヘッダーの送信にどのように関与しているかに焦点を当てています。
response
構造体 (src/pkg/net/http/server.go
)
response
構造体は、HTTPリクエストに対するサーバー側の応答を管理します。このコミットでは、以下のフィールドが追加・変更されています。
handlerHeader Header
: これはハンドラがrw.Header()
を通じてアクセスし、変更するヘッダーマップです。calledHeader bool
: 新しく追加されたフィールドで、ハンドラがrw.Header()
メソッドを呼び出したかどうかを追跡します。
response.Header()
メソッドの変更点:
以前は、response.Header()
が呼び出されると、w.cw.header
が w.handlerHeader
のクローンで初期化される可能性がありました。このコミットでは、w.calledHeader = true
が設定されるようになり、w.cw.header
のクローン作成ロジックが WriteHeader
メソッドに移動しました。
response.WriteHeader(code int)
メソッドの変更点:
このメソッドは、HTTPステータスコードを設定し、ヘッダーの送信を論理的に開始します。
重要な変更は、w.cw.header = w.handlerHeader.clone()
の行が条件付きになったことです。
if w.calledHeader && w.cw.header == nil { w.cw.header = w.handlerHeader.clone() }
これは、ハンドラが rw.Header()
を明示的に呼び出し(w.calledHeader
が true
)、かつ chunkWriter
のヘッダーがまだクローンされていない場合にのみ、ヘッダーのクローンを作成することを意味します。
また、Content-Length
のチェックが w.cw.header.get
から w.handlerHeader.get
に変更され、Content-Length
の削除も w.cw.header.Del
から w.handlerHeader.Del
に変更されました。これにより、handlerHeader
が直接操作されるようになります。
chunkWriter
構造体 (src/pkg/net/http/server.go
)
chunkWriter
構造体は、応答ボディの書き込みと、必要に応じてチャンクエンコーディングを処理します。この構造体は、実際のHTTPヘッダーをワイヤーに書き込む役割を担います。
header Header
: このフィールドは、ワイヤーに書き込まれる実際のヘッダーマップです。wroteHeader bool
: このフィールドは、ヘッダーが実際にワイヤーに書き込まれたかどうかを示します。
chunkWriter.writeHeader(p []byte)
メソッドの変更点:
このメソッドは、応答ヘッダーをワイヤーに書き込む実際のロジックを含んでいます。
最も重要な変更は、cw.header
の初期化ロジックです。
以前は、cw.header = w.handlerHeader.clone()
が無条件に実行されていました。
新しいロジックでは、cw.header
が nil
の場合、以下の条件で初期化されます。
w.handlerDone
がtrue
の場合(ハンドラが処理を完了し、これ以上ヘッダーを変更しないことが保証される場合)、cw.header
はw.handlerHeader
を直接参照します。これにより、不要なクローン作成が回避されます。w.handlerDone
がfalse
の場合(ハンドラがまだヘッダーを変更する可能性がある場合)、cw.header
はw.handlerHeader.clone()
を呼び出して、ヘッダーマップのスナップショットを作成します。これは、Go 1.0 との互換性を維持するためであり、ハンドラがWriteHeader
呼び出し後にヘッダーを(誤って)変更しても、その変更がワイヤーに送信されるヘッダーに影響を与えないようにするためです。
メモリアロケーションの削減
このコミットの主要な最適化は、シンプルなハンドラ(rw.Header()
を明示的に呼び出さないハンドラ)の場合に、ヘッダーのクローン作成を回避することです。
- Go 1.0 の挙動:
WriteHeader
が呼び出されると、handlerHeader
のクローンがchunkWriter.header
に格納され、それがワイヤーに送信されました。これは、ハンドラがrw.Header()
を呼び出したかどうかにかかわらず発生しました。 - Go 1.1 の変更(このコミット):
- ハンドラが
rw.Header()
を一度も呼び出さなかった場合(response.calledHeader
がfalse
)、response.WriteHeader
はヘッダーのクローンを作成しません。 chunkWriter.writeHeader
が呼び出された際、response.handlerDone
がtrue
であれば(つまり、ハンドラが処理を終えており、これ以上ヘッダーを変更しないことが確実であれば)、chunkWriter.header
はresponse.handlerHeader
を直接参照します。- これにより、シンプルなハンドラではヘッダーのクローン作成というアロケーションが完全に回避され、パフォーマンスが向上します。
- ハンドラが
テストの追加 (src/pkg/net/http/serve_test.go
)
TestHeaderToWire
という新しいテスト関数が追加され、WriteHeader
, Write
, Header
, Flush
の様々な組み合わせにおけるヘッダーの挙動が検証されています。これにより、Go 1.1 の遅延フラッシュの挙動がGo 1.0 との互換性を保ちつつ正しく機能することが保証されます。
テストケースの例:
write without Header
:Write
のみ呼び出し、Content-Length
とContent-Type
が自動的に設定されることを確認。Header mutation before write
:Header
を変更後Write
を呼び出し、Write
後にHeader
を変更しても反映されないことを確認。flush then write
:Flush
後にWrite
を呼び出し、チャンクエンコーディングが使用され、Flush
後にHeader
を変更しても反映されないことを確認。sniff-on-first-write content-type
: 最初のWrite
でコンテンツタイプがスニッフィングされ、明示的なContent-Type
がない場合にtext/html
などが設定されることを確認。explicit content-type wins
: 明示的に設定されたContent-Type
がスニッフィング結果よりも優先されることを確認。empty handler
: 空のハンドラの場合にContent-Type: text/plain
とContent-Length: 0
が設定されることを確認。WriteHeader call
:WriteHeader
呼び出し後にHeader
を変更しても反映されないことを確認。
これらのテストは、net/http
パッケージの堅牢性を高め、開発者が ResponseWriter
の挙動をより正確に理解するのに役立ちます。
ベンチマーク結果の解釈
コミットメッセージに含まれるベンチマーク結果は、この変更がパフォーマンスに与える好影響を示しています。
BenchmarkServerFakeConnWithKeepAliveLite
は、最もシンプルなHTTPリクエストとハンドラ(rw.Header()
を呼び出さない)をシミュレートするベンチマークです。ns/op
(操作あたりのナノ秒): 15245 -> 14966 (-1.83%)- これは、1つのHTTPリクエストを処理するのにかかる時間が約1.83%短縮されたことを意味します。
allocs
(アロケーション数): 24 -> 23 (-4.17%)- これは、1つのHTTPリクエスト処理あたりのメモリアロケーション数が1つ削減されたことを意味します。この1つの削減は、まさにヘッダーのクローン作成が回避されたことによるものです。
bytes
(割り当てられたバイト数): 1717 -> 1668 (-2.85%)- これは、1つのHTTPリクエスト処理あたりに割り当てられるメモリ量が約2.85%削減されたことを意味します。
これらの結果は、特に高負荷なHTTPサーバーにおいて、このコミットが全体的なスループットとメモリ効率の向上に貢献することを示しています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルと行に集中しています。
-
src/pkg/net/http/serve_test.go
:TestHeaderToWire
関数が新規追加されました (約200行の追加)。この関数は、WriteHeader
,Write
,Header
,Flush
の様々な組み合わせにおけるヘッダーの挙動を検証する多数のテストケースを含んでいます。BenchmarkServerFakeConnWithKeepAliveLite
ベンチマーク関数が新規追加されました (約30行の追加)。これは、最もシンプルなハンドラにおけるパフォーマンスを測定します。
-
src/pkg/net/http/server.go
:chunkWriter
構造体の定義が変更されました。header Header
フィールドのコメントがより詳細になりました。wroteHeader bool
フィールドのコメントがより詳細になりました。
response
構造体の定義にcalledHeader bool
フィールドが追加されました。response.Header()
メソッドの変更。w.calledHeader = true
の設定が追加されました。- 条件付きのヘッダークローンロジックが削除されました(
WriteHeader
に移動)。
response.WriteHeader(code int)
メソッドの変更。w.cw.header = w.handlerHeader.clone()
の行がif w.calledHeader && w.cw.header == nil { ... }
という条件付きのブロック内に移動しました。Content-Length
の取得と削除がw.cw.header
からw.handlerHeader
に変更されました。
chunkWriter.writeHeader(p []byte)
メソッドの変更。cw.header
の初期化ロジックが大幅に変更され、w.handlerDone
の状態に基づいてクローンを作成するか、直接参照するかが決定されるようになりました。Content-Length
の設定ロジックがcw.header.get
からw.handlerDone && cw.header.get
に変更されました。
コアとなるコードの解説
src/pkg/net/http/server.go
の変更点詳細
type chunkWriter struct
の変更
type chunkWriter struct {
res *response
// header is either the same as res.handlerHeader,
// or a deep clone if the handler called Header.
header Header
// wroteHeader tells whether the header's been written to "the
// wire" (or rather: w.conn.buf). this is unlike
// (*response).wroteHeader, which tells only whether it was
// logically written.
wroteHeader bool
// ... (他のフィールドは省略)
}
header
フィールドのコメントが更新され、res.handlerHeader
と同じであるか、ハンドラがHeader()
を呼び出した場合にディープクローンであるかが明確にされました。wroteHeader
フィールドのコメントも更新され、ヘッダーが物理的にワイヤーに書き込まれた状態を示すことが強調されました。これは(*response).wroteHeader
(論理的に書き込まれたかを示す)とは異なる点が明記されています。
type response struct
の変更
type response struct {
// ... (他のフィールドは省略)
// handlerHeader is copied into cw.header at WriteHeader
// time, and privately mutated thereafter.
handlerHeader Header
calledHeader bool // handler accessed handlerHeader via Header
// ... (他のフィールドは省略)
}
calledHeader bool
フィールドが追加されました。これは、ハンドラがrw.Header()
メソッドを呼び出してhandlerHeader
にアクセスしたかどうかを追跡するためのものです。このフラグは、ヘッダーのクローン作成を最適化するために使用されます。
func (w *response) Header() Header
の変更
func (w *response) Header() Header {
if w.cw.header == nil && w.wroteHeader && !w.cw.wroteHeader {
// Accessing the header between logically writing it
// and physically writing it means we need to allocate
// a clone to snapshot the logically written state.
w.cw.header = w.handlerHeader.clone()
}
w.calledHeader = true
return w.handlerHeader
}
w.calledHeader = true
が追加されました。これにより、ハンドラがHeader()
メソッドを呼び出したことが記録されます。if w.cw.header == nil && w.wroteHeader && !w.cw.wroteHeader
の条件は、ヘッダーが論理的に書き込まれた(w.wroteHeader
がtrue
)が、まだ物理的にワイヤーに書き込まれていない(!w.cw.wroteHeader
)状態で、かつchunkWriter
のヘッダーがまだ設定されていない場合に、handlerHeader
のクローンを作成してw.cw.header
に割り当てるためのものです。これは、ハンドラがWriteHeader
を呼び出した後にHeader()
を呼び出してヘッダーを変更しようとした場合に、その時点でのヘッダーの状態をスナップショットとして保持するための互換性ロジックです。
func (w *response) WriteHeader(code int)
の変更
func (w *response) WriteHeader(code int) {
// ... (既存のロジック)
w.wroteHeader = true
w.status = code
if w.calledHeader && w.cw.header == nil {
w.cw.header = w.handlerHeader.clone()
}
if cl := w.handlerHeader.get("Content-Length"); cl != "" {
v, err := strconv.ParseInt(cl, 10, 64)
if err == nil && v >= 0 {
w.contentLength = v
} else {
log.Printf("http: invalid Content-Length of %q", cl)
w.handlerHeader.Del("Content-Length")
}
}
}
if w.calledHeader && w.cw.header == nil { w.cw.header = w.handlerHeader.clone() }
が追加されました。これは、ハンドラがHeader()
を呼び出しており(w.calledHeader
がtrue
)、かつchunkWriter
のヘッダーがまだクローンされていない場合にのみ、handlerHeader
のクローンを作成してw.cw.header
に割り当てます。これにより、Header()
を呼び出さないシンプルなハンドラでは、このクローン作成が回避され、アロケーションが削減されます。Content-Length
の取得と削除の対象がw.cw.header
からw.handlerHeader
に変更されました。これは、handlerHeader
がハンドラが操作する「真の」ヘッダーマップであることを反映しています。
func (cw *chunkWriter) writeHeader(p []byte)
の変更
func (cw *chunkWriter) writeHeader(p []byte) {
// ... (既存のロジック)
cw.wroteHeader = true
w := cw.res
if cw.header == nil {
if w.handlerDone {
// The handler won't be making further changes to the
// response header map, so we use it directly.
cw.header = w.handlerHeader
} else {
// Snapshot the header map, since it might be some
// time before we actually write w.cw to the wire and
// we don't want the handler's potential future
// (arguably buggy) modifications to the map to make
// it into the written headers. This preserves
// compatibility with Go 1.0, which always flushed the
// headers on a call to rw.WriteHeader.
cw.header = w.handlerHeader.clone()
}
}
// If the handler is done but never sent a Content-Length
// response header and this is our first (and last) write, set
// it, even to zero. This helps HTTP/1.0 clients keep their
// "keep-alive" connections alive.
if w.handlerDone && cw.header.get("Content-Length") == "" && w.req.Method != "HEAD" {
w.contentLength = int64(len(p))
cw.header.Set("Content-Length", strconv.Itoa(len(p)))
}
// ... (既存のロジック)
code := w.status
// ... (既存のロジック)
}
cw.header
の初期化ロジックが大幅に変更されました。if cw.header == nil
ブロック内で、w.handlerDone
の値に基づいてcw.header
の設定方法が分岐します。w.handlerDone
がtrue
の場合(ハンドラが処理を完了し、これ以上ヘッダーを変更しないことが保証される場合)、cw.header
はw.handlerHeader
を直接参照します。これにより、不要なクローン作成が回避されます。w.handlerDone
がfalse
の場合(ハンドラがまだヘッダーを変更する可能性がある場合)、cw.header
はw.handlerHeader.clone()
を呼び出して、ヘッダーマップのスナップショットを作成します。これは、Go 1.0 との互換性を維持するためであり、ハンドラがWriteHeader
呼び出し後にヘッダーを(誤って)変更しても、その変更がワイヤーに送信されるヘッダーに影響を与えないようにするためです。
Content-Length
の自動設定ロジックの条件がdone
からw.handlerDone
に変更されました。これは、response
構造体のhandlerDone
フィールドが、ハンドラの完了状態を正確に反映していることを利用しています。code := w.status
の行が、cw.header
の初期化ロジックの後に移動しました。これは、code
がcw.header
の初期化ロジックに影響を与えないため、より適切な位置に配置されたものです。
src/pkg/net/http/serve_test.go
の変更点詳細
func TestHeaderToWire(t *testing.T)
の追加
このテスト関数は、net/http
パッケージの ResponseWriter
がヘッダーをワイヤーに書き込む際の様々な挙動を検証するために追加されました。特に、Write
, WriteHeader
, Header
, Flush
の呼び出し順序がヘッダーにどのように影響するかを詳細にテストします。
各テストケースは、handler
関数(テスト対象の http.HandlerFunc
)と check
関数(応答の出力が期待通りであるかを検証する)のペアで構成されます。
主要なテストシナリオ:
write without Header
:rw.Write
のみ呼び出した場合に、Content-Length
とContent-Type: text/plain
が自動的に設定されることを確認します。Header mutation before write
:rw.Header()
でヘッダーを設定し、rw.Write
を呼び出した後、再度rw.Header()
でヘッダーを変更しても、最初のWrite
時点のヘッダーが送信されることを確認します。これは、WriteHeader
が論理的に呼び出された後にヘッダーがスナップショットされる挙動を検証します。write then useless Header mutation
:rw.Write
を呼び出した後、rw.Header().Set
でヘッダーを変更しても、その変更が応答に含まれないことを確認します。flush then write
:rw.(Flusher).Flush()
を呼び出した後、rw.Write
を呼び出した場合に、Transfer-Encoding: chunked
が使用されることを確認します。また、Flush
後にヘッダーを変更しても反映されないことを確認します。header then flush
:rw.Header().Set
でヘッダーを設定し、rw.(Flusher).Flush()
を呼び出した後、rw.Write
を呼び出した場合に、設定したヘッダーが送信され、Transfer-Encoding: chunked
が使用されることを確認します。sniff-on-first-write content-type
:Content-Type
を明示的に設定せずにHTMLコンテンツをrw.Write
した場合に、Content-Type: text/html
が自動的にスニッフィングされて設定されることを確認します。explicit content-type wins
:Content-Type
を明示的に設定した場合に、コンテンツスニッフィングの結果よりも明示的な設定が優先されることを確認します。empty handler
: ハンドラが何も書き込まない場合に、Content-Type: text/plain
とContent-Length: 0
が設定されることを確認します。only Header, no write
:rw.Header().Set
でヘッダーを設定し、Write
やWriteHeader
を呼び出さない場合に、設定したヘッダーが送信されることを確認します。WriteHeader call
:rw.WriteHeader(404)
を呼び出した後、rw.Header().Set
でヘッダーを変更しても、その変更が応答に含まれないことを確認します。
これらのテストは、net/http
パッケージの ResponseWriter
の複雑な挙動を網羅的に検証し、Go 1.1 で導入された遅延フラッシュのロジックが期待通りに機能し、かつGo 1.0 との互換性が維持されていることを保証します。
func BenchmarkServerFakeConnWithKeepAliveLite(b *testing.B)
の追加
このベンチマーク関数は、BenchmarkServerFakeConnWithKeepAlive
と同様ですが、よりシンプルなシナリオを想定しています。具体的には、ハンドラが rw.Header()
を明示的に呼び出さない場合を測定します。
func BenchmarkServerFakeConnWithKeepAliveLite(b *testing.B) {
b.ReportAllocs()
req := []byte(strings.Replace(`GET / HTTP/1.1
Host: golang.org
`, "\n", "\r\n", -1))
res := []byte("Hello world!\n")
conn := &rwTestConn{
Reader: &repeatReader{content: req, count: b.N},
Writer: ioutil.Discard,
closec: make(chan bool, 1),
}
handled := 0
handler := HandlerFunc(func(rw ResponseWriter, r *Request) {
handled++
rw.Write(res)
})
ln := &oneConnListener{conn: conn}
go Serve(ln, handler)
<-conn.closec
if b.N != handled {
b.Errorf("b.N=%d but handled %d", b.N, handled)
}
}
- このベンチマークは、
rw.Header()
を呼び出さないハンドラが、ヘッダーのクローン作成を回避できることによるパフォーマンス向上を具体的に測定するために追加されました。 b.ReportAllocs()
が呼び出されており、アロケーション数も報告されるように設定されています。これにより、メモリアロケーションの削減効果が数値として確認できます。
関連リンク
- Go言語の
net/http
パッケージのドキュメント: https://pkg.go.dev/net/http - Go 1.1 リリースノート (HTTP/2.0 のサポートなど、関連する変更点が含まれる可能性があります): https://go.dev/doc/go1.1 (当時のリリースノートはGo 1.1のページに統合されている可能性があります)
- Go言語の
http.ResponseWriter
インターフェースに関する議論やドキュメント: https://pkg.go.dev/net/http#ResponseWriter
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード (
src/pkg/net/http/server.go
,src/pkg/net/http/serve_test.go
) - コミットメッセージに記載されている Go CL (Code Review) リンク: https://golang.org/cl/8101043 (このリンクは古い可能性があり、現在のGoのコードレビューシステムでは異なるURL形式になっている可能性がありますが、当時のCLへの参照として重要です。)
- Go言語のHTTPサーバーの内部実装に関する一般的な知識とベストプラクティス。
- HTTP/1.1 の仕様 (RFC 2616 または後継の RFC 7230-7235) におけるヘッダー処理、
Content-Length
、Transfer-Encoding: chunked
に関する情報。 - Go言語のベンチマークに関するドキュメント: https://go.dev/doc/articles/go_benchmarking_code (当時の情報源は異なる可能性がありますが、ベンチマークの概念理解に役立ちます)
- Go言語のガベージコレクションとメモリアロケーションに関する一般的な情報。
- Go 1.0 と Go 1.1 のリリースノートや変更履歴(当時の情報源を特定するのは困難ですが、Goの公式ブログやメーリングリストのアーカイブが参考になる可能性があります)。
- Go言語の
net/http
パッケージの進化に関するブログ記事や技術解説。