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

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

このコミットは、Go言語の標準ライブラリである net/http パッケージ内のHTTPサーバー実装を簡素化し、bufio.Reader および bufio.WriterReset メソッドを活用することで、パフォーマンスを向上させることを目的としています。具体的には、以前使用されていた switchReaderswitchWriter といったカスタムのラッパー型を廃止し、bufio パッケージが提供する標準機能に直接依存するように変更されています。これにより、コードの複雑性が軽減され、リソースの再利用が効率化されています。

コミット

commit 3e38b7f2465a6ab476cb0e184f2b2abee1a6e76f
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Sat Aug 10 19:22:44 2013 -0700

    net/http: simplify server, use bufio Reader.Reset and Writer.Reset
    
    Update #5100
    Update #6086
    
    Remove switchReader, switchWriter, switchReaderPair,
    switchWriterPair, etc.
    
    Now it only maintains pools of bufio Readers and Writers, but
    uses Reset instead of working around all their
    previously-associated state.
    
    Compared to before the bufio Reset change, it's the same number of
    allocations, and also faster:
    
    benchmark                                   old ns/op    new ns/op    delta
    BenchmarkClientServer                          111218       109828   -1.25%
    BenchmarkClientServerParallel4                  70580        70013   -0.80%
    BenchmarkClientServerParallel64                 72636        68919   -5.12%
    BenchmarkServer                                139858       137068   -1.99%
    BenchmarkServerFakeConnNoKeepAlive              14619        14314   -2.09%
    BenchmarkServerFakeConnWithKeepAlive            12390        11361   -8.31%
    BenchmarkServerFakeConnWithKeepAliveLite         7630         7306   -4.25%
    BenchmarkServerHandlerTypeLen                    9688         9342   -3.57%
    BenchmarkServerHandlerNoLen                      8700         8470   -2.64%
    BenchmarkServerHandlerNoType                     9255         8949   -3.31%
    BenchmarkServerHandlerNoHeader                   7058         6806   -3.57%
    
    benchmark                                  old allocs   new allocs    delta
    BenchmarkClientServer                              61           61    0.00%
    BenchmarkClientServerParallel4                     61           61    0.00%
    BenchmarkClientServerParallel64                    61           61    0.00%
    BenchmarkServer                                    16           16    0.00%
    BenchmarkServerFakeConnNoKeepAlive                 24           24    0.00%
    BenchmarkServerFakeConnWithKeepAlive               19           19    0.00%
    BenchmarkServerFakeConnWithKeepAliveLite            9            9    0.00%
    BenchmarkServerHandlerTypeLen                      17           17    0.00%\n    BenchmarkServerHandlerNoLen                        14           14    0.00%\n    BenchmarkServerHandlerNoType                       15           15    0.00%\n    BenchmarkServerHandlerNoHeader                      9            9    0.00%\n    \n    benchmark                                   old bytes    new bytes    delta\n    BenchmarkClientServer                            6988         6985   -0.04%\n    BenchmarkClientServerParallel4                   6979         6985    0.09%\n    BenchmarkClientServerParallel64                  7002         7019    0.24%\n    BenchmarkServer                                  1846         1848    0.11%\n    BenchmarkServerFakeConnNoKeepAlive               2420         2412   -0.33%\n    BenchmarkServerFakeConnWithKeepAlive             2126         2129    0.14%\n    BenchmarkServerFakeConnWithKeepAliveLite          989          990    0.10%\n    BenchmarkServerHandlerTypeLen                    1818         1819    0.06%\n    BenchmarkServerHandlerNoLen                      1775         1777    0.11%\n    BenchmarkServerHandlerNoType                     1783         1785    0.11%\n    BenchmarkServerHandlerNoHeader                    989          990    0.10%\n    \n    R=golang-dev, r\n    CC=golang-dev\n    https://golang.org/cl/12708046

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

https://github.com/golang/go/commit/3e38b7f2465a6ab476cb0e184f2b2abee1a6e76f

元コミット内容

このコミットの目的は、net/http パッケージのサーバー実装を簡素化し、bufio.Readerbufio.WriterReset メソッドを利用することです。これにより、switchReaderswitchWriterswitchReaderPairswitchWriterPair といったカスタム型が削除されました。変更後も bufio.Readerbufio.Writer のプールは維持されますが、以前の関連する状態を回避するために Reset メソッドが使用されます。

ベンチマーク結果によると、この変更はアロケーション数に影響を与えず、パフォーマンスが向上しています。特に BenchmarkServerFakeConnWithKeepAlive では8.31%の高速化が見られます。

