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

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

このコミットは、Go言語の標準ライブラリであるbufioパッケージのReader型にio.WriterToインターフェースの実装を追加するものです。これにより、bufio.Readerから直接データをio.Writerに効率的に書き出すことが可能になり、特にio.Copy関数を使用した場合のパフォーマンスが大幅に向上します。

コミット

commit e289a2b913d476cf8592728e6d50767d0e29afde
Author: Michael Chaten <mchaten@gmail.com>
Date:   Thu Sep 27 16:31:03 2012 +1000

    bufio: Implement io.WriterTo for (*Reader)
    
    This is part 1 of 2 for issue 4028
    
    benchmark                       old ns/op    new ns/op    delta
    BenchmarkReaderCopyOptimal          33495         9849  -70.60%
    BenchmarkReaderCopyUnoptimal        70631        27041  -61.72%
    BenchmarkReaderCopyOldImpl          51407        52970   +3.04%
    
    Update #4028
    
    R=dave, nigeltao, rsc, bradfitz, rogpeppe
    CC=golang-dev
    https://golang.org/cl/6548047

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

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

元コミット内容

bufioパッケージのReader型にio.WriterToインターフェースを実装します。これは、Issue 4028の解決に向けた2つの変更のうちの1つ目です。

ベンチマーク結果は以下の通りです。

  • BenchmarkReaderCopyOptimal: 33495 ns/op から 9849 ns/op へ、70.60%の改善
  • BenchmarkReaderCopyUnoptimal: 70631 ns/op から 27041 ns/op へ、61.72%の改善
  • BenchmarkReaderCopyOldImpl: 51407 ns/op から 52970 ns/op へ、3.04%の悪化(これは既存の実装のベンチマークであり、比較対象として残されている)

Issue #4028を更新します。

レビュー担当者: dave, nigeltao, rsc, bradfitz, rogpeppe CC: golang-dev 変更リスト: https://golang.org/cl/6548047

変更の背景

この変更の主な背景は、Go言語の標準ライブラリにおけるI/O操作の効率化、特にio.Copy関数を用いたデータ転送のパフォーマンス改善です。コミットメッセージに明記されているように、この変更はIssue 4028に関連しています。

Issue 4028は、io.Copyio.Readerio.Writerインターフェースを介してデータをコピーする際に、io.WriterToio.ReaderFromといったより効率的なインターフェースが実装されている場合に、それらを活用できていないというパフォーマンス上の課題を指摘していました。

具体的には、io.Copy(dst, src)が呼び出された際、Goの標準的なio.Copyの実装は、まずsrcio.WriterToインターフェースを実装しているかを確認します。もし実装していれば、src.WriteTo(dst)を呼び出すことで、src自身が最も効率的な方法でdstにデータを書き込むことができます。同様に、dstio.ReaderFromインターフェースを実装していれば、dst.ReadFrom(src)を呼び出すことで、dst自身がsrcからデータを読み込むことができます。これらの最適化パスは、中間バッファリングを減らしたり、基盤となるシステムコールを最適化したりすることで、大幅なパフォーマンス向上をもたらします。

このコミットでは、bufio.Readerio.WriterToを実装することで、bufio.Readerから他のio.Writerへのコピー操作(特にio.Copy経由)が、バッファリングされたデータを直接効率的に転送できるようになり、ベンチマーク結果が示すように顕著なパフォーマンス改善が達成されました。

前提知識の解説

Go言語のioパッケージ

