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

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

このコミットは、Go言語の標準ライブラリである net/http パッケージ内の server.go ファイルに対する変更です。具体的には、io.WriteString の使用箇所を、より直接的な buf.WriteString に置き換えることで、パフォーマンスの最適化を図っています。

コミット

commit c6c439d7a0be6bfb91348d8a76b09ffd173688fa
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon May 20 19:26:26 2013 -0700

    net/http: use WriteString directly when possible

    Several places used io.WriteString unnecessarily when the
    static type already implemented WriteString. No need to
    check for it at runtime.

    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/9608043

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

https://github.com/golang/go/commit/c6c439d7a0be6bfb91348d8a76b09ffd173688fa

元コミット内容

net/http: use WriteString directly when possible

このコミットの目的は、io.WriteString を使用していたいくつかの箇所で、静的型が既に WriteString メソッドを実装している場合に、io.WriteString を介さずに直接その WriteString メソッドを呼び出すように変更することです。これにより、実行時の型チェックが不要になり、効率が向上します。

変更の背景

Go言語の io パッケージには、WriteString というヘルパー関数があります。この関数は、io.Writer インターフェースを実装する任意の型に対して文字列を書き込むために使用されます。io.WriteString の内部では、渡された io.Writerio.StringWriter インターフェース(WriteString(s string) (n int, err error) メソッドを持つインターフェース)も実装しているかどうかをチェックし、もし実装していればその WriteString メソッドを直接呼び出します。実装していなければ、io.WriterWrite([]byte) メソッドを使って文字列をバイトスライスに変換してから書き込みます。

このコミットが行われた背景には、net/http パッケージ内の特定のコードパスにおいて、io.WriteString に渡されるレシーバー(この場合は cw.res.conn.bufecr.resp.conn.bufw.conn.buf)が、既に WriteString メソッドを直接持っている bytes.Buffer のような具体的な型であったという状況があります。

io.WriteString を使用すると、内部でインターフェースアサーション(型アサーション)や型スイッチングが行われ、io.StringWriter インターフェースの実装チェックが行われます。これはわずかながらオーバーヘッドを発生させます。しかし、コードの静的解析によって、渡される型が確実に WriteString メソッドを持っていることが分かっている場合、この実行時のチェックは不要になります。

したがって、この変更は、不要な実行時チェックを回避し、より直接的なメソッド呼び出しに切り替えることで、HTTPサーバーのパフォーマンスを微細ながらも向上させることを目的としています。特に、HTTPリクエストの処理パスのような頻繁に実行されるコードでは、このような小さな最適化が全体のスループットに寄与する可能性があります。

前提知識の解説

  1. Go言語のインターフェース: Go言語のインターフェースは、メソッドのシグネチャの集合を定義します。型がインターフェースのすべてのメソッドを実装していれば、その型はそのインターフェースを「実装している」と見なされます。

    • io.Writer インターフェース: Write(p []byte) (n int, err error) メソッドを定義します。バイトスライスを書き込むための基本的なインターフェースです。
    • io.StringWriter インターフェース: WriteString(s string) (n int, err error) メソッドを定義します。文字列を直接書き込むためのインターフェースです。
  2. io.WriteString 関数: func WriteString(w Writer, s string) (n int, err error) この関数は、io.Writer インターフェースを実装する w に対して文字列 s を書き込みます。内部的には、wio.StringWriter インターフェースも実装しているかをチェックし、もし実装していれば w.WriteString(s) を直接呼び出します。実装していなければ、s をバイトスライスに変換してから w.Write([]byte(s)) を呼び出します。

  3. bytes.Buffer: bytes.Buffer は、可変長のバイトバッファを実装する型です。これは io.Writer および io.StringWriter インターフェースの両方を実装しています。bytes.BufferWriteString メソッドは、文字列を直接バッファに効率的に追加します。

  4. 静的型と動的型: Go言語は静的型付け言語ですが、インターフェースを使用することで動的な振る舞いを実現します。

    • 静的型: コンパイル時に決定される変数の型です。例えば、var buf bytes.Buffer の場合、buf の静的型は bytes.Buffer です。
    • 動的型: 実行時に変数が保持する具体的な値の型です。例えば、var w io.Writer = &bytes.Buffer{} の場合、w の静的型は io.Writer ですが、動的型は *bytes.Buffer です。 io.WriteString は、引数 w の静的型が io.Writer であっても、その動的型が io.StringWriter を実装しているかを実行時にチェックします。

