[インデックス 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.Writer
が io.StringWriter
インターフェース(WriteString(s string) (n int, err error)
メソッドを持つインターフェース)も実装しているかどうかをチェックし、もし実装していればその WriteString
メソッドを直接呼び出します。実装していなければ、io.Writer
の Write([]byte)
メソッドを使って文字列をバイトスライスに変換してから書き込みます。
このコミットが行われた背景には、net/http
パッケージ内の特定のコードパスにおいて、io.WriteString
に渡されるレシーバー(この場合は cw.res.conn.buf
や ecr.resp.conn.buf
、w.conn.buf
)が、既に WriteString
メソッドを直接持っている bytes.Buffer
のような具体的な型であったという状況があります。
io.WriteString
を使用すると、内部でインターフェースアサーション(型アサーション)や型スイッチングが行われ、io.StringWriter
インターフェースの実装チェックが行われます。これはわずかながらオーバーヘッドを発生させます。しかし、コードの静的解析によって、渡される型が確実に WriteString
メソッドを持っていることが分かっている場合、この実行時のチェックは不要になります。
したがって、この変更は、不要な実行時チェックを回避し、より直接的なメソッド呼び出しに切り替えることで、HTTPサーバーのパフォーマンスを微細ながらも向上させることを目的としています。特に、HTTPリクエストの処理パスのような頻繁に実行されるコードでは、このような小さな最適化が全体のスループットに寄与する可能性があります。
前提知識の解説
-
Go言語のインターフェース: Go言語のインターフェースは、メソッドのシグネチャの集合を定義します。型がインターフェースのすべてのメソッドを実装していれば、その型はそのインターフェースを「実装している」と見なされます。
io.Writer
インターフェース:Write(p []byte) (n int, err error)
メソッドを定義します。バイトスライスを書き込むための基本的なインターフェースです。io.StringWriter
インターフェース:WriteString(s string) (n int, err error)
メソッドを定義します。文字列を直接書き込むためのインターフェースです。
-
io.WriteString
関数:func WriteString(w Writer, s string) (n int, err error)
この関数は、io.Writer
インターフェースを実装するw
に対して文字列s
を書き込みます。内部的には、w
がio.StringWriter
インターフェースも実装しているかをチェックし、もし実装していればw.WriteString(s)
を直接呼び出します。実装していなければ、s
をバイトスライスに変換してからw.Write([]byte(s))
を呼び出します。 -
bytes.Buffer
型:bytes.Buffer
は、可変長のバイトバッファを実装する型です。これはio.Writer
およびio.StringWriter
インターフェースの両方を実装しています。bytes.Buffer
のWriteString
メソッドは、文字列を直接バッファに効率的に追加します。 -
静的型と動的型: 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つの箇所です。
-
チャンクエンコーディングの終了処理 (
chunkWriter.close
): HTTP/1.1のチャンク転送エンコーディングでは、メッセージの終端を示すために「0\r\n\r\n」というゼロ長のチャンクを送信します。以前はio.WriteString(cw.res.conn.buf, "0\r\n\r\n")
を使用していましたが、cw.res.conn.buf
はbytes.Buffer
型(またはそれに準ずるWriteString
メソッドを持つ型)であることが静的に分かっているため、cw.res.conn.buf.WriteString("0\r\n\r\n")
に変更されました。 -
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")
に変更されました。 -
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.buf
、ecr.resp.conn.buf
、w.conn.buf
といったバッファが、内部的に bytes.Buffer
のような WriteString
メソッドを直接提供する具体的な型であることを前提としています。bytes.Buffer
は io.Writer
と io.StringWriter
の両方を実装しているため、io.WriteString
を介しても最終的には bytes.Buffer
の WriteString
メソッドが呼び出されます。しかし、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.buf
はio.Writer
を実装していますが、その具体的な型はbytes.Buffer
のようなWriteString
メソッドを持つ型です。io.WriteString
の内部では、cw.res.conn.buf
がio.StringWriter
インターフェースも実装しているかどうかのチェックが行われます。このチェックは実行時に行われるため、わずかなオーバーヘッドが発生します。
-
変更後:
cw.res.conn.buf.WriteString("0\r\n\r\n")
cw.res.conn.buf
がbytes.Buffer
のような具体的な型であり、かつその型がWriteString
メソッドを直接持っていることがコンパイル時に分かっているため、直接そのメソッドを呼び出します。- これにより、
io.WriteString
を介した呼び出しで発生していた実行時のインターフェースチェックが不要になり、直接メソッドが呼び出されるため、より効率的になります。
この変更は、Go言語におけるインターフェースの利用と、具体的な型のメソッド呼び出しの間のパフォーマンス特性を理解していることを示しています。コンパイル時に具体的な型が判明しており、その型が目的のメソッドを直接提供している場合は、インターフェースを介した汎用的なヘルパー関数よりも、直接メソッドを呼び出す方が効率的であるという原則に基づいています。
関連リンク
- Go言語
io
パッケージのドキュメント: https://pkg.go.dev/io - Go言語
bytes
パッケージのドキュメント: https://pkg.go.dev/bytes - Go言語
net/http
パッケージのドキュメント: https://pkg.go.dev/net/http - Go言語のインターフェースに関する公式ブログ記事やチュートリアル (一般的な情報源)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード (特に
io
およびbytes
パッケージ) - Go言語のパフォーマンス最適化に関する一般的な記事や議論 (例:
io.WriteString
とWrite
の比較など) - 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.Writer
が io.StringWriter
インターフェース(WriteString(s string) (n int, err error)
メソッドを持つインターフェース)も実装しているかどうかをチェックし、もし実装していればその WriteString
メソッドを直接呼び出します。実装していなければ、io.Writer
の Write([]byte)
メソッドを使って文字列をバイトスライスに変換してから書き込みます。
このコミットが行われた背景には、net/http
パッケージ内の特定のコードパスにおいて、io.WriteString
に渡されるレシーバー(この場合は cw.res.conn.buf
や ecr.resp.conn.buf
、w.conn.buf
)が、既に WriteString
メソッドを直接持っている bytes.Buffer
のような具体的な型であったという状況があります。
io.WriteString
を使用すると、内部でインターフェースアサーション(型アサーション)や型スイッチングが行われ、io.StringWriter
インターフェースの実装チェックが行われます。これはわずかながらオーバーヘッドを発生させます。しかし、コードの静的解析によって、渡される型が確実に WriteString
メソッドを持っていることが分かっている場合、この実行時のチェックは不要になります。
したがって、この変更は、不要な実行時チェックを回避し、より直接的なメソッド呼び出しに切り替えることで、HTTPサーバーのパフォーマンスを微細ながらも向上させることを目的としています。特に、HTTPリクエストの処理パスのような頻繁に実行されるコードでは、このような小さな最適化が全体のスループットに寄与する可能性があります。
前提知識の解説
-
Go言語のインターフェース: Go言語のインターフェースは、メソッドのシグネチャの集合を定義します。型がインターフェースのすべてのメソッドを実装していれば、その型はそのインターフェースを「実装している」と見なされます。
io.Writer
インターフェース:Write(p []byte) (n int, err error)
メソッドを定義します。バイトスライスを書き込むための基本的なインターフェースです。io.StringWriter
インターフェース:WriteString(s string) (n int, err error)
メソッドを定義します。文字列を直接書き込むためのインターフェースです。
-
io.WriteString
関数:func WriteString(w Writer, s string) (n int, err error)
この関数は、io.Writer
インターフェースを実装するw
に対して文字列s
を書き込みます。内部的には、w
がio.StringWriter
インターフェースも実装しているかをチェックし、もし実装していればw.WriteString(s)
を直接呼び出します。実装していなければ、s
をバイトスライスに変換してからw.Write([]byte(s))
を呼び出します。 -
bytes.Buffer
型:bytes.Buffer
は、可変長のバイトバッファを実装する型です。これはio.Writer
およびio.StringWriter
インターフェースの両方を実装しています。bytes.Buffer
のWriteString
メソッドは、文字列を直接バッファに効率的に追加します。 -
静的型と動的型: 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つの箇所です。
-
チャンクエンコーディングの終了処理 (
chunkWriter.close
): HTTP/1.1のチャンク転送エンコーディングでは、メッセージの終端を示すために「0\r\n\r\n」というゼロ長のチャンクを送信します。以前はio.WriteString(cw.res.conn.buf, "0\r\n\r\n")
を使用していましたが、cw.res.conn.buf
はbytes.Buffer
型(またはそれに準ずるWriteString
メソッドを持つ型)であることが静的に分かっているため、cw.res.conn.buf.WriteString("0\r\n\r\n")
に変更されました。 -
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")
に変更されました。 -
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.buf
、ecr.resp.conn.buf
、w.conn.buf
といったバッファが、内部的に bytes.Buffer
のような WriteString
メソッドを直接提供する具体的な型であることを前提としています。bytes.Buffer
は io.Writer
と io.StringWriter
の両方を実装しているため、io.WriteString
を介しても最終的には bytes.Buffer
の WriteString
メソッドが呼び出されます。しかし、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.buf
はio.Writer
を実装していますが、その具体的な型はbytes.Buffer
のようなWriteString
メソッドを持つ型です。io.WriteString
の内部では、cw.res.conn.buf
がio.StringWriter
インターフェースも実装しているかどうかのチェックが行われます。このチェックは実行時に行われるため、わずかなオーバーヘッドが発生します。
-
変更後:
cw.res.conn.buf.WriteString("0\r\n\r\n")
cw.res.conn.buf
がbytes.Buffer
のような具体的な型であり、かつその型がWriteString
メソッドを直接持っていることがコンパイル時に分かっているため、直接そのメソッドを呼び出します。- これにより、
io.WriteString
を介した呼び出しで発生していた実行時のインターフェースチェックが不要になり、直接メソッドが呼び出されるため、より効率的になります。
この変更は、Go言語におけるインターフェースの利用と、具体的な型のメソッド呼び出しの間のパフォーマンス特性を理解していることを示しています。コンパイル時に具体的な型が判明しており、その型が目的のメソッドを直接提供している場合は、インターフェースを介した汎用的なヘルパー関数よりも、直接メソッドを呼び出す方が効率的であるという原則に基づいています。
関連リンク
- Go言語
io
パッケージのドキュメント: https://pkg.go.dev/io - Go言語
bytes
パッケージのドキュメント: https://pkg.go.dev/bytes - Go言語
net/http
パッケージのドキュメント: https://pkg.go.dev/net/http - Go言語のインターフェースに関する公式ブログ記事やチュートリアル (一般的な情報源)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード (特に
io
およびbytes
パッケージ) - Go言語のパフォーマンス最適化に関する一般的な記事や議論 (例:
io.WriteString
とWrite
の比較など) - Go言語のコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/9608043 (コミットメッセージに記載されているリンク)
- GitHubのコミットページ: https://github.com/golang/go/commit/c6c439d7a0be6bfb91348d8a76b09ffd173688fa
- Go言語のインターフェースの内部動作に関する技術記事 (例: インターフェース値の構造、型アサーションのコストなど)