Go言語のioパッケージは、プリミティブなI/O操作を提供する基本的なインターフェースを定義しています。

  • io.Readerインターフェース:

    type Reader interface {
        Read(p []byte) (n int, err error)
    }
    

    データを読み込むための基本的なインターフェースです。Readメソッドは、最大len(p)バイトをpに読み込み、読み込んだバイト数nとエラーを返します。

  • io.Writerインターフェース:

    type Writer interface {
        Write(p []byte) (n int, err error)
    }
    

    データを書き込むための基本的なインターフェースです。Writeメソッドは、pからlen(p)バイトを書き込み、書き込んだバイト数nとエラーを返します。

  • io.WriterToインターフェース:

    type WriterTo interface {
        WriteTo(w Writer) (n int64, err error)
    }
    

    このインターフェースは、自身が持っているデータを指定されたio.Writerに直接書き込む能力を持つ型が実装します。io.Copy関数は、ソースがio.WriterToを実装している場合、このメソッドを優先的に使用して効率的なコピーを行います。これにより、io.Copyが内部でバッファを確保してReadWriteを繰り返すよりも、実装側がより効率的な方法(例えば、内部バッファを直接転送する、または基盤となるシステムコールを最適化する)でデータを転送できるようになります。

  • io.ReaderFromインターフェース:

    type ReaderFrom interface {
        ReadFrom(r Reader) (n int64, err error)
    }
    

    このインターフェースは、指定されたio.Readerから直接データを読み込む能力を持つ型が実装します。io.Copy関数は、デスティネーションがio.ReaderFromを実装している場合、このメソッドを優先的に使用します。

bufioパッケージ

bufioパッケージは、I/O操作をバッファリングすることで効率化するための機能を提供します。

  • bufio.Reader: io.Readerをラップし、内部にバッファを持つことで、少量のデータを頻繁に読み込む際に発生するシステムコールを減らし、パフォーマンスを向上させます。例えば、ファイルから1バイトずつ読み込む場合でも、bufio.Readerは一度に大きなチャンクを読み込み、それを内部バッファに保持することで、実際のI/O操作の回数を減らします。

io.Copy関数

io.Copy(dst Writer, src Reader) (written int64, err error)は、srcからdstへデータをコピーするためのGo言語の標準関数です。この関数は、内部的に以下のような最適化ロジックを持っています。

  1. srcio.WriterToを実装している場合、src.WriteTo(dst)を呼び出します。
  2. dstio.ReaderFromを実装している場合、dst.ReadFrom(src)を呼び出します。
  3. 上記いずれでもない場合、内部で一時的なバッファ(通常は32KB)を確保し、src.Readdst.Writeを繰り返し呼び出してデータを転送します。

このコミットは、bufio.Readerio.WriterToを実装することで、io.Copyが上記1の最適化パスを利用できるようになることを目的としています。

技術的詳細

このコミットでは、bufio.Reader型にWriteToメソッドを追加し、io.WriterToインターフェースを実装しています。

WriteToメソッドのロジックは以下の通りです。

  1. 内部バッファの書き出し: まず、bufio.Readerが現在保持している内部バッファ(b.buf[b.r:b.w])の内容を、ヘルパー関数writeBufを使って引数wio.Writer)に書き出します。これにより、既に読み込まれてバッファにあるデータが効率的に転送されます。
  2. 基盤となるリーダーの最適化パス: bufio.Readerがラップしている基盤となるio.Readerb.rd)が、もし自身もio.WriterToインターフェースを実装している場合、そのWriteToメソッドを直接呼び出します。これは、基盤となるリーダーが自身のデータを最も効率的にwに書き出すことができるため、さらなる最適化を可能にします。例えば、ファイルディスクリプタを直接転送するような最適化(sendfileシステムコールなど)が可能な場合があります。
  3. 残りのデータの転送: 上記の最適化パスが利用できない場合、または基盤となるリーダーがio.WriterToを実装していない場合、bufio.Readerはループを使って残りのデータを転送します。
    • b.fill()を呼び出して、内部バッファを可能な限り埋めます。
    • バッファにデータがある限り(b.r < b.w)、writeBufを使ってそのデータをwに書き出します。
    • このループは、基盤となるリーダーからの読み込みが完了するか、エラーが発生するまで続きます。
  4. EOFの処理: 読み込み中にio.EOFエラーが発生した場合、それは正常な終了を示すため、b.errnilにリセットします。
  5. エラーの返却: 最終的に、書き込んだバイト数nと、発生したエラー(もしあれば)を返します。

ヘルパー関数writeBuf(w io.Writer) (int64, error)は、bufio.Readerの現在のバッファ内容をwに書き込み、書き込んだバイト数とエラーを返します。書き込み後、バッファの読み込みポインタb.rを更新します。