このコミットの変更は、コード内で使用されているバッファ(cw.res.conn.buf など)の静的型が既に bytes.Buffer のような WriteString メソッドを持つ具体的な型であることが分かっているため、io.WriteString を介した間接的な呼び出しを避け、直接 buf.WriteString を呼び出すことで、実行時の不要なインターフェースチェックを省略し、パフォーマンスを向上させるという考えに基づいています。

技術的詳細

このコミットは、Goの net/http パッケージ内のHTTPサーバー実装において、文字列の書き込み処理を最適化するものです。具体的には、io.WriteString ヘルパー関数の使用を、基盤となるバッファオブジェクトが直接提供する WriteString メソッドの呼び出しに置き換えています。

変更の対象となっているのは、主に以下の3つの箇所です。

  1. チャンクエンコーディングの終了処理 (chunkWriter.close): HTTP/1.1のチャンク転送エンコーディングでは、メッセージの終端を示すために「0\r\n\r\n」というゼロ長のチャンクを送信します。以前は io.WriteString(cw.res.conn.buf, "0\r\n\r\n") を使用していましたが、cw.res.conn.bufbytes.Buffer 型(またはそれに準ずる WriteString メソッドを持つ型)であることが静的に分かっているため、cw.res.conn.buf.WriteString("0\r\n\r\n") に変更されました。

  2. Expect: 100-continue レスポンスの送信 (expectContinueReader.Read): HTTP/1.1の Expect: 100-continue ヘッダは、クライアントが大きなリクエストボディを送信する前に、サーバーがリクエストを受け入れる準備ができているかを確認するために使用されます。サーバーは 100 Continue レスポンスを返して、クライアントにボディの送信を続行するよう指示します。以前は io.WriteString(ecr.resp.conn.buf, "HTTP/1.1 100 Continue\r\n\r\n") を使用していましたが、これも同様に ecr.resp.conn.buf.WriteString("HTTP/1.1 100 Continue\r\n\r\n") に変更されました。

  3. HTTPレスポンスヘッダの書き込み (chunkWriter.writeHeader): HTTPレスポンスのステータスライン(例: HTTP/1.1 200 OK)を書き込む際にも、以前は io.WriteString(w.conn.buf, statusLine(w.req, code)) を使用していました。これも w.conn.buf.WriteString(statusLine(w.req, code)) に変更されました。

これらの変更は、cw.res.conn.bufecr.resp.conn.bufw.conn.buf といったバッファが、内部的に bytes.Buffer のような WriteString メソッドを直接提供する具体的な型であることを前提としています。bytes.Bufferio.Writerio.StringWriter の両方を実装しているため、io.WriteString を介しても最終的には bytes.BufferWriteString メソッドが呼び出されます。しかし、io.WriteString を使用すると、その呼び出しパスの途中で io.StringWriter インターフェースへの型アサーション(または型スイッチ)のオーバーヘッドが発生します。

このコミットは、そのオーバーヘッドを回避し、コンパイル時に型が確定している場合に直接 WriteString メソッドを呼び出すことで、わずかながらも実行効率を向上させる「マイクロ最適化」の一種です。このような最適化は、特に高負荷なサーバーアプリケーションにおいて、累積的なパフォーマンス向上に繋がる可能性があります。

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

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

--- a/src/pkg/net/http/server.go
+++ b/src/pkg/net/http/server.go
@@ -278,7 +278,7 @@ func (cw *chunkWriter) close() {
 		// zero EOF chunk, trailer key/value pairs (currently
 		// unsupported in Go's server), followed by a blank
 		// line.
-		io.WriteString(cw.res.conn.buf, "0\r\n\r\n")
+		cw.res.conn.buf.WriteString("0\r\n\r\n")
 	}
 }
 
@@ -512,7 +512,7 @@ func (ecr *expectContinueReader) Read(p []byte) (n int, err error) {
 	}
 	if !ecr.resp.wroteContinue && !ecr.resp.conn.hijacked() {
 		ecr.resp.wroteContinue = true
-		io.WriteString(ecr.resp.conn.buf, "HTTP/1.1 100 Continue\r\n\r\n")
+		ecr.resp.conn.buf.WriteString("HTTP/1.1 100 Continue\r\n\r\n")
 		ecr.resp.conn.buf.Flush()
 	}
 	return ecr.readCloser.Read(p)
@@ -847,7 +847,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
 		setHeader.connection = "close"
 	}
 