変更の背景

Goの net/http パッケージは、ウェブサーバーを構築するための基盤を提供します。HTTPプロトコルはステートレスですが、TCP接続は再利用されることが多く(特にHTTP/1.1のKeep-Alive機能)、これによりパフォーマンスが向上します。サーバーは、新しいリクエストが来るたびに新しい bufio.Readerbufio.Writer を作成するのではなく、既存のものを再利用したいと考えます。

このコミット以前は、bufio.Readerbufio.Writer の基盤となる io.Readerio.Writer を動的に切り替えるために、switchReaderswitchWriter といったカスタムのラッパー型が使用されていました。これは、bufio パッケージが提供する ReaderWriter のインスタンスを再利用しつつ、異なる基盤となるI/Oストリーム(例えば、異なるHTTPリクエストのボディ)に切り替える必要があったためです。しかし、これらのカスタムラッパーはコードの複雑性を増し、管理が煩雑になる可能性がありました。

このコミットの背景には、bufio パッケージに Reset メソッドが導入されたことがあります。Reset メソッドは、既存の bufio.Readerbufio.Writer の内部状態をリセットし、新しい基盤となる io.Readerio.Writer に関連付ける機能を提供します。これにより、カスタムのラッパー型を使用することなく、bufio インスタンスの再利用が可能になり、コードの簡素化と効率化が図れるようになりました。

コミットメッセージに記載されている Update #5100Update #6086 は、この変更が解決または関連する既存の課題や改善提案を示唆していますが、具体的な内容は提供された情報からは特定できませんでした。しかし、一般的にこのような変更は、リソース管理の改善、ガベージコレクションの負荷軽減、そして全体的なパフォーマンスの向上を目的としています。

前提知識の解説

  • net/http パッケージ: Go言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。ウェブアプリケーションやAPIサーバーを構築する際に中心的な役割を果たします。
  • bufio パッケージ: バッファリングされたI/O操作を提供します。bufio.Readerio.Reader をラップしてバッファリングされた読み取りを提供し、bufio.Writerio.Writer をラップしてバッファリングされた書き込みを提供します。これにより、基盤となるI/O操作の回数を減らし、パフォーマンスを向上させます。
  • io.Readerio.Writer インターフェース: Go言語における基本的なI/Oインターフェースです。io.ReaderRead メソッドを持ち、データを読み取る能力を定義し、io.WriterWrite メソッドを持ち、データを書き込む能力を定義します。
  • Keep-Alive: HTTP/1.1で導入された機能で、単一のTCP接続を複数のHTTPリクエスト/レスポンスの送受信に再利用することを可能にします。これにより、接続の確立と切断にかかるオーバーヘッドを削減し、ウェブページのロード時間を短縮します。
  • オブジェクトプール: オブジェクトの再利用を目的としたデザインパターンです。新しいオブジェクトを頻繁に作成・破棄する代わりに、事前に作成されたオブジェクトのプールから取得し、使用後にプールに戻すことで、アロケーションとガベージコレクションのオーバーヘッドを削減します。
  • bufio.Reader.Reset(r io.Reader): bufio.Reader のメソッドで、内部バッファをリセットし、新しい io.Reader r から読み取るように設定します。これにより、既存の bufio.Reader インスタンスを再利用して、異なる入力ソースから読み取ることができます。
  • bufio.Writer.Reset(w io.Writer): bufio.Writer のメソッドで、内部バッファをリセットし、新しい io.Writer w に書き込むように設定します。これにより、既存の bufio.Writer インスタンスを再利用して、異なる出力先に書き込むことができます。

技術的詳細

このコミットの核心は、net/http パッケージのサーバーがHTTP接続を処理する際に使用する bufio.Readerbufio.Writer の管理方法の変更です。

以前のバージョンでは、conn 構造体(HTTP接続を表す)は bufio.ReadWriter の他に、その基盤となる io.Readerio.Writer を動的に切り替えるためのカスタムラッパーである switchReaderswitchWriter を保持していました。これらのラッパーは、bufio.Readerbufio.Writer が内部的に参照する io.Readerio.Writer を変更するために使用されていました。例えば、HTTPリクエストのヘッダーを読み取った後、リクエストボディを読み取るために、基盤となるリーダーを切り替える必要がありました。

このアプローチは機能的には問題ありませんでしたが、switchReaderswitchWriter といった追加の型と、それらを管理するためのロジックが必要となり、コードの複雑性を増していました。また、これらのラッパー型は、bufio.Readerbufio.Writer のプールと連携して動作するため、bufioReaderPairbufioWriterPair といった構造体も存在し、プールから取得した bufio インスタンスと、それに対応する switch インスタンスをペアで管理していました。