テストコードでは、TestReaderWriteToで正常系の動作を確認し、TestReaderWriteToErrorsでエラーハンドリングを検証しています。 さらに、ベンチマークテストが追加されており、io.WriterToの実装がio.Copyのパフォーマンスに与える影響を明確に示しています。

  • BenchmarkReaderCopyOptimal: 基盤となるリーダーがio.WriterToを実装している場合のio.Copyのパフォーマンスを測定します。このケースでは、bufio.ReaderWriteToが基盤のWriteToを呼び出すため、非常に効率的です。
  • BenchmarkReaderCopyUnoptimal: 基盤となるリーダーがio.WriterToを実装していない場合のio.Copyのパフォーマンスを測定します。この場合、bufio.Readerは内部バッファリングとループによる転送を行います。
  • BenchmarkReaderCopyNoWriteTo: bufio.Readerio.WriterToを実装していない場合の、従来のio.Copyの動作をシミュレートします。これは比較のために残されています。

ベンチマーク結果は、io.WriterToの実装がio.Copyのパフォーマンスを劇的に改善することを示しており、特に最適なケースでは70%以上の高速化が達成されています。

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

src/pkg/bufio/bufio.go ファイルに以下の変更が加えられています。

  1. Reader型にWriteToメソッドが追加されました。
  2. writeBufというヘルパー関数が追加されました。
--- a/src/pkg/bufio/bufio.go
+++ b/src/pkg/bufio/bufio.go
@@ -375,6 +375,41 @@ func (b *Reader) ReadString(delim byte) (line string, err error) {
 	return string(bytes), e
 }
 
+// WriteTo implements io.WriterTo.
+func (b *Reader) WriteTo(w io.Writer) (n int64, err error) {
+	n, err = b.writeBuf(w)
+	if err != nil {
+		return
+	}
+
+	if r, ok := b.rd.(io.WriterTo); ok {
+		m, err := r.WriteTo(w)
+		n += m
+		return n, err
+	}
+
+	for b.fill(); b.r < b.w; b.fill() {
+		m, err := b.writeBuf(w)
+		n += m
+		if err != nil {
+			return n, err
+		}
+	}
+
+	if b.err == io.EOF {
+		b.err = nil
+	}
+
+	return n, b.readErr()
+}
+
+// writeBuf writes the Reader's buffer to the writer.
+func (b *Reader) writeBuf(w io.Writer) (int64, error) {
+	n, err := w.Write(b.buf[b.r:b.w])
+	b.r += n
+	return int64(n), err
+}
+
 // buffered output
 
 // Writer implements buffering for an io.Writer object.

src/pkg/bufio/bufio_test.go ファイルには、WriteToメソッドのテストケースとベンチマークが追加されています。

コアとなるコードの解説

func (b *Reader) WriteTo(w io.Writer) (n int64, err error)

このメソッドはio.WriterToインターフェースの実装です。bufio.Readerが保持するデータを、引数で渡されたio.Writer wに書き込みます。

  1. n, err = b.writeBuf(w): まず、bufio.Readerの内部バッファに現在残っているデータをwに書き出します。これにより、既にバッファリングされているデータが即座に転送されます。エラーが発生した場合は、そのエラーを返して処理を終了します。

  2. if r, ok := b.rd.(io.WriterTo); ok: bufio.Readerがラップしている基盤となるio.Readerb.rd)が、もしio.WriterToインターフェースを実装しているかどうかを型アサーションで確認します。

    • もし実装していれば、r.WriteTo(w)を直接呼び出します。これは、基盤となるリーダーが自身のデータをwに書き出すための最も効率的な方法を知っている可能性があるため、非常に重要な最適化パスです。例えば、ファイルディスクリプタ間の直接転送(sendfile)などが利用できる場合があります。
    • 基盤のWriteToが成功した場合、その結果(書き込んだバイト数mとエラーerr)を累積し、処理を終了します。
  3. for b.fill(); b.r < b.w; b.fill(): 基盤となるリーダーがio.WriterToを実装していない場合、またはその呼び出しが完了した後、このループが実行されます。

    • b.fill(): bufio.Readerの内部バッファを、基盤となるリーダーから可能な限り多くのデータで満たします。
    • b.r < b.w: バッファにまだ読み込まれていないデータが残っている間、ループを続けます。
    • m, err := b.writeBuf(w): バッファ内のデータをwに書き出します。
    • n += m: 書き込んだバイト数を累積します。
    • if err != nil { return n, err }: 書き込み中にエラーが発生した場合、処理を終了します。
  4. if b.err == io.EOF { b.err = nil }: ループが終了した後、bufio.Readerの内部エラー状態b.errio.EOFであれば、それをnilにリセットします。これは、io.EOFが正常なストリームの終了を示すためです。

  5. return n, b.readErr(): 最終的に、これまでに書き込んだ合計バイト数nと、bufio.Readerが保持している読み込み関連のエラー(b.readErr()b.errを返すヘルパー)を返します。