-	io.WriteString(w.conn.buf, statusLine(w.req, code))
+	w.conn.buf.WriteString(statusLine(w.req, code))
 	cw.header.WriteSubset(w.conn.buf, excludeHeader)
 	setHeader.Write(w.conn.buf.Writer)
 	w.conn.buf.Write(crlf)

コアとなるコードの解説

上記の差分が示すように、すべての変更は io.WriteString(buffer, string) の形式の呼び出しを buffer.WriteString(string) の形式に置き換えるものです。

  • 変更前: io.WriteString(cw.res.conn.buf, "0\r\n\r\n")

    • io.WriteString 関数は、第一引数に io.Writer インターフェースを受け取ります。cw.res.conn.bufio.Writer を実装していますが、その具体的な型は bytes.Buffer のような WriteString メソッドを持つ型です。
    • io.WriteString の内部では、cw.res.conn.bufio.StringWriter インターフェースも実装しているかどうかのチェックが行われます。このチェックは実行時に行われるため、わずかなオーバーヘッドが発生します。
  • 変更後: cw.res.conn.buf.WriteString("0\r\n\r\n")

    • cw.res.conn.bufbytes.Buffer のような具体的な型であり、かつその型が WriteString メソッドを直接持っていることがコンパイル時に分かっているため、直接そのメソッドを呼び出します。
    • これにより、io.WriteString を介した呼び出しで発生していた実行時のインターフェースチェックが不要になり、直接メソッドが呼び出されるため、より効率的になります。

この変更は、Go言語におけるインターフェースの利用と、具体的な型のメソッド呼び出しの間のパフォーマンス特性を理解していることを示しています。コンパイル時に具体的な型が判明しており、その型が目的のメソッドを直接提供している場合は、インターフェースを介した汎用的なヘルパー関数よりも、直接メソッドを呼び出す方が効率的であるという原則に基づいています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特に io および bytes パッケージ)
  • Go言語のパフォーマンス最適化に関する一般的な記事や議論 (例: io.WriteStringWrite の比較など)
  • Go言語のコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/9608043 (コミットメッセージに記載されているリンク)
  • GitHubのコミットページ: https://github.com/golang/go/commit/c6c439d7a0be6bfb91348d8a76b09ffd173688fa
  • Go言語のインターフェースの内部動作に関する技術記事 (例: インターフェース値の構造、型アサーションのコストなど)# [インデックス 16356] ファイルの概要

このコミットは、Go言語の標準ライブラリである net/http パッケージ内の server.go ファイルに対する変更です。具体的には、io.WriteString の使用箇所を、より直接的な buf.WriteString に置き換えることで、パフォーマンスの最適化を図っています。

コミット

commit c6c439d7a0be6bfb91348d8a76b09ffd173688fa
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon May 20 19:26:26 2013 -0700

    net/http: use WriteString directly when possible

    Several places used io.WriteString unnecessarily when the
    static type already implemented WriteString. No need to
    check for it at runtime.

    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/9608043

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

https://github.com/golang/go/commit/c6c439d7a0be6bfb91348d8a76b09ffd173688fa

元コミット内容

net/http: use WriteString directly when possible

このコミットの目的は、io.WriteString を使用していたいくつかの箇所で、静的型が既に WriteString メソッドを実装している場合に、io.WriteString を介さずに直接その WriteString メソッドを呼び出すように変更することです。これにより、実行時の型チェックが不要になり、効率が向上します。

変更の背景

Go言語の io パッケージには、WriteString というヘルパー関数があります。この関数は、io.Writer インターフェースを実装する任意の型に対して文字列を書き込むために使用されます。io.WriteString の内部では、渡された io.Writerio.StringWriter インターフェース(WriteString(s string) (n int, err error) メソッドを持つインターフェース)も実装しているかどうかをチェックし、もし実装していればその WriteString メソッドを直接呼び出します。実装していなければ、io.WriterWrite([]byte) メソッドを使って文字列をバイトスライスに変換してから書き込みます。

このコミットが行われた背景には、net/http パッケージ内の特定のコードパスにおいて、io.WriteString に渡されるレシーバー(この場合は cw.res.conn.bufecr.resp.conn.bufw.conn.buf)が、既に WriteString メソッドを直接持っている bytes.Buffer のような具体的な型であったという状況があります。

io.WriteString を使用すると、内部でインターフェースアサーション(型アサーション)や型スイッチングが行われ、io.StringWriter インターフェースの実装チェックが行われます。これはわずかながらオーバーヘッドを発生させます。しかし、コードの静的解析によって、渡される型が確実に WriteString メソッドを持っていることが分かっている場合、この実行時のチェックは不要になります。

