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

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

このコミットは、Go言語の標準ライブラリである net/http パッケージにおけるHTTPサーバーの応答ヘッダー処理に関する改善とテストの追加を目的としています。具体的には、http.ResponseWriterWrite, 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.ResponseWriterWriteHeader メソッドの挙動の違いがあります。

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 パッケージに関する知識が必要です。

  1. http.ResponseWriter インターフェース:

    • HTTPハンドラがクライアントに応答を書き込むために使用するインターフェースです。
    • 主なメソッド:
      • Header() Header: 応答ヘッダーを表す http.Header マップを返します。このマップを変更することで、応答ヘッダーを設定できます。
      • WriteHeader(statusCode int): HTTPステータスコードを書き込み、ヘッダーの送信を開始します。このメソッドが呼び出されると、通常、ヘッダーはワイヤーにフラッシュされます。
      • Write(data []byte) (int, error): 応答ボディにデータを書き込みます。WriteHeader がまだ呼び出されていない場合、Write は暗黙的に WriteHeader(http.StatusOK) を呼び出してからボディを書き込みます。
  2. http.Header:

    • map[string][]string のエイリアスであり、HTTPヘッダーのキーと値のペアを表現します。キーは大文字・小文字を区別しません。
  3. HTTP応答のライフサイクル:

    • HTTPハンドラがリクエストを受け取ると、http.ResponseWriter*http.Request が渡されます。
    • ハンドラは rw.Header().Set("Key", "Value") のようにヘッダーを設定します。
    • ハンドラは rw.WriteHeader(statusCode) を呼び出してステータスコードを設定し、ヘッダーの送信をトリガーします。
    • ハンドラは rw.Write([]byte("body")) を呼び出して応答ボディを書き込みます。
    • WriteHeader が明示的に呼び出されない場合でも、最初の Write 呼び出し時に暗黙的に WriteHeader(http.StatusOK) が呼び出されます。
  4. http.Flusher インターフェース:

    • http.ResponseWriter がこのインターフェースを実装している場合、Flush() メソッドを呼び出すことで、バッファリングされている応答データを強制的にクライアントに送信できます。これは、ストリーミング応答などで役立ちます。
  5. Content-LengthTransfer-Encoding: chunked:

    • HTTP応答のボディの長さをクライアントに伝えるためのヘッダーです。
    • Content-Length は、ボディの長さが事前にわかっている場合に使用されます。
    • Transfer-Encoding: chunked は、ボディの長さが事前にわからない場合や、データをストリーミングする場合に使用されます。この場合、ボディはチャンク(塊)に分割され、各チャンクの前にその長さが記述されます。
  6. メモリアロケーションとパフォーマンス:

    • 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.headerw.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.calledHeadertrue)、かつ 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.headernil の場合、以下の条件で初期化されます。

  1. w.handlerDonetrue の場合(ハンドラが処理を完了し、これ以上ヘッダーを変更しないことが保証される場合)、cw.headerw.handlerHeader を直接参照します。これにより、不要なクローン作成が回避されます。
  2. w.handlerDonefalse の場合(ハンドラがまだヘッダーを変更する可能性がある場合)、cw.headerw.handlerHeader.clone() を呼び出して、ヘッダーマップのスナップショットを作成します。これは、Go 1.0 との互換性を維持するためであり、ハンドラが WriteHeader 呼び出し後にヘッダーを(誤って)変更しても、その変更がワイヤーに送信されるヘッダーに影響を与えないようにするためです。

メモリアロケーションの削減

このコミットの主要な最適化は、シンプルなハンドラ(rw.Header() を明示的に呼び出さないハンドラ)の場合に、ヘッダーのクローン作成を回避することです。

  • Go 1.0 の挙動: WriteHeader が呼び出されると、handlerHeader のクローンが chunkWriter.header に格納され、それがワイヤーに送信されました。これは、ハンドラが rw.Header() を呼び出したかどうかにかかわらず発生しました。
  • Go 1.1 の変更(このコミット):
    • ハンドラが rw.Header() を一度も呼び出さなかった場合(response.calledHeaderfalse)、response.WriteHeader はヘッダーのクローンを作成しません。
    • chunkWriter.writeHeader が呼び出された際、response.handlerDonetrue であれば(つまり、ハンドラが処理を終えており、これ以上ヘッダーを変更しないことが確実であれば)、chunkWriter.headerresponse.handlerHeader を直接参照します。
    • これにより、シンプルなハンドラではヘッダーのクローン作成というアロケーションが完全に回避され、パフォーマンスが向上します。

テストの追加 (src/pkg/net/http/serve_test.go)

TestHeaderToWire という新しいテスト関数が追加され、WriteHeader, Write, Header, Flush の様々な組み合わせにおけるヘッダーの挙動が検証されています。これにより、Go 1.1 の遅延フラッシュの挙動がGo 1.0 との互換性を保ちつつ正しく機能することが保証されます。

テストケースの例:

  • write without Header: Write のみ呼び出し、Content-LengthContent-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/plainContent-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サーバーにおいて、このコミットが全体的なスループットとメモリ効率の向上に貢献することを示しています。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルと行に集中しています。

  1. src/pkg/net/http/serve_test.go:

    • TestHeaderToWire 関数が新規追加されました (約200行の追加)。この関数は、WriteHeader, Write, Header, Flush の様々な組み合わせにおけるヘッダーの挙動を検証する多数のテストケースを含んでいます。
    • BenchmarkServerFakeConnWithKeepAliveLite ベンチマーク関数が新規追加されました (約30行の追加)。これは、最もシンプルなハンドラにおけるパフォーマンスを測定します。
  2. 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.wroteHeadertrue)が、まだ物理的にワイヤーに書き込まれていない(!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.calledHeadertrue)、かつ 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.handlerDonetrue の場合(ハンドラが処理を完了し、これ以上ヘッダーを変更しないことが保証される場合)、cw.headerw.handlerHeader を直接参照します。これにより、不要なクローン作成が回避されます。
    • w.handlerDonefalse の場合(ハンドラがまだヘッダーを変更する可能性がある場合)、cw.headerw.handlerHeader.clone() を呼び出して、ヘッダーマップのスナップショットを作成します。これは、Go 1.0 との互換性を維持するためであり、ハンドラが WriteHeader 呼び出し後にヘッダーを(誤って)変更しても、その変更がワイヤーに送信されるヘッダーに影響を与えないようにするためです。
  • Content-Length の自動設定ロジックの条件が done から w.handlerDone に変更されました。これは、response 構造体の handlerDone フィールドが、ハンドラの完了状態を正確に反映していることを利用しています。
  • code := w.status の行が、cw.header の初期化ロジックの後に移動しました。これは、codecw.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-LengthContent-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/plainContent-Length: 0 が設定されることを確認します。
  • only Header, no write: rw.Header().Set でヘッダーを設定し、WriteWriteHeader を呼び出さない場合に、設定したヘッダーが送信されることを確認します。
  • 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-LengthTransfer-Encoding: chunked に関する情報。
  • Go言語のベンチマークに関するドキュメント: https://go.dev/doc/articles/go_benchmarking_code (当時の情報源は異なる可能性がありますが、ベンチマークの概念理解に役立ちます)
  • Go言語のガベージコレクションとメモリアロケーションに関する一般的な情報。
  • Go 1.0 と Go 1.1 のリリースノートや変更履歴(当時の情報源を特定するのは困難ですが、Goの公式ブログやメーリングリストのアーカイブが参考になる可能性があります)。
  • Go言語の net/http パッケージの進化に関するブログ記事や技術解説。