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

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

このコミットは、Go言語の標準ライブラリであるbufioパッケージにおける、読み込みバッファの最小サイズに関する変更です。具体的には、bufio.Readerが使用するバッファの最小サイズを16バイトに設定し、それに伴うテストコードの修正が含まれています。

  • src/pkg/bufio/bufio.go: bufioパッケージの主要な実装ファイルであり、Reader構造体とNewReaderSize関数が定義されています。このファイルでは、最小バッファサイズを定義する定数の追加と、NewReaderSize関数におけるバッファサイズ検証ロジックの変更が行われています。
  • src/pkg/bufio/bufio_test.go: bufioパッケージの単体テストファイルです。最小バッファサイズの変更に伴い、既存のテストケースが新しい制約と挙動に合わせて広範囲にわたって更新されています。

コミット

  • コミットハッシュ: 64776da456682db445462e6d095de1b2b6652a8e
  • 作者: Rob Pike r@golang.org
  • コミット日時: 2011年12月13日 15:07:17 -0800
  • コミットメッセージ:
    bufio: make the minimum read buffer size 16 bytes.
    
    R=gri, rsc
    CC=golang-dev
    https://golang.org/cl/5485067
    

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

https://github.com/golang/go/commit/64776da456682db445462e6d095de1b2b6652a8e

元コミット内容