func (b *Reader) writeBuf(w io.Writer) (int64, error)

このヘルパー関数は、bufio.Readerの内部バッファ(b.buf)のうち、まだ読み出されていない部分(b.buf[b.r:b.w])を、指定されたio.Writer wに書き込みます。

  1. n, err := w.Write(b.buf[b.r:b.w]): バッファの有効な部分をw.Writeに渡して書き込みます。nは実際に書き込まれたバイト数です。

  2. b.r += n: 書き込みが成功したバイト数だけ、bufio.Readerの読み込みポインタb.rを進めます。これにより、次にwriteBufが呼び出されたときに、既に書き込まれたデータが再度処理されるのを防ぎます。

  3. return int64(n), err: 書き込んだバイト数と、w.Writeから返されたエラーを返します。

この実装により、bufio.Readerio.Copyなどの関数と連携する際に、より効率的なデータ転送パスを提供できるようになり、特に大量のデータを扱う場合のパフォーマンスが向上します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (ioパッケージ、bufioパッケージ)
  • Go言語のIssueトラッカー (Issue 4028)
  • Go言語の変更リスト (CL 6548047)
  • Go言語のio.Copy関数の実装に関する一般的な知識
  • Go言語のベンチマークテストの読み方に関する一般的な知識
  • Go言語のインターフェースと型アサーションに関する一般的な知識
  • sendfileシステムコールに関する一般的な知識 (基盤となるio.WriterToの実装が利用する可能性のある最適化として)

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

このコミットは、Go言語の標準ライブラリであるbufioパッケージのReader型にio.WriterToインターフェースの実装を追加するものです。これにより、bufio.Readerから直接データをio.Writerに効率的に書き出すことが可能になり、特にio.Copy関数を使用した場合のパフォーマンスが大幅に向上します。

コミット

commit e289a2b913d476cf8592728e6d50767d0e29afde
Author: Michael Chaten <mchaten@gmail.com>
Date:   Thu Sep 27 16:31:03 2012 +1000

    bufio: Implement io.WriterTo for (*Reader)
    
    This is part 1 of 2 for issue 4028
    
    benchmark                       old ns/op    new ns/op    delta
    BenchmarkReaderCopyOptimal          33495         9849  -70.60%
    BenchmarkReaderCopyUnoptimal        70631        27041  -61.72%
    BenchmarkReaderCopyOldImpl          51407        52970   +3.04%
    
    Update #4028
    
    R=dave, nigeltao, rsc, bradfitz, rogpeppe
    CC=golang-dev
    https://golang.org/cl/6548047

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

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

元コミット内容

bufioパッケージのReader型にio.WriterToインターフェースを実装します。これは、Issue 4028の解決に向けた2つの変更のうちの1つ目です。

ベンチマーク結果は以下の通りです。

  • BenchmarkReaderCopyOptimal: 33495 ns/op から 9849 ns/op へ、70.60%の改善
  • BenchmarkReaderCopyUnoptimal: 70631 ns/op から 27041 ns/op へ、61.72%の改善
  • BenchmarkReaderCopyOldImpl: 51407 ns/op から 52970 ns/op へ、3.04%の悪化(これは既存の実装のベンチマークであり、比較対象として残されている)

Issue #4028を更新します。

レビュー担当者: dave, nigeltao, rsc, bradfitz, rogpeppe CC: golang-dev 変更リスト: https://golang.org/cl/6548047

変更の背景

この変更の主な背景は、Go言語の標準ライブラリにおけるI/O操作の効率化、特にio.Copy関数を用いたデータ転送のパフォーマンス改善です。コミットメッセージに明記されているように、この変更はIssue 4028に関連しています。