このコミットでは、bufio.Readerbufio.WriterReset メソッドが導入されたことを利用して、この複雑な構造を排除しています。Reset メソッドは、bufio.Readerbufio.Writer の内部状態(バッファの内容、エラー状態など)をクリアし、新しい基盤となる io.Readerio.Writer を設定します。これにより、switchReaderswitchWriter のようなカスタムラッパーは不要になります。

具体的には、以下の変更が行われました。

  1. conn 構造体からのフィールド削除: conn 構造体から bufswr (*switchReader) と bufsww (*switchWriter) フィールドが削除されました。
  2. bufioReaderPairbufioWriterPair の削除: これらのヘルパー構造体は、switchReaderswitchWriter の削除に伴い不要になったため、削除されました。
  3. キャッシュの型変更: bufioReaderCachebufioWriterCache2kbufioWriterCache4k といったキャッシュチャネルの型が、bufioReaderPairbufioWriterPair から直接 *bufio.Reader*bufio.Writer に変更されました。
  4. newBufioReadernewBufioWriterSize の簡素化: これらの関数は、プールから bufio インスタンスを取得する際に、Reset メソッドを呼び出すように変更されました。これにより、switchReaderswitchWriter を作成・設定するロジックが不要になりました。
  5. putBufioReaderputBufioWriter の簡素化: これらの関数は、bufio インスタンスをプールに戻す際に、Reset(nil) を呼び出すように変更されました。これにより、switchReaderswitchWriter の参照をクリアするロジックが不要になりました。また、putBufioWriter から、バッファにデータが残っている場合やエラーが発生している場合に再利用しないというロジックが削除されました。これは Reset メソッドがこれらの状態を適切に処理するためと考えられます。
  6. readRequest および finishRequest での呼び出し変更: response 構造体の初期化やリクエストの終了処理において、newBufioWriterSizeputBufioWriter の呼び出しから sw (switchWriter) の引数が削除されました。

この変更により、コードベースはより簡潔になり、bufio パッケージの標準機能に依存することで、将来的なメンテナンスも容易になります。

ベンチマーク結果は、この変更がパフォーマンスに良い影響を与えたことを示しています。特に ns/op (nanoseconds per operation) の値が減少しており、これは各操作にかかる時間が短縮されたことを意味します。allocs (allocations) の値は変わっておらず、これはオブジェクトプールの利用により、ヒープアロケーションの回数が維持されていることを示しています。bytes (bytes allocated) の値もほぼ変わっておらず、メモリ使用量に大きな影響がないことを示しています。

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

変更は主に src/pkg/net/http/server.go ファイルで行われています。

--- a/src/pkg/net/http/server.go
+++ b/src/pkg/net/http/server.go
@@ -110,8 +110,6 @@ type conn struct {
 	sr         liveSwitchReader     // where the LimitReader reads from; usually the rwc
 	lr         *io.LimitedReader    // io.LimitReader(sr)
 	buf        *bufio.ReadWriter    // buffered(lr,rwc), reading from bufio->limitReader->sr->rwc
-	bufswr     *switchReader        // the *switchReader io.Reader source of buf
-	bufsww     *switchWriter        // the *switchWriter io.Writer dest of buf
 	tlsState   *tls.ConnectionState // or nil when not using TLS
 
 	mu           sync.Mutex // guards the following
@@ -430,34 +428,20 @@ func (srv *Server) newConn(rwc net.Conn) (c *conn, err error) {
 	}\n \tc.sr = liveSwitchReader{r: c.rwc}\n \tc.lr = io.LimitReader(&c.sr, noLimit).(*io.LimitedReader)\n-\tbr, sr := newBufioReader(c.lr)\n-\tbw, sw := newBufioWriterSize(c.rwc, 4<<10)\n+\tbr := newBufioReader(c.lr)\n+\tbw := newBufioWriterSize(c.rwc, 4<<10)\n \tc.buf = bufio.NewReadWriter(br, bw)\n-\tc.bufswr = sr\n-\tc.bufsww = sw\n \treturn c, nil
 }\n \n-// TODO: remove this, if issue 5100 is fixed
