[インデックス 14176] ファイルの概要
このコミットは、Go言語の標準ライブラリbufio
パッケージにおける*Writer
型へのio.ReaderFrom
インターフェースの実装に関するものです。具体的には、以下の2つのファイルが変更されています。
src/pkg/bufio/bufio.go
:bufio.Writer
にReadFrom
メソッドが追加され、io.ReaderFrom
インターフェースが実装されました。src/pkg/bufio/bufio_test.go
:bufio.Writer
のReadFrom
メソッドの動作を検証するための新しいテストケースと、パフォーマンス改善を示すベンチマークが追加されました。
コミット
commit 2a4818dd11a4fac1016b1ef2c2200a8e094272bd
Author: Michael Chaten <mchaten@gmail.com>
Date: Fri Oct 19 11:22:51 2012 +1100
bufio: Implement io.ReaderFrom for (*Writer).
This is part 2 of 2 for issue 4028.
benchmark old ns/op new ns/op delta
BenchmarkWriterCopyOptimal 53293 28326 -46.85%
BenchmarkWriterCopyUnoptimal 53757 30537 -43.19%
BenchmarkWriterCopyNoReadFrom 53192 36642 -31.11%
Fixes #4028.
R=nigeltao
CC=golang-dev
https://golang.org/cl/6565056
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2a4818dd11a4fac1016b1ef2c2200a8e094272bd
元コミット内容
bufio: Implement io.ReaderFrom for (*Writer).
This is part 2 of 2 for issue 4028.
benchmark old ns/op new ns/op delta
BenchmarkWriterCopyOptimal 53293 28326 -46.85%
BenchmarkWriterCopyUnoptimal 53757 30537 -43.19%
BenchmarkWriterCopyNoReadFrom 53192 36642 -31.11%
Fixes #4028.
R=nigeltao
CC=golang-dev
https://golang.org/cl/6565056
変更の背景
このコミットは、Go言語のbufio
パッケージにおけるWriter
のパフォーマンス改善を目的としています。特に、io.Copy
のような操作において、バッファリングされた書き込み処理の効率を高めることが狙いです。コミットメッセージに「This is part 2 of 2 for issue 4028.」とあるように、これはIssue 4028(具体的な内容は現在のGoの公開Issueトラッカーでは直接確認できませんが、おそらくio.Copy
のパフォーマンスに関するものと推測されます)を解決するための2部構成の変更の2番目の部分です。
既存のio.Copy
関数は、通常、Reader
からデータを読み込み、それをWriter
に書き込むというループで動作します。しかし、Writer
がio.ReaderFrom
インターフェースを実装している場合、io.Copy
はより効率的なパス(通常はより大きなチャンクでのデータ転送や、基盤となるI/Oメカニズムへの直接的な委譲)を利用できます。このコミットは、bufio.Writer
がこの最適化されたパスを利用できるようにすることで、特に大量のデータをコピーする際のパフォーマンスを大幅に向上させることを目指しています。
ベンチマーク結果が示すように、BenchmarkWriterCopyOptimal
では約46.85%の、BenchmarkWriterCopyUnoptimal
でも約43.19%の性能向上が見られます。これは、io.ReaderFrom
の実装が、bufio.Writer
を介したデータ転送において顕著な効果をもたらしたことを示しています。
前提知識の解説
io.ReaderFrom
インターフェース
Go言語のio
パッケージは、I/Oプリミティブの基本的なインターフェースを定義しています。その中の一つにio.ReaderFrom
インターフェースがあります。
type ReaderFrom interface {
ReadFrom(r Reader) (n int64, err error)
}
このインターフェースは、ReadFrom
メソッドを定義しています。このメソッドは、r
からデータを読み込み、それを実装している型(この場合はWriter
)に書き込みます。io.Copy
関数は、dst
引数がio.ReaderFrom
を実装している場合、このReadFrom
メソッドを呼び出すことで、より効率的なデータ転送を試みます。これにより、中間バッファリングのオーバーヘッドを減らしたり、基盤となるシステムコールを最適化したりすることが可能になります。
bufio.Writer
bufio
パッケージは、I/O操作をバッファリングすることで効率を向上させるための型を提供します。bufio.Writer
は、基盤となるio.Writer
への書き込みをバッファリングします。データはまず内部バッファに書き込まれ、バッファが満杯になったとき、またはFlush
メソッドが明示的に呼び出されたときに、基盤となるio.Writer
に書き込まれます。これにより、小さな書き込みが頻繁に行われる場合でも、システムコールを減らし、I/Oの効率を高めることができます。
ゼロコピー (Zero-copy) と最適化されたI/O
「ゼロコピー」とは、CPUがデータをコピーする回数を減らすことで、データ転送の効率を向上させる技術の総称です。特にネットワークI/OやファイルI/Oにおいて、カーネル空間とユーザー空間の間でのデータコピーを最小限に抑えることで、CPU使用率を下げ、スループットを向上させます。
io.ReaderFrom
のようなインターフェースは、直接的なゼロコピーではないにしても、特定の条件下でI/O操作を最適化するためのメカニズムを提供します。例えば、基盤となるWriter
がio.ReaderFrom
を実装している場合、bufio.Writer
は自身のバッファリングロジックをスキップし、基盤となるWriter
のReadFrom
メソッドに処理を委譲することができます。これにより、より効率的なデータ転送パスが利用され、パフォーマンスが向上します。
技術的詳細
このコミットの核心は、bufio.Writer
にReadFrom
メソッドを実装し、io.ReaderFrom
インターフェースに準拠させることです。
bufio.Writer
のReadFrom
メソッドのロジックは以下のようになっています。
- 既存バッファのフラッシュ: まず、
b.Flush()
を呼び出して、bufio.Writer
の内部バッファに既に存在するデータを基盤となるio.Writer
に書き込みます。これは、ReadFrom
が新しいデータを読み込む前に、既存のデータが確実に書き込まれるようにするために重要です。 - 基盤となるWriterの
io.ReaderFrom
チェック:b.wr
(bufio.Writer
がラップしている基盤となるio.Writer
)がio.ReaderFrom
インターフェースを実装しているかどうかを型アサーションw, ok := b.wr.(io.ReaderFrom)
で確認します。- もし実装していれば、
bufio.Writer
は自身のバッファリングロジックを介さずに、直接基盤となるWriter
のReadFrom
メソッドを呼び出します。これにより、基盤となるWriterが提供する可能性のある、より最適化されたデータ転送パス(例えば、ファイルディスクリプタ間の直接転送など)を利用できます。これがベンチマークにおける「Optimal」ケースに相当します。 - 実装していなければ、通常のバッファリングされた読み書きループにフォールバックします。
- もし実装していれば、
- フォールバックループ: 基盤となる
Writer
がio.ReaderFrom
を実装していない場合、bufio.Writer
はr
からデータを読み込み、自身の内部バッファに書き込み、バッファが満杯になるたびにFlush()
を呼び出して基盤となるWriter
に書き込みます。r.Read(b.buf[b.n:])
でr
からデータを読み込み、bufio.Writer
の内部バッファの空きスペースに格納します。- 読み込んだバイト数
m
をb.n
(バッファ内のデータ量)とn
(総転送バイト数)に加算します。 b.Flush()
を呼び出して、バッファの内容を基盤となるWriter
に書き込みます。これにより、bufio.Writer
のバッファがクリアされ、次の読み込みのためのスペースが確保されます。io.EOF
エラーが発生した場合、それは正常なストリームの終了を示すため、err
をnil
に設定して返します。その他のエラーはそのまま返されます。
この実装により、io.Copy
がbufio.Writer
を宛先として使用する場合、基盤となるWriter
がio.ReaderFrom
をサポートしていればその最適化されたパスを利用し、そうでなければbufio.Writer
自身の効率的なバッファリングメカニズムを利用するという、柔軟かつ高性能なデータ転送が可能になります。
コアとなるコードの変更箇所
src/pkg/bufio/bufio.go
bufio.Writer
にReadFrom
メソッドが追加されました。
--- a/src/pkg/bufio/bufio.go
+++ b/src/pkg/bufio/bufio.go
@@ -567,6 +567,35 @@ func (b *Writer) WriteString(s string) (int, error) {
return nn, nil
}
+// ReadFrom implements io.ReaderFrom.
+func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) {
+ if err = b.Flush(); err != nil {
+ return 0, err
+ }
+ if w, ok := b.wr.(io.ReaderFrom); ok {
+ return w.ReadFrom(r)
+ }
+ var m int
+ for {
+ m, err = r.Read(b.buf[b.n:])
+ if m == 0 {
+ break
+ }
+ b.n += m
+ n += int64(m)
+ if err1 := b.Flush(); err1 != nil {
+ return n, err1
+ }
+ if err != nil {
+ break
+ }
+ }
+ if err == io.EOF {
+ err = nil
+ }
+ return n, err
+}
+
// buffered input and output
// ReadWriter stores pointers to a Reader and a Writer.
src/pkg/bufio/bufio_test.go
TestWriterReadFrom
関数が追加され、Writer.ReadFrom
の機能テストが行われています。また、createTestInput
ヘルパー関数が追加され、TestReaderWriteTo
もonlyReader
を使用するように変更されています。
さらに、errorReaderFromTest
構造体とTestWriterReadFromErrors
関数が追加され、エラーハンドリングのテストが行われています。
最後に、BenchmarkWriterCopyOptimal
, BenchmarkWriterCopyUnoptimal
, BenchmarkWriterCopyNoReadFrom
という3つの新しいベンチマークが追加され、Writer.ReadFrom
のパフォーマンスが測定されています。
--- a/src/pkg/bufio/bufio_test.go
+++ b/src/pkg/bufio/bufio_test.go
@@ -763,8 +763,8 @@ func testReadLineNewlines(t *testing.T, input string, expect []readLineResult) {
}\n"
}
-func TestReaderWriteTo(t *testing.T) {
- input := make([]byte, 8192)
+func createTestInput(n int) []byte {
+ input := make([]byte, n)
for i := range input {
// 101 and 251 are arbitrary prime numbers.
// The idea is to create an input sequence
@@ -774,7 +774,12 @@ func TestReaderWriteTo(t *testing.T) {
input[i] ^= byte(i / 101)
}
}
- r := NewReader(bytes.NewBuffer(input))
+ return input
+}
+
+func TestReaderWriteTo(t *testing.T) {
+ input := createTestInput(8192)
+ r := NewReader(&onlyReader{bytes.NewBuffer(input)})
w := new(bytes.Buffer)
if n, err := r.WriteTo(w); err != nil || n != int64(len(input)) {
t.Fatalf("r.WriteTo(w) = %d, %v, want %d, nil", n, err, len(input))
@@ -817,6 +822,65 @@ func TestReaderWriteToErrors(t *testing.T) {
}
}
+func TestWriterReadFrom(t *testing.T) {
+ ws := []func(io.Writer) io.Writer{
+ func(w io.Writer) io.Writer { return &onlyWriter{w} },
+ func(w io.Writer) io.Writer { return w },
+ }
+
+ rs := []func(io.Reader) io.Reader{
+ iotest.DataErrReader,
+ func(r io.Reader) io.Reader { return r },
+ }
+
+ for ri, rfunc := range rs {
+ for wi, wfunc := range ws {
+ input := createTestInput(8192)
+ b := new(bytes.Buffer)
+ w := NewWriter(wfunc(b))
+ r := rfunc(bytes.NewBuffer(input))
+ if n, err := w.ReadFrom(r); err != nil || n != int64(len(input)) {
+ t.Errorf("ws[%d],rs[%d]: w.ReadFrom(r) = %d, %v, want %d, nil", wi, ri, n, err, len(input))
+ continue
+ }
+ if got, want := b.String(), string(input); got != want {
+ t.Errorf("ws[%d], rs[%d]:\ngot %q\nwant %q\n", wi, ri, got, want)
+ }
+ }
+ }
+}
+
+type errorReaderFromTest struct {
+ rn, wn int
+ rerr, werr error
+ expected error
+}
+
+func (r errorReaderFromTest) Read(p []byte) (int, error) {
+ return len(p) * r.rn, r.rerr
+}
+
+func (w errorReaderFromTest) Write(p []byte) (int, error) {
+ return len(p) * w.wn, w.werr
+}
+
+var errorReaderFromTests = []errorReaderFromTest{
+ {0, 1, io.EOF, nil, nil},
+ {1, 1, io.EOF, nil, nil},
+ {0, 1, io.ErrClosedPipe, nil, io.ErrClosedPipe},
+ {0, 0, io.ErrClosedPipe, io.ErrShortWrite, io.ErrClosedPipe},
+ {1, 0, nil, io.ErrShortWrite, io.ErrShortWrite},
+}
+
+func TestWriterReadFromErrors(t *testing.T) {
+ for i, rw := range errorReaderFromTests {
+ w := NewWriter(rw)
+ if _, err := w.ReadFrom(rw); err != rw.expected {
+ t.Errorf("w.ReadFrom(errorReaderFromTests[%d]) = _, %v, want _,%v", i, err, rw.expected)
+ }
+ }
+}
+
// An onlyReader only implements io.Reader, no matter what other methods the underlying implementation may have.\n type onlyReader struct {\n \tr io.Reader\n@@ -866,3 +930,34 @@ func BenchmarkReaderCopyNoWriteTo(b *testing.B) {\n \t\tio.Copy(dst, src)\n \t}\n }\n+\n+func BenchmarkWriterCopyOptimal(b *testing.B) {\n+\t// Optimal case is where the underlying writer implements io.ReaderFrom\n+\tfor i := 0; i < b.N; i++ {\n+\t\tb.StopTimer()\n+\t\tsrc := &onlyReader{bytes.NewBuffer(make([]byte, 8192))}\n+\t\tdst := NewWriter(new(bytes.Buffer))\n+\t\tb.StartTimer()\n+\t\tio.Copy(dst, src)\n+\t}\n+}\n+\n+func BenchmarkWriterCopyUnoptimal(b *testing.B) {\n+\tfor i := 0; i < b.N; i++ {\n+\t\tb.StopTimer()\n+\t\tsrc := &onlyReader{bytes.NewBuffer(make([]byte, 8192))}\n+\t\tdst := NewWriter(&onlyWriter{new(bytes.Buffer)})\n+\t\tb.StartTimer()\n+\t\tio.Copy(dst, src)\n+\t}\n+}\n+\n+func BenchmarkWriterCopyNoReadFrom(b *testing.B) {\n+\tfor i := 0; i < b.N; i++ {\n+\t\tb.StopTimer()\n+\t\tsrc := &onlyReader{bytes.NewBuffer(make([]byte, 8192))}\n+\t\tdst := &onlyWriter{NewWriter(new(bytes.Buffer))}\n+\t\tb.StartTimer()\n+\t\tio.Copy(dst, src)\n+\t}\n+}\n```
## コアとなるコードの解説
### `bufio.go` の `ReadFrom` メソッド
```go
func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) {
if err = b.Flush(); err != nil {
return 0, err
}
if w, ok := b.wr.(io.ReaderFrom); ok {
return w.ReadFrom(r)
}
var m int
for {
m, err = r.Read(b.buf[b.n:])
if m == 0 {
break
}
b.n += m
n += int64(m)
if err1 := b.Flush(); err1 != nil {
return n, err1
}
if err != nil {
break
}
}
if err == io.EOF {
err = nil
}
return n, err
}
if err = b.Flush(); err != nil
:ReadFrom
が開始される前に、bufio.Writer
の内部バッファに蓄積されているデータを強制的に基盤となるWriter
に書き出します。これにより、ReadFrom
が読み込む新しいデータと既存のデータが混ざることを防ぎます。if w, ok := b.wr.(io.ReaderFrom); ok
:bufio.Writer
がラップしている基盤となるio.Writer
(b.wr
)が、自身もio.ReaderFrom
インターフェースを実装しているかどうかをチェックします。- もし実装していれば、
return w.ReadFrom(r)
によって、bufio.Writer
は自身のバッファリングロジックを介さずに、直接基盤となるWriter
のReadFrom
メソッドに処理を委譲します。これは、基盤となるWriter
がより効率的なデータ転送メカニズム(例えば、OSレベルの最適化されたコピー操作など)を提供できる場合に特に有効です。
- もし実装していれば、
for { ... }
: 基盤となるWriter
がio.ReaderFrom
を実装していない場合のフォールバックロジックです。m, err = r.Read(b.buf[b.n:])
:r
(ソースReader
)からbufio.Writer
の内部バッファの空いている部分にデータを読み込みます。if m == 0 { break }
: 読み込むデータがなくなったらループを終了します。b.n += m
: 読み込んだバイト数m
を内部バッファの現在データ量b.n
に加算します。n += int64(m)
: 総転送バイト数n
を更新します。if err1 := b.Flush(); err1 != nil
: データを読み込んだ後、バッファの内容を基盤となるWriter
にフラッシュします。これにより、バッファが満杯になるたびにデータが書き込まれ、メモリ使用量を抑えつつ効率的な書き込みが行われます。if err != nil { break }
: 読み込み中にエラーが発生した場合、ループを終了します。
if err == io.EOF { err = nil }
:io.EOF
は正常なストリームの終了を示すため、エラーをnil
に変換して返します。これにより、呼び出し元はデータがすべて転送されたことを正常な終了として扱えます。
bufio_test.go
のテストとベンチマーク
createTestInput
: テスト用のランダムなバイトスライスを生成するヘルパー関数です。TestWriterReadFrom
:bufio.Writer
のReadFrom
メソッドが正しく動作するかを検証する機能テストです。様々なio.Writer
とio.Reader
の組み合わせ(例えば、onlyWriter
やiotest.DataErrReader
など)でテストを行い、データが正しく転送されることを確認します。errorReaderFromTest
とTestWriterReadFromErrors
:ReadFrom
メソッドのエラーハンドリングが適切に行われるかを検証するテストです。Read
やWrite
操作で特定のエラーを返すカスタムのReaderFrom
実装を使用し、期待されるエラーが返されることを確認します。- ベンチマーク:
BenchmarkWriterCopyOptimal
: 基盤となるWriter
がio.ReaderFrom
を実装している場合のio.Copy
のパフォーマンスを測定します。このシナリオでは、bufio.Writer
は基盤となるWriter
のReadFrom
メソッドに処理を委譲するため、最も効率的なパスが利用されます。BenchmarkWriterCopyUnoptimal
: 基盤となるWriter
がio.ReaderFrom
を実装していないが、bufio.Writer
がバッファリングを行う場合のio.Copy
のパフォーマンスを測定します。この場合、bufio.Writer
は自身のフォールバックロジックを使用してデータを転送します。BenchmarkWriterCopyNoReadFrom
:io.Copy
がio.ReaderFrom
の最適化パスを全く利用できないように、bufio.Writer
をonlyWriter
でラップした場合のパフォーマンスを測定します。これは、このコミットによる最適化がない場合のベースライン性能を示します。
これらのベンチマークは、io.ReaderFrom
の実装がio.Copy
のパフォーマンスに与える影響を明確に示しており、特に「Optimal」ケースでの大幅な改善が確認できます。
関連リンク
- Go CL 6565056: https://golang.org/cl/6565056
参考にした情報源リンク
- GitHubコミットページ: https://github.com/golang/go/commit/2a4818dd11a4fac1016b1ef2c2200a8e094272bd
- Go言語
io
パッケージドキュメント: https://pkg.go.dev/io - Go言語
bufio
パッケージドキュメント: https://pkg.go.dev/bufio - Go言語
io.Copy
関数: https://pkg.go.dev/io#Copy - Go言語
io.ReaderFrom
インターフェース: https://pkg.go.dev/io#ReaderFrom - Go言語
io.Writer
インターフェース: https://pkg.go.dev/io#Writer - Go言語
io.Reader
インターフェース: https://pkg.go.dev/io#Reader - Go言語
bytes
パッケージドキュメント: https://pkg.go.dev/bytes - Go言語
testing
パッケージドキュメント: https://pkg.go.dev/testing - Go言語
testing/iotest
パッケージドキュメント: https://pkg.go.dev/testing/iotest - ゼロコピー (Wikipedia): https://ja.wikipedia.org/wiki/%E3%82%BC%E3%83%AD%E3%82%B3%E3%83%94%E3%83%BC