Issue 4028は、io.Copyio.Readerio.Writerインターフェースを介してデータをコピーする際に、io.WriterToio.ReaderFromといったより効率的なインターフェースが実装されている場合に、それらを活用できていないというパフォーマンス上の課題を指摘していました。

具体的には、io.Copy(dst, src)が呼び出された際、Goの標準的なio.Copyの実装は、まずsrcio.WriterToインターフェースを実装しているかを確認します。もし実装していれば、src.WriteTo(dst)を呼び出すことで、src自身が最も効率的な方法でdstにデータを書き込むことができます。同様に、dstio.ReaderFromインターフェースを実装していれば、dst.ReadFrom(src)を呼び出すことで、dst自身がsrcからデータを読み込むことができます。これらの最適化パスは、中間バッファリングを減らしたり、基盤となるシステムコールを最適化したりすることで、大幅なパフォーマンス向上をもたらします。

このコミットでは、bufio.Readerio.WriterToを実装することで、bufio.Readerから他のio.Writerへのコピー操作(特にio.Copy経由)が、バッファリングされたデータを直接効率的に転送できるようになり、ベンチマーク結果が示すように顕著なパフォーマンス改善が達成されました。

前提知識の解説

Go言語のioパッケージ

Go言語のioパッケージは、プリミティブなI/O操作を提供する基本的なインターフェースを定義しています。

  • io.Readerインターフェース:

    type Reader interface {
        Read(p []byte) (n int, err error)
    }
    

    データを読み込むための基本的なインターフェースです。Readメソッドは、最大len(p)バイトをpに読み込み、読み込んだバイト数nとエラーを返します。

  • io.Writerインターフェース:

    type Writer interface {
        Write(p []byte) (n int, err error)
    }
    

    データを書き込むための基本的なインターフェースです。Writeメソッドは、pからlen(p)バイトを書き込み、書き込んだバイト数nとエラーを返します。

  • io.WriterToインターフェース:

    type WriterTo interface {
        WriteTo(w Writer) (n int64, err error)
    }
    

    このインターフェースは、自身が持っているデータを指定されたio.Writerに直接書き込む能力を持つ型が実装します。io.Copy関数は、ソースがio.WriterToを実装している場合、このメソッドを優先的に使用して効率的なコピーを行います。これにより、io.Copyが内部でバッファを確保してReadWriteを繰り返すよりも、実装側がより効率的な方法(例えば、内部バッファを直接転送する、または基盤となるシステムコールを最適化する)でデータを転送できるようになります。

  • io.ReaderFromインターフェース:

    type ReaderFrom interface {
        ReadFrom(r Reader) (n int64, err error)
    }
    

    このインターフェースは、指定されたio.Readerから直接データを読み込む能力を持つ型が実装します。io.Copy関数は、デスティネーションがio.ReaderFromを実装している場合、このメソッドを優先的に使用します。

bufioパッケージ

bufioパッケージは、I/O操作をバッファリングすることで効率化するための機能を提供します。

  • bufio.Reader: io.Readerをラップし、内部にバッファを持つことで、少量のデータを頻繁に読み込む際に発生するシステムコールを減らし、パフォーマンスを向上させます。例えば、ファイルから1バイトずつ読み込む場合でも、bufio.Readerは一度に大きなチャンクを読み込み、それを内部バッファに保持することで、実際のI/O操作の回数を減らします。

io.Copy関数

io.Copy(dst Writer, src Reader) (written int64, err error)は、srcからdstへデータをコピーするためのGo言語の標準関数です。この関数は、内部的に以下のような最適化ロジックを持っています。

  1. srcio.WriterToを実装している場合、src.WriteTo(dst)を呼び出します。
  2. dstio.ReaderFromを実装している場合、dst.ReadFrom(src)を呼び出します。
  3. 上記いずれでもない場合、内部で一時的なバッファ(通常は32KB)を確保し、src.Readdst.Writeを繰り返し呼び出してデータを転送します。

このコミットは、bufio.Readerio.WriterToを実装することで、io.Copyが上記1の最適化パスを利用できるようになることを目的としています。

技術的詳細