このコミットの目的は、「bufioパッケージの読み込みバッファの最小サイズを16バイトにする」ことです。コミットメッセージには、レビュー担当者(R=gri, rsc)とメーリングリスト(CC=golang-dev)、そしてGoのコードレビューシステムであるGerritへのリンク(https://golang.org/cl/5485067)が含まれています。これは、Goプロジェクトにおける典型的な開発ワークフローを示しており、変更がレビュープロセスを経て承認されたことを意味します。

変更の背景

Go言語のbufioパッケージは、効率的なI/O操作のためにバッファリングを提供します。バッファリングは、ディスクやネットワークとの間でデータをやり取りする際のシステムコール回数を減らすことで、アプリケーションのパフォーマンスを大幅に向上させます。

bufio.Readerを生成するNewReaderSize関数は、以前は1バイトより大きい任意のバッファサイズを受け入れていました(size <= 1というチェックがあったため、最小は2バイト)。しかし、非常に小さなバッファサイズ(例えば数バイト)を使用すると、バッファがすぐに満杯になり、頻繁なバッファの再充填が必要になります。これは、バッファリングの利点を打ち消し、かえってパフォーマンスを低下させる可能性があります。また、特定のI/O操作(例: ReadSlicePeek)において、バッファが小さすぎるとErrBufferFullのようなエラーが頻繁に発生し、アプリケーションロジックが複雑になる原因にもなりえます。

このコミットは、このような非効率性や潜在的な問題に対処するために導入されました。最小バッファサイズを16バイトに設定することで、bufio.Readerが常に合理的なサイズのバッファで動作することを保証し、より予測可能で効率的なI/Oパフォーマンスを提供することを目指しています。16バイトというサイズは、多くのシステムで効率的なメモリアクセスやアライメントに適した、2のべき乗のサイズとして選ばれたと考えられます。

前提知識の解説

  • Go言語のbufioパッケージ: Goの標準ライブラリの一部で、バッファリングされたI/O機能を提供します。これにより、低レベルのI/O操作(os.Filenet.Connなど)を直接扱うよりも効率的にデータを読み書きできます。bufio.Readerは読み込み操作を、bufio.Writerは書き込み操作をバッファリングします。
  • バッファリング (Buffering): データが最終的な目的地に到達する前に、一時的にメモリ(バッファ)に蓄えられるプロセスです。I/O操作はCPUにとってコストの高い操作であり、システムコールを頻繁に発行するとオーバーヘッドが大きくなります。バッファリングにより、一度に大量のデータをまとめて読み書きすることで、システムコールの回数を減らし、全体的なスループットを向上させます。
  • io.Readerインターフェース: Go言語のioパッケージで定義されている基本的なインターフェースの一つです。Read(p []byte) (n int, err error)メソッドを持ち、任意のデータソースからバイト列を読み込むための抽象化を提供します。bufio.Readerは、このio.Readerインターフェースをラップしてバッファリング機能を追加します。
  • NewReaderSize関数: bufioパッケージのコンストラクタ関数で、指定されたio.Readerと、そのReaderが使用する内部バッファのサイズ(バイト単位)を受け取って、新しいbufio.Readerインスタンスを返します。この関数を使うことで、バッファのサイズをアプリケーションの要件に合わせて調整できます。
  • ErrBufferFullエラー: bufioパッケージで定義されているエラー定数です。bufio.ReaderReadSlicePeekなどのメソッドが、要求された操作を完了するために十分なバッファスペースがない場合に返されます。例えば、区切り文字が見つかる前にバッファが満杯になった場合などに発生します。

技術的詳細

このコミットの技術的な核心は、bufio.Readerの最小バッファサイズを強制することにあります。

  1. minReadBufferSize定数の導入: src/pkg/bufio/bufio.goconst minReadBufferSize = 16という新しい定数が追加されました。この定数は、bufio.Readerが許容する最小のバッファサイズを明示的に16バイトと定義しています。これにより、マジックナンバー(コード中に直接埋め込まれた意味不明な数値)を避け、コードの可読性と保守性が向上します。

  2. NewReaderSize関数のバッファサイズ検証ロジックの変更: NewReaderSize関数の内部で、引数として渡されるsize(バッファサイズ)の検証ロジックが変更されました。

    • 変更前: if size <= 1 { return nil, BufSizeError(size) } これは、バッファサイズが1バイト以下の場合にエラーを返すことを意味し、実質的な最小バッファサイズは2バイトでした。
    • 変更後: if size < minReadBufferSize { return nil, BufSizeError(size) } この変更により、sizeが新しく定義されたminReadBufferSize(16バイト)よりも小さい場合にBufSizeErrorを返すようになりました。これにより、開発者が意図的に、または誤って、非効率な小さなバッファサイズを設定することを防ぎます。関数のコメントも「which must be greater than one」から「which must be at least 16 bytes」に更新され、新しい制約が明確に示されています。
  3. テストケースの広範な更新: src/pkg/bufio/bufio_test.goでは、この最小バッファサイズの変更に合わせて、多数のテストケースが修正されました。

    • bufsizes配列の変更: テストスイートが様々なバッファサイズでbufio.Readerの挙動を検証するために使用するbufsizes配列から、以前の最小値である2が削除され、代わりに新しい最小値であるminReadBufferSize(16)が追加されました。これにより、テストが新しい制約の下で適切に実行されることが保証されます。
    • 既存テストの調整: TestBufferFullTestPeekTestLineTooLongTestReadEmptyBufferTestLinesAfterReadTestReadLineNewlinesなど、多くのテスト関数で、ハードコードされていた小さなバッファサイズ(例: 5, 4, 3, 10)がminReadBufferSizeに置き換えられました。また、これらのテストで使用される入力データや期待される出力も、新しい最小バッファサイズに合わせて調整されています。例えば、TestLineTooLongでは、テストデータの生成ロジックがminReadBufferSizeに基づいて動的に変更され、より現実的なシナリオでErrBufferFullの挙動を検証できるようになりました。これにより、bufioパッケージの各種機能が、16バイトの最小バッファサイズという新しい制約の下でも期待通りに動作することを確認しています。

これらの変更は、bufioパッケージの堅牢性とパフォーマンスを向上させることを目的としています。小さなバッファサイズが引き起こす可能性のあるエッジケースや非効率性を排除し、より予測可能で効率的なI/O操作を保証します。

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

src/pkg/bufio/bufio.go

--- a/src/pkg/bufio/bufio.go
+++ b/src/pkg/bufio/bufio.go
@@ -52,12 +52,14 @@ type Reader struct {
 	lastRuneSize int
 }
 
+const minReadBufferSize = 16
+
 // NewReaderSize creates a new Reader whose buffer has the specified size,
-// which must be greater than one.  If the argument io.Reader is already a
+// which must be at least 16 bytes.  If the argument io.Reader is already a
 // Reader with large enough size, it returns the underlying Reader.
 // It returns the Reader and any error.
 func NewReaderSize(rd io.Reader, size int) (*Reader, error) {
-\tif size <= 1 {\n+\tif size < minReadBufferSize {\n \t\treturn nil, BufSizeError(size)\n \t}\n \t// Is it already a Reader?

src/pkg/bufio/bufio_test.go

--- a/src/pkg/bufio/bufio_test.go
+++ b/src/pkg/bufio/bufio_test.go
@@ -135,9 +135,10 @@ var bufreaders = []bufReader{
 	{"lines", readLines},
 }
 
+const minReadBufferSize = 16
+
 var bufsizes = []int{
-\t2, 3, 4, 5, 6, 7, 8, 9, 10,
-\t23, 32, 46, 64, 93, 128, 1024, 4096,
+\tminReadBufferSize, 23, 32, 46, 64, 93, 128, 1024, 4096,
 }
 
 func TestReader(t *testing.T) {
@@ -514,27 +515,32 @@ func TestWriteString(t *testing.T) {
 }
 
 func TestBufferFull(t *testing.T) {
-\tbuf, _ := NewReaderSize(strings.NewReader("hello, world"), 5)
-\tline, err := buf.ReadSlice(',')
-\tif string(line) != "hello" || err != ErrBufferFull {
+\tconst longString = "And now, hello, world! It is the time for all good men to come to the aid of their party"
+\tbuf, err := NewReaderSize(strings.NewReader(longString), minReadBufferSize)
+\tif err != nil {
+\t\tt.Fatal("NewReaderSize:", err)
+\t}
+\tline, err := buf.ReadSlice('!')
+\tif string(line) != "And now, hello, " || err != ErrBufferFull {
 \t\tt.Errorf("first ReadSlice(,) = %q, %v", line, err)
 \t}
-\tline, err = buf.ReadSlice(',')
-\tif string(line) != "," || err != nil {
+\tline, err = buf.ReadSlice('!')
+\tif string(line) != "world!" || err != nil {
 \t\tt.Errorf("second ReadSlice(,) = %q, %v", line, err)
 \t}
 }
 
 func TestPeek(t *testing.T) {
 	p := make([]byte, 10)
-\tbuf, _ := NewReaderSize(strings.NewReader("abcdefghij"), 4)
+\t// string is 16 (minReadBufferSize) long.
+\tbuf, _ := NewReaderSize(strings.NewReader("abcdefghijklmnop"), minReadBufferSize)
 \tif s, err := buf.Peek(1); string(s) != "a" || err != nil {
 \t\tt.Fatalf("want %q got %q, err=%v", "a", string(s), err)
 \t}
 \tif s, err := buf.Peek(4); string(s) != "abcd" || err != nil {
 \t\tt.Fatalf("want %q got %q, err=%v", "abcd", string(s), err)
 \t}
-\tif _, err := buf.Peek(5); err != ErrBufferFull {
+\tif _, err := buf.Peek(32); err != ErrBufferFull {
 \t\tt.Fatalf("want ErrBufFull got %v", err)
 \t}
 \tif _, err := buf.Read(p[0:3]); string(p[0:3]) != "abc" || err != nil {
@@ -552,8 +558,8 @@ func TestPeek(t *testing.T) {
 \tif s, err := buf.Peek(4); string(s) != "ghij" || err != nil {
 \t\tt.Fatalf("want %q got %q, err=%v", "ghij", string(s), err)
 \t}
-\tif _, err := buf.Read(p[0:4]); string(p[0:4]) != "ghij" || err != nil {
-\t\tt.Fatalf("want %q got %q, err=%v", "ghij", string(p[0:3]), err)
+\tif _, err := buf.Read(p[0:]); string(p[0:]) != "ghijklmnop" || err != nil {
+\t\tt.Fatalf("want %q got %q, err=%v", "ghijklmnop", string(p[0:minReadBufferSize]), err)
 \t}
 \tif s, err := buf.Peek(0); string(s) != "" || err != nil {
 \t\tt.Fatalf("want %q got %q, err=%v", "", string(s), err)
@@ -635,19 +641,25 @@ func TestReadLine(t *testing.T) {
 }
 
 func TestLineTooLong(t *testing.T) {
-\tbuf := bytes.NewBuffer([]byte("aaabbbcc\n"))
-\tl, _ := NewReaderSize(buf, 3)
+\tdata := make([]byte, 0)
+\tfor i := 0; i < minReadBufferSize*5/2; i++ {
+\t\tdata = append(data, '0'+byte(i%10))
+\t}
+\tbuf := bytes.NewBuffer(data)
+\tl, _ := NewReaderSize(buf, minReadBufferSize)
 \tline, isPrefix, err := l.ReadLine()
-\tif !isPrefix || !bytes.Equal(line, []byte("aaa")) || err != nil {
-\t\tt.Errorf("bad result for first line: %x %s", line, err)
+\tif !isPrefix || !bytes.Equal(line, data[:minReadBufferSize]) || err != nil {
+\t\tt.Errorf("bad result for first line: got %q want %q %v", line, data[:minReadBufferSize], err)
 \t}
+\tdata = data[len(line):]
 \tline, isPrefix, err = l.ReadLine()
-\tif !isPrefix || !bytes.Equal(line, []byte("bbb")) || err != nil {
-\t\tt.Errorf("bad result for second line: %x", line)
+\tif !isPrefix || !bytes.Equal(line, data[:minReadBufferSize]) || err != nil {
+\t\tt.Errorf("bad result for second line: got %q want %q %v", line, data[:minReadBufferSize], err)
 \t}
+\tdata = data[len(line):]
 \tline, isPrefix, err = l.ReadLine()
-\tif isPrefix || !bytes.Equal(line, []byte("cc")) || err != nil {
-\t\tt.Errorf("bad result for third line: %x", line)
+\tif isPrefix || !bytes.Equal(line, data[:minReadBufferSize/2]) || err != nil {
+\t\tt.Errorf("bad result for third line: got %q want %q %v", line, data[:minReadBufferSize/2], err)
 \t}
 \tline, isPrefix, err = l.ReadLine()
 \tif isPrefix || err == nil {
@@ -656,8 +668,8 @@ func TestLineTooLong(t *testing.T) {
 }
 
 func TestReadAfterLines(t *testing.T) {
-\tline1 := "line1"
-\trestData := "line2\nline 3\n"
+\tline1 := "this is line1"
+\trestData := "this is line2\nthis is line 3\n"
 \tinbuf := bytes.NewBuffer([]byte(line1 + "\n" + restData))
 \toutbuf := new(bytes.Buffer)
 \tmaxLineLength := len(line1) + len(restData)/2
@@ -676,7 +688,7 @@ func TestReadAfterLines(t *testing.T) {
 }
 
 func TestReadEmptyBuffer(t *testing.T) {
-\tl, _ := NewReaderSize(bytes.NewBuffer(nil), 10)
+\tl, _ := NewReaderSize(bytes.NewBuffer(nil), minReadBufferSize)
 \tline, isPrefix, err := l.ReadLine()
 \tif err != io.EOF {
 \t\tt.Errorf("expected EOF from ReadLine, got '%s' %t %s", line, isPrefix, err)
@@ -684,7 +696,7 @@ func TestReadEmptyBuffer(t *testing.T) {
 }
 
 func TestLinesAfterRead(t *testing.T) {
-\tl, _ := NewReaderSize(bytes.NewBuffer([]byte("foo")), 10)
+\tl, _ := NewReaderSize(bytes.NewBuffer([]byte("foo")), minReadBufferSize)
 \t_, err := ioutil.ReadAll(l)
 \tif err != nil {
 \t\tt.Error(err)
@@ -715,34 +727,19 @@ type readLineResult struct {
 }
 
 var readLineNewlinesTests = []struct {
-\tinput   string
-\tbufSize int
-\texpect  []readLineResult
+\tinput  string
+\texpect []readLineResult
 }{
-\t{"h\r\nb\r\n", 2, []readLineResult{
-\t\t{[]byte("h"), true, nil},
-\t\t{nil, false, nil},
-\t\t{[]byte("b"), true, nil},
-\t\t{nil, false, nil},
-\t\t{nil, false, io.EOF},
-\t}},
-\t{"hello\r\nworld\r\n", 6, []readLineResult{
-\t\t{[]byte("hello"), true, nil},
+\t{"012345678901234\r\n012345678901234\r\n", []readLineResult{
+\t\t{[]byte("012345678901234"), true, nil},
 \t\t{nil, false, nil},
-\t\t{[]byte("world"), true, nil},
+\t\t{[]byte("012345678901234"), true, nil},
 \t\t{nil, false, nil},
 \t\t{nil, false, io.EOF},
 \t}},
-\t{"hello\rworld\r", 6, []readLineResult{
-\t\t{[]byte("hello"), true, nil},
-\t\t{[]byte("\rworld"), true, nil},
-\t\t{[]byte("\r"), false, nil},
-\t\t{nil, false, io.EOF},
-\t}},
-\t{"h\ri\r\n\r", 2, []readLineResult{
-\t\t{[]byte("h"), true, nil},
-\t\t{[]byte("\ri"), true, nil},
-\t\t{nil, false, nil},
+\t{"0123456789012345\r012345678901234\r", []readLineResult{
+\t\t{[]byte("0123456789012345"), true, nil},
+\t\t{[]byte("\r012345678901234"), true, nil},
 \t\t{[]byte("\r"), false, nil},
 \t\t{nil, false, io.EOF},
 \t}},
@@ -750,12 +747,12 @@ var readLineNewlinesTests = []struct {
 
 func TestReadLineNewlines(t *testing.T) {
 	for _, e := range readLineNewlinesTests {
-\t\ttestReadLineNewlines(t, e.input, e.bufSize, e.expect)
+\t\ttestReadLineNewlines(t, e.input, e.expect)
 	}
 }
 
-func testReadLineNewlines(t *testing.T, input string, bufSize int, expect []readLineResult) {
-\tb, err := NewReaderSize(strings.NewReader(input), bufSize)
+func testReadLineNewlines(t *testing.T, input string, expect []readLineResult) {
+\tb, err := NewReaderSize(strings.NewReader(input), minReadBufferSize)
 \tif err != nil {
 \t\tt.Fatal(err)
 \t}

コアとなるコードの解説

src/pkg/bufio/bufio.go

  • const minReadBufferSize = 16: この行は、bufio.Readerが内部で使用するバッファの最小サイズを16バイトと定義しています。これは、バッファリングの効率を確保し、非常に小さなバッファサイズによるパフォーマンス低下を防ぐための閾値となります。
  • NewReaderSize関数の変更:
    • 変更前は、size <= 1という条件でバッファサイズが1バイト以下の場合にエラーを返していました。これは、実質的に最小バッファサイズが2バイトであることを意味します。
    • 変更後は、size < minReadBufferSizeという条件に変わり、sizeが新しく定義されたminReadBufferSize(16バイト)よりも小さい場合にエラーを返すようになりました。これにより、開発者が意図せず非効率な小さなバッファサイズを設定してしまうことを防ぎ、bufio.Readerが常に合理的なサイズのバッファで動作することを保証します。
    • 関数のコメントも更新され、新しい最小バッファサイズ(16バイト)の要件が明確に示されています。

src/pkg/bufio/bufio_test.go

  • const minReadBufferSize = 16: テストファイルにも同様にminReadBufferSize定数が定義されています。これにより、テストコードが本体のコードと同じ最小バッファサイズを参照し、一貫性を保つことができます。
  • bufsizes配列の更新:
    • 以前は2から始まる様々なバッファサイズがテストされていましたが、この変更により2が削除され、代わりにminReadBufferSize(16)が追加されました。これは、テストスイートが新しい最小バッファサイズ制約の下で適切に動作するかを検証するために重要です。
  • 各テスト関数の調整:
    • TestBufferFullTestPeekTestLineTooLongTestReadEmptyBufferTestLinesAfterReadTestReadLineNewlinesなど、多くのテスト関数で、以前は固定値や小さな値で設定されていたNewReaderSizeのバッファサイズがminReadBufferSizeに置き換えられています。
    • これらのテストでは、入力データや期待される結果も、新しい最小バッファサイズに合わせて調整されています。例えば、TestLineTooLongでは、生成されるテストデータの長さがminReadBufferSizeに基づいて動的に決定されるようになり、より堅牢なテストが可能になっています。これにより、bufioパッケージの各種機能が、16バイトの最小バッファサイズという新しい制約の下でも期待通りに動作することを確認しています。特に、ErrBufferFullの発生条件や、ReadLineが返すプレフィックスの挙動などが、新しいバッファサイズで正しく検証されるようになっています。

これらのテストの変更は、コードの変更が意図した通りに機能し、既存の機能が新しい制約の下でも壊れていないことを保証するための重要なステップです。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Gitの差分表示
  • Go言語のソースコードリポジトリ (GitHub)