-type bufioReaderPair struct {\n-\tbr *bufio.Reader\n-\tsr *switchReader // from which the bufio.Reader is reading\n-}\n-\n-// TODO: remove this, if issue 5100 is fixed
-type bufioWriterPair struct {\n-\tbw *bufio.Writer\n-\tsw *switchWriter // to which the bufio.Writer is writing\n-}\n-\n // TODO: use a sync.Cache instead
 var (\n-\tbufioReaderCache   = make(chan bufioReaderPair, 4)\n-\tbufioWriterCache2k = make(chan bufioWriterPair, 4)\n-\tbufioWriterCache4k = make(chan bufioWriterPair, 4)\n+\tbufioReaderCache   = make(chan *bufio.Reader, 4)\n+\tbufioWriterCache2k = make(chan *bufio.Writer, 4)\n+\tbufioWriterCache4k = make(chan *bufio.Writer, 4)\n )\n \n-func bufioWriterCache(size int) chan bufioWriterPair {\n+func bufioWriterCache(size int) chan *bufio.Writer {\n \tswitch size {\n \tcase 2 << 10:\n \t\treturn bufioWriterCache2k\n@@ -467,55 +451,38 @@ func bufioWriterCache(size int) chan bufioWriterPair {\n \treturn nil\n }\n \n-func newBufioReader(r io.Reader) (*bufio.Reader, *switchReader) {\n+func newBufioReader(r io.Reader) *bufio.Reader {\n \tselect {\n \tcase p := <-bufioReaderCache:\n-\t\tp.sr.Reader = r\n-\t\treturn p.br, p.sr\n+\t\tp.Reset(r)\n+\t\treturn p\n \tdefault:\n-\t\tsr := &switchReader{r}\n-\t\treturn bufio.NewReader(sr), sr\n+\t\treturn bufio.NewReader(r)\n \t}\n }\n \n-func putBufioReader(br *bufio.Reader, sr *switchReader) {\n-\tif n := br.Buffered(); n > 0 {\n-\t\tio.CopyN(ioutil.Discard, br, int64(n))\n-\t}\n-\tbr.Read(nil) // clears br.err\n-\tsr.Reader = nil\n+func putBufioReader(br *bufio.Reader) {\n+\tbr.Reset(nil)\n \tselect {\n-\tcase bufioReaderCache <- bufioReaderPair{br, sr}:\n+\tcase bufioReaderCache <- br:\n \tdefault:\n \t}\n }\n \n-func newBufioWriterSize(w io.Writer, size int) (*bufio.Writer, *switchWriter) {\n+func newBufioWriterSize(w io.Writer, size int) *bufio.Writer {\n \tselect {\n \tcase p := <-bufioWriterCache(size):\n-\t\tp.sw.Writer = w\n-\t\treturn p.bw, p.sw\n+\t\tp.Reset(w)\n+\t\treturn p\n \tdefault:\n-\t\tsw := &switchWriter{w}\n-\t\treturn bufio.NewWriterSize(sw, size), sw\n+\t\treturn bufio.NewWriterSize(w, size)\n \t}\n }\n \n-func putBufioWriter(bw *bufio.Writer, sw *switchWriter) {\n-\tif bw.Buffered() > 0 {\n-\t\t// It must have failed to flush to its target\n-\t\t// earlier. We can't reuse this bufio.Writer.\n-\t\treturn\n-\t}\n-\tif err := bw.Flush(); err != nil {\n-\t\t// Its sticky error field is set, which is returned by\n-\t\t// Flush even when there's no data buffered.  This\n-\t\t// bufio Writer is dead to us.  Don't reuse it.\n-\t\treturn\n-\t}\n-\tsw.Writer = nil\n+func putBufioWriter(bw *bufio.Writer) {\n+\tbw.Reset(nil)\n \tselect {\n-\tcase bufioWriterCache(bw.Available()) <- bufioWriterPair{bw, sw}:\n+\tcase bufioWriterCache(bw.Available()) <- bw:\n \tdefault:\n \t}\n }\n@@ -621,7 +588,7 @@ func (c *conn) readRequest() (w *response, err error) {\n \t\tcontentLength: -1,\n \t}\n \tw.cw.res = w\n-\tw.w, w.sw = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize)\n+\tw.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize)\n \treturn w, nil\n }\n \n@@ -1007,7 +974,7 @@ func (w *response) finishRequest() {\n \t}\n \n \tw.w.Flush()\n-\tputBufioWriter(w.w, w.sw)\n+\tputBufioWriter(w.w)\n \tw.cw.close()\n \tw.conn.buf.Flush()\n \n@@ -1040,11 +1007,11 @@ func (c *conn) finalFlush() {\n \n \t\t// Steal the bufio.Reader (~4KB worth of memory) and its associated\n \t\t// reader for a future connection.\n-\t\tputBufioReader(c.buf.Reader, c.bufswr)\n+\t\tputBufioReader(c.buf.Reader)\n \n \t\t// Steal the bufio.Writer (~4KB worth of memory) and its associated\n \t\t// writer for a future connection.\n-\t\tputBufioWriter(c.buf.Writer, c.bufsww)\n+\t\tputBufioWriter(c.buf.Writer)\n \n \t\tc.buf = nil\n \t}\n```

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

このコミットの主要な変更点は、`net/http` パッケージの `server.go` における `bufio.Reader` と `bufio.Writer` の管理方法の根本的な見直しです。

1.  **`conn` 構造体の簡素化**:
    *   以前は `conn` 構造体に `bufswr` (`*switchReader`) と `bufsww` (`*switchWriter`) というフィールドがありました。これらは `bufio.Reader` と `bufio.Writer` が内部的に使用する `io.Reader` と `io.Writer` を動的に切り替えるためのカスタムラッパーでした。
    *   このコミットでは、これらのフィールドが削除されました。これは、`bufio.Reader` と `bufio.Writer` の `Reset` メソッドが導入されたことで、これらのカスタムラッパーが不要になったためです。

2.  **`bufioReaderPair` と `bufioWriterPair` の削除**:
    *   以前は、`bufio.Reader` や `bufio.Writer` をオブジェクトプールで管理する際に、それらと対応する `switchReader` や `switchWriter` をペアで扱うために `bufioReaderPair` と `bufioWriterPair` というヘルパー構造体が定義されていました。
    *   `switchReader` と `switchWriter` が不要になったため、これらのペア構造体も削除されました。

3.  **キャッシュチャネルの型変更**:
    *   `bufioReaderCache`、`bufioWriterCache2k`、`bufioWriterCache4k` といった `bufio` インスタンスを再利用するためのチャネルの型が、`bufioReaderPair` や `bufioWriterPair` から直接 `*bufio.Reader` や `*bufio.Writer` に変更されました。これにより、キャッシュの管理がより直接的になりました。

4.  **`newBufioReader` と `newBufioWriterSize` 関数の変更**:
    *   これらの関数は、新しい `bufio.Reader` や `bufio.Writer` インスタンスを取得する(またはプールから再利用する)役割を担っています。
    *   変更前は、プールから取得した `bufio` インスタンスに対して、対応する `switchReader` や `switchWriter` の `Reader` や `Writer` フィールドを新しい基盤となる `io.Reader` や `io.Writer` に設定していました。
    *   変更後は、プールから取得した `bufio` インスタンスに対して、直接 `p.Reset(r)` や `p.Reset(w)` を呼び出すようになりました。これにより、`bufio` インスタンスの内部状態がリセットされ、新しいI/Oソースに関連付けられます。

5.  **`putBufioReader` と `putBufioWriter` 関数の変更**:
    *   これらの関数は、使用済みの `bufio` インスタンスをプールに戻す役割を担っています。
    *   変更前は、プールに戻す前に `switchReader` や `switchWriter` の `Reader` や `Writer` フィールドを `nil` に設定していました。また、`putBufioWriter` では、バッファにデータが残っていたり、エラーが発生していたりする場合には再利用しないというロジックがありました。
    *   変更後は、`br.Reset(nil)` や `bw.Reset(nil)` を呼び出すことで、`bufio` インスタンスの内部状態をクリーンアップし、プールに戻す準備をします。`Reset(nil)` は、バッファをクリアし、基盤となるリーダー/ライターへの参照を解除します。これにより、以前の複雑なチェックやクリアロジックが不要になりました。

これらの変更により、`net/http` サーバーは `bufio` パッケージのより新しい、より効率的な機能を利用できるようになり、コードの簡素化とパフォーマンスの向上が実現されました。特に、`Reset` メソッドの利用は、オブジェクトプールの効率を最大化し、ガベージコレクションの負担を軽減する上で重要な役割を果たします。

## 関連リンク

*   Go言語の `net/http` パッケージ: [https://pkg.go.dev/net/http](https://pkg.go.dev/net/http)
*   Go言語の `bufio` パッケージ: [https://pkg.go.dev/bufio](https://pkg.go.dev/bufio)
*   Go言語の `io` パッケージ: [https://pkg.go.dev/io](https://pkg.go.dev/io)

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

*   Go言語の公式ドキュメント
*   Go言語のソースコード
*   `bufio.Reader.Reset` および `bufio.Writer.Reset` のドキュメント
*   Go言語のベンチマークに関する一般的な情報