このコミットでは、bufio.Reader型にWriteToメソッドを追加し、io.WriterToインターフェースを実装しています。

WriteToメソッドのロジックは以下の通りです。

  1. 内部バッファの書き出し: まず、bufio.Readerが現在保持している内部バッファ(b.buf[b.r:b.w])の内容を、ヘルパー関数writeBufを使って引数wio.Writer)に書き出します。これにより、既に読み込まれてバッファにあるデータが効率的に転送されます。
  2. 基盤となるリーダーの最適化パス: bufio.Readerがラップしている基盤となるio.Readerb.rd)が、もし自身もio.WriterToインターフェースを実装している場合、そのWriteToメソッドを直接呼び出します。これは、基盤となるリーダーが自身のデータを最も効率的にwに書き出すことができるため、さらなる最適化を可能にします。例えば、ファイルディスクリプタを直接転送するような最適化(sendfileシステムコールなど)が可能な場合があります。
  3. 残りのデータの転送: 上記の最適化パスが利用できない場合、または基盤となるリーダーがio.WriterToを実装していない場合、bufio.Readerはループを使って残りのデータを転送します。
    • b.fill()を呼び出して、内部バッファを可能な限り埋めます。
    • バッファにデータがある限り(b.r < b.w)、writeBufを使ってそのデータをwに書き出します。
    • このループは、基盤となるリーダーからの読み込みが完了するか、エラーが発生するまで続きます。
  4. EOFの処理: 読み込み中にio.EOFエラーが発生した場合、それは正常な終了を示すため、b.errnilにリセットします。
  5. エラーの返却: 最終的に、書き込んだバイト数nと、発生したエラー(もしあれば)を返します。

ヘルパー関数writeBuf(w io.Writer) (int64, error)は、bufio.Readerの現在のバッファ内容をwに書き込み、書き込んだバイト数とエラーを返します。書き込み後、バッファの読み込みポインタb.rを更新します。

テストコードでは、TestReaderWriteToで正常系の動作を確認し、TestReaderWriteToErrorsでエラーハンドリングを検証しています。 さらに、ベンチマークテストが追加されており、io.WriterToの実装がio.Copyのパフォーマンスに与える影響を明確に示しています。

  • BenchmarkReaderCopyOptimal: 基盤となるリーダーがio.WriterToを実装している場合のio.Copyのパフォーマンスを測定します。このケースでは、bufio.ReaderWriteToが基盤のWriteToを呼び出すため、非常に効率的です。
  • BenchmarkReaderCopyUnoptimal: 基盤となるリーダーがio.WriterToを実装していない場合のio.Copyのパフォーマンスを測定します。この場合、bufio.Readerは内部バッファリングとループによる転送を行います。
  • BenchmarkReaderCopyNoWriteTo: bufio.Readerio.WriterToを実装していない場合の、従来のio.Copyの動作をシミュレートします。これは比較のために残されています。

ベンチマーク結果は、io.WriterToの実装がio.Copyのパフォーマンスを劇的に改善することを示しており、特に最適なケースでは70%以上の高速化が達成されています。

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

src/pkg/bufio/bufio.go ファイルに以下の変更が加えられています。

  1. Reader型にWriteToメソッドが追加されました。
  2. writeBufというヘルパー関数が追加されました。
--- a/src/pkg/bufio/bufio.go
+++ b/src/pkg/bufio/bufio.go
@@ -375,6 +375,41 @@ func (b *Reader) ReadString(delim byte) (line string, err error) {
 	return string(bytes), e
 }
 
+// WriteTo implements io.WriterTo.
+func (b *Reader) WriteTo(w io.Writer) (n int64, err error) {
+	n, err = b.writeBuf(w)
+	if err != nil {
+		return
+	}
+
+	if r, ok := b.rd.(io.WriterTo); ok {
+		m, err := r.WriteTo(w)
+		n += m
+		return n, err
+	}
+
+	for b.fill(); b.r < b.w; b.fill() {
+		m, err := b.writeBuf(w)
+		n += m
+		if err != nil {
+			return n, err
+		}
+	}
+
+	if b.err == io.EOF {
+		b.err = nil
+	}
+
+	return n, b.readErr()
+}
+
+// writeBuf writes the Reader's buffer to the writer.
+func (b *Reader) writeBuf(w io.Writer) (int64, error) {
+	n, err := w.Write(b.buf[b.r:b.w])
+	b.r += n
+	return int64(n), err
+}
+
 // buffered output
 
 // Writer implements buffering for an io.Writer object.