したがって、この変更は、不要な実行時チェックを回避し、より直接的なメソッド呼び出しに切り替えることで、HTTPサーバーのパフォーマンスを微細ながらも向上させることを目的としています。特に、HTTPリクエストの処理パスのような頻繁に実行されるコードでは、このような小さな最適化が全体のスループットに寄与する可能性があります。

前提知識の解説

  1. Go言語のインターフェース: Go言語のインターフェースは、メソッドのシグネチャの集合を定義します。型がインターフェースのすべてのメソッドを実装していれば、その型はそのインターフェースを「実装している」と見なされます。

    • io.Writer インターフェース: Write(p []byte) (n int, err error) メソッドを定義します。バイトスライスを書き込むための基本的なインターフェースです。
    • io.StringWriter インターフェース: WriteString(s string) (n int, err error) メソッドを定義します。文字列を直接書き込むためのインターフェースです。
  2. io.WriteString 関数: func WriteString(w Writer, s string) (n int, err error) この関数は、io.Writer インターフェースを実装する w に対して文字列 s を書き込みます。内部的には、wio.StringWriter インターフェースも実装しているかをチェックし、もし実装していれば w.WriteString(s) を直接呼び出します。実装していなければ、s をバイトスライスに変換してから w.Write([]byte(s)) を呼び出します。

  3. bytes.Buffer: bytes.Buffer は、可変長のバイトバッファを実装する型です。これは io.Writer および io.StringWriter インターフェースの両方を実装しています。bytes.BufferWriteString メソッドは、文字列を直接バッファに効率的に追加します。

  4. 静的型と動的型: Go言語は静的型付け言語ですが、インターフェースを使用することで動的な振る舞いを実現します。

    • 静的型: コンパイル時に決定される変数の型です。例えば、var buf bytes.Buffer の場合、buf の静的型は bytes.Buffer です。
    • 動的型: 実行時に変数が保持する具体的な値の型です。例えば、var w io.Writer = &bytes.Buffer{} の場合、w の静的型は io.Writer ですが、動的型は *bytes.Buffer です。 io.WriteString は、引数 w の静的型が io.Writer であっても、その動的型が io.StringWriter を実装しているかを実行時にチェックします。

このコミットの変更は、コード内で使用されているバッファ(cw.res.conn.buf など)の静的型が既に bytes.Buffer のような WriteString メソッドを持つ具体的な型であることが分かっているため、io.WriteString を介した間接的な呼び出しを避け、直接 buf.WriteString を呼び出すことで、実行時の不要なインターフェースチェックを省略し、パフォーマンスを向上させるという考えに基づいています。

技術的詳細

このコミットは、Goの net/http パッケージ内のHTTPサーバー実装において、文字列の書き込み処理を最適化するものです。具体的には、io.WriteString ヘルパー関数の使用を、基盤となるバッファオブジェクトが直接提供する WriteString メソッドの呼び出しに置き換えています。

変更の対象となっているのは、主に以下の3つの箇所です。

  1. チャンクエンコーディングの終了処理 (chunkWriter.close): HTTP/1.1のチャンク転送エンコーディングでは、メッセージの終端を示すために「0\r\n\r\n」というゼロ長のチャンクを送信します。以前は io.WriteString(cw.res.conn.buf, "0\r\n\r\n") を使用していましたが、cw.res.conn.bufbytes.Buffer 型(またはそれに準ずる WriteString メソッドを持つ型)であることが静的に分かっているため、cw.res.conn.buf.WriteString("0\r\n\r\n") に変更されました。

  2. Expect: 100-continue レスポンスの送信 (expectContinueReader.Read): HTTP/1.1の Expect: 100-continue ヘッダは、クライアントが大きなリクエストボディを送信する前に、サーバーがリクエストを受け入れる準備ができているかを確認するために使用されます。サーバーは 100 Continue レスポンスを返して、クライアントにボディの送信を続行するよう指示します。以前は io.WriteString(ecr.resp.conn.buf, "HTTP/1.1 100 Continue\r\n\r\n") を使用していましたが、これも同様に ecr.resp.conn.buf.WriteString("HTTP/1.1 100 Continue\r\n\r\n") に変更されました。

  3. HTTPレスポンスヘッダの書き込み (chunkWriter.writeHeader): HTTPレスポンスのステータスライン(例: HTTP/1.1 200 OK)を書き込む際にも、以前は io.WriteString(w.conn.buf, statusLine(w.req, code)) を使用していました。これも w.conn.buf.WriteString(statusLine(w.req, code)) に変更されました。