src/pkg/bufio/bufio_test.go ファイルには、WriteToメソッドのテストケースとベンチマークが追加されています。

コアとなるコードの解説

func (b *Reader) WriteTo(w io.Writer) (n int64, err error)

このメソッドはio.WriterToインターフェースの実装です。bufio.Readerが保持するデータを、引数で渡されたio.Writer wに書き込みます。

  1. n, err = b.writeBuf(w): まず、bufio.Readerの内部バッファに現在残っているデータをwに書き出します。これにより、既にバッファリングされているデータが即座に転送されます。エラーが発生した場合は、そのエラーを返して処理を終了します。

  2. if r, ok := b.rd.(io.WriterTo); ok: bufio.Readerがラップしている基盤となるio.Readerb.rd)が、もしio.WriterToインターフェースを実装しているかどうかを型アサーションで確認します。

    • もし実装していれば、r.WriteTo(w)を直接呼び出します。これは、基盤となるリーダーが自身のデータをwに書き出すための最も効率的な方法を知っている可能性があるため、非常に重要な最適化パスです。例えば、ファイルディスクリプタ間の直接転送(sendfile)などが利用できる場合があります。
    • 基盤のWriteToが成功した場合、その結果(書き込んだバイト数mとエラーerr)を累積し、処理を終了します。
  3. for b.fill(); b.r < b.w; b.fill(): 基盤となるリーダーがio.WriterToを実装していない場合、またはその呼び出しが完了した後、このループが実行されます。

    • b.fill(): bufio.Readerの内部バッファを、基盤となるリーダーから可能な限り多くのデータで満たします。
    • b.r < b.w: バッファにまだ読み込まれていないデータが残っている間、ループを続けます。
    • m, err := b.writeBuf(w): バッファ内のデータをwに書き出します。
    • n += m: 書き込んだバイト数を累積します。
    • if err != nil { return n, err }: 書き込み中にエラーが発生した場合、処理を終了します。
  4. if b.err == io.EOF { b.err = nil }: ループが終了した後、bufio.Readerの内部エラー状態b.errio.EOFであれば、それをnilにリセットします。これは、io.EOFが正常なストリームの終了を示すためです。

  5. return n, b.readErr(): 最終的に、これまでに書き込んだ合計バイト数nと、bufio.Readerが保持している読み込み関連のエラー(b.readErr()b.errを返すヘルパー)を返します。

func (b *Reader) writeBuf(w io.Writer) (int64, error)

このヘルパー関数は、bufio.Readerの内部バッファ(b.buf)のうち、まだ読み出されていない部分(b.buf[b.r:b.w])を、指定されたio.Writer wに書き込みます。

  1. n, err := w.Write(b.buf[b.r:b.w]): バッファの有効な部分をw.Writeに渡して書き込みます。nは実際に書き込まれたバイト数です。

  2. b.r += n: 書き込みが成功したバイト数だけ、bufio.Readerの読み込みポインタb.rを進めます。これにより、次にwriteBufが呼び出されたときに、既に書き込まれたデータが再度処理されるのを防ぎます。

  3. return int64(n), err: 書き込んだバイト数と、w.Writeから返されたエラーを返します。

この実装により、bufio.Readerio.Copyなどの関数と連携する際に、より効率的なデータ転送パスを提供できるようになり、特に大量のデータを扱う場合のパフォーマンスが向上します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (ioパッケージ、bufioパッケージ)
  • Go言語のIssueトラッカー (Issue 4028)
  • Go言語の変更リスト (CL 6548047)
  • Go言語のio.Copy関数の実装に関する一般的な知識
  • Go言語のベンチマークテストの読み方に関する一般的な知識
  • Go言語のインターフェースと型アサーションに関する一般的な知識
  • sendfileシステムコールに関する一般的な知識 (基盤となるio.WriterToの実装が利用する可能性のある最適化として)