これらの変更は、cw.res.conn.bufecr.resp.conn.bufw.conn.buf といったバッファが、内部的に bytes.Buffer のような WriteString メソッドを直接提供する具体的な型であることを前提としています。bytes.Bufferio.Writerio.StringWriter の両方を実装しているため、io.WriteString を介しても最終的には bytes.BufferWriteString メソッドが呼び出されます。しかし、io.WriteString を使用すると、その呼び出しパスの途中で io.StringWriter インターフェースへの型アサーション(または型スイッチ)のオーバーヘッドが発生します。

このコミットは、そのオーバーヘッドを回避し、コンパイル時に型が確定している場合に直接 WriteString メソッドを呼び出すことで、わずかながらも実行効率を向上させる「マイクロ最適化」の一種です。このような最適化は、特に高負荷なサーバーアプリケーションにおいて、累積的なパフォーマンス向上に繋がる可能性があります。

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

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

--- a/src/pkg/net/http/server.go
+++ b/src/pkg/net/http/server.go
@@ -278,7 +278,7 @@ func (cw *chunkWriter) close() {
 		// zero EOF chunk, trailer key/value pairs (currently
 		// unsupported in Go's server), followed by a blank
 		// line.
-		io.WriteString(cw.res.conn.buf, "0\r\n\r\n")
+		cw.res.conn.buf.WriteString("0\r\n\r\n")
 	}
 }
 
@@ -512,7 +512,7 @@ func (ecr *expectContinueReader) Read(p []byte) (n int, err error) {
 	}
 	if !ecr.resp.wroteContinue && !ecr.resp.conn.hijacked() {
 		ecr.resp.wroteContinue = true
-		io.WriteString(ecr.resp.conn.buf, "HTTP/1.1 100 Continue\r\n\r\n")
+		ecr.resp.conn.buf.WriteString("HTTP/1.1 100 Continue\r\n\r\n")
 		ecr.resp.conn.buf.Flush()
 	}
 	return ecr.readCloser.Read(p)
@@ -847,7 +847,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
 		setHeader.connection = "close"
 	}
 
-	io.WriteString(w.conn.buf, statusLine(w.req, code))
+	w.conn.buf.WriteString(statusLine(w.req, code))
 	cw.header.WriteSubset(w.conn.buf, excludeHeader)
 	setHeader.Write(w.conn.buf.Writer)
 	w.conn.buf.Write(crlf)

コアとなるコードの解説

上記の差分が示すように、すべての変更は io.WriteString(buffer, string) の形式の呼び出しを buffer.WriteString(string) の形式に置き換えるものです。

  • 変更前: io.WriteString(cw.res.conn.buf, "0\r\n\r\n")

    • io.WriteString 関数は、第一引数に io.Writer インターフェースを受け取ります。cw.res.conn.bufio.Writer を実装していますが、その具体的な型は bytes.Buffer のような WriteString メソッドを持つ型です。
    • io.WriteString の内部では、cw.res.conn.bufio.StringWriter インターフェースも実装しているかどうかのチェックが行われます。このチェックは実行時に行われるため、わずかなオーバーヘッドが発生します。
  • 変更後: cw.res.conn.buf.WriteString("0\r\n\r\n")

    • cw.res.conn.bufbytes.Buffer のような具体的な型であり、かつその型が WriteString メソッドを直接持っていることがコンパイル時に分かっているため、直接そのメソッドを呼び出します。
    • これにより、io.WriteString を介した呼び出しで発生していた実行時のインターフェースチェックが不要になり、直接メソッドが呼び出されるため、より効率的になります。

この変更は、Go言語におけるインターフェースの利用と、具体的な型のメソッド呼び出しの間のパフォーマンス特性を理解していることを示しています。コンパイル時に具体的な型が判明しており、その型が目的のメソッドを直接提供している場合は、インターフェースを介した汎用的なヘルパー関数よりも、直接メソッドを呼び出す方が効率的であるという原則に基づいています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特に io および bytes パッケージ)
  • Go言語のパフォーマンス最適化に関する一般的な記事や議論 (例: io.WriteStringWrite の比較など)
  • Go言語のコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/9608043 (コミットメッセージに記載されているリンク)
  • GitHubのコミットページ: https://github.com/golang/go/commit/c6c439d7a0be6bfb91348d8a76b09ffd173688fa
  • Go言語のインターフェースの内部動作に関する技術記事 (例: インターフェース値の構造、型アサーションのコストなど)