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

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

このコミットは、Go言語の標準ライブラリ encoding/xml パッケージにおける変更を取り消すものです。具体的には、以前のコミット (CL 13004046) で導入された、XMLトークンをエンコードするたびにバッファをフラッシュする挙動を元に戻し、バッファリングの目的を損なわないように修正しています。

コミット

commit 46f96079df8298796883bfb83832a9cc6f1d765d
Author: Rob Pike <r@golang.org>
Date:   Fri Sep 6 07:54:43 2013 +1000

    undo CL 13004046 / 5db14b33d6ef
    
    Flushing after every token negates the point of buffering. A different approach is required.
    
    ««« original CL description
    encoding/xml: flush buffer after encoding token
    
    R=rsc, bradfitz, adg
    CC=golang-dev
    https://golang.org/cl/13004046
    
    »»»
    
    R=golang-dev, adg, rsc
    CC=golang-dev
    https://golang.org/cl/13515043

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

https://github.com/golang/go/commit/46f96079df8298796883bfb83832a9cc6f1d765d

元コミット内容

このコミットは、以下の元のコミット (CL 13004046 / 5db14b33d6ef) の変更を取り消しています。

元のコミットメッセージ:

encoding/xml: flush buffer after encoding token

元のコミットは、encoding/xml パッケージの Encoder がXMLトークンをエンコードするたびに、内部バッファを強制的にフラッシュするように変更しました。これは、おそらくエンコードされたXMLデータがすぐに利用可能になることを保証するため、または特定のストリーミングシナリオでの要件を満たすために行われたと考えられます。

変更の背景

このコミットの背景には、元のコミット (CL 13004046) が導入した「XMLトークンをエンコードするたびにバッファをフラッシュする」という挙動が、Goの encoding/xml パッケージの設計思想とパフォーマンス特性に反するという認識があります。

コミットメッセージにある「Flushing after every token negates the point of buffering. A different approach is required.」(各トークンの後にフラッシュすることは、バッファリングの目的を無効にする。異なるアプローチが必要である。)という記述が、この変更の核心を突いています。

一般的に、I/O操作においてバッファリングはパフォーマンスを向上させるために用いられます。データを小さなチャンクで書き込むのではなく、ある程度の量をまとめてバッファに蓄え、バッファがいっぱいになったときや明示的にフラッシュされたときに一度に書き込むことで、システムコールやディスクI/Oの回数を減らし、オーバーヘッドを削減します。

元のコミットのように、各XMLトークン(要素の開始タグ、終了タグ、テキストノードなど)の後に強制的にフラッシュを行うと、バッファリングの利点が失われます。これは、実質的にバッファリングを行わないのと同等になり、I/O操作が頻繁に発生するため、エンコーディングのパフォーマンスが著しく低下する可能性があります。

このため、Rob Pike氏(Go言語の共同開発者の一人)は、このアプローチが適切ではないと判断し、元の変更を取り消すことを決定しました。これは、パフォーマンスと設計の整合性を重視した判断と言えます。

前提知識の解説

1. Go言語の encoding/xml パッケージ

encoding/xml パッケージは、Go言語でXMLデータをエンコード(Goのデータ構造からXMLへ変換)およびデコード(XMLからGoのデータ構造へ変換)するための機能を提供します。このパッケージは、XMLの構造(要素、属性、テキスト、コメント、処理命令など)をGoの構造体やインターフェースにマッピングする機能を持っています。

2. xml.Encoder

xml.Encoder は、Goの値をXML形式で出力ストリームに書き込むための型です。通常、NewEncoder 関数を使って io.Writer インターフェースを実装するオブジェクト(例: bytes.Buffer, os.Stdout, net.Conn など)を渡して作成されます。

3. Encoder.EncodeToken メソッド

EncodeToken メソッドは、xml.Token インターフェースを実装する単一のXMLトークンをエンコーダの出力に書き込みます。xml.Token には StartElement, EndElement, CharData, Comment, ProcInst などがあります。このメソッドは、XMLドキュメントをトークン単位で細かく制御しながら構築する際に使用されます。

4. バッファリングとI/Oパフォーマンス

コンピュータシステムにおけるI/O(Input/Output)操作は、CPUの処理速度に比べて非常に低速です。ファイルシステムへの書き込みやネットワークへの送信など、物理的なデバイスとのやり取りには時間がかかります。

バッファリングは、このI/Oの遅延を緩和するための一般的な技術です。

  • 書き込みバッファリング: データを直接ターゲットに書き込むのではなく、まずメモリ上のバッファに一時的に蓄えます。バッファがいっぱいになったり、明示的にフラッシュ命令が発行されたり、プログラムが終了したりする際に、バッファの内容がまとめてターゲットに書き込まれます。これにより、I/O操作の回数を減らし、全体のスループットを向上させることができます。
  • フラッシュ (Flush): バッファに蓄えられたデータを、強制的にターゲット(ファイル、ネットワークなど)に書き出す操作です。フラッシュは、データの一貫性や即時性を保証する必要がある場合に重要ですが、頻繁に行うとバッファリングの利点が失われ、パフォーマンスが低下します。

5. p.Flush()p.cachedWriteError()

encoding/xml パッケージの内部には、printer という構造体があり、これが実際のXMLデータの書き込みを担当しています。

  • p.Flush(): これは、printer の内部バッファを強制的に出力ストリームに書き出すメソッドです。元のコミットでは、各トークンエンコード後にこれを呼び出すことで、即時フラッシュを実現していました。
  • p.cachedWriteError(): これは、printer が書き込み中に発生したエラーを返すためのメソッドです。このメソッド自体はフラッシュ操作を行いません。代わりに、書き込み操作が成功したかどうか、または以前に発生した書き込みエラーがあるかどうかを確認するために使用されます。このコミットでは、p.Flush() の代わりに p.cachedWriteError() を返すことで、不要なフラッシュを削除し、バッファリングの挙動を復元しています。

技術的詳細

このコミットの技術的な変更は、encoding/xml パッケージの marshal.go ファイル内の Encoder.EncodeToken メソッドに集中しています。

元のコミット (CL 13004046) では、EncodeToken メソッドがXMLトークン(特にコメント Comment と開始要素 StartElement)を処理した後、p.Flush() を呼び出して内部の printer バッファを強制的にフラッシュしていました。

このコミットでは、その p.Flush() の呼び出しを p.cachedWriteError() に置き換えています。

  • 変更前 (p.Flush()): p.Flush() は、printer 構造体が持つ bufio.WriterFlush メソッドを呼び出すことで、バッファの内容を基になる io.Writer に書き出します。これにより、各トークンがエンコードされるたびに、XMLデータが即座に出力ストリームに書き込まれることになります。これは、バッファリングの目的(I/O操作の回数を減らすこと)に反し、パフォーマンスの低下を招きます。

  • 変更後 (p.cachedWriteError()): p.cachedWriteError() は、printer がこれまでの書き込み操作でエラーを記録していた場合にそのエラーを返します。エラーがなければ nil を返します。このメソッドは、バッファをフラッシュする機能は持っていません。これにより、EncodeToken はトークンをバッファに書き込むだけで、明示的なフラッシュは行わなくなります。フラッシュは、バッファがいっぱいになったときや、Encoder のライフサイクルにおける適切なタイミング(例: Encoder.Flush() メソッドが呼び出されたとき、または Encoder が閉じられるとき)にのみ行われるようになります。

この変更により、encoding/xml のエンコーダは、より効率的なバッファリング戦略に戻り、XMLデータの生成におけるI/Oオーバーヘッドを削減し、全体的なパフォーマンスを向上させます。

また、src/pkg/encoding/xml/marshal_test.go から TestEncodeTokenFlush というテスト関数が削除されています。このテストは、おそらく元のコミットで導入された即時フラッシュの挙動を検証するためのものでした。即時フラッシュの挙動が取り消されたため、このテストも不要となり削除されました。

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

src/pkg/encoding/xml/marshal.go

--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -196,6 +196,7 @@ func (enc *Encoder) EncodeToken(t Token) error {
 	\tp.WriteString(\"<!--\")
 	\tp.Write(t)
 	\tp.WriteString(\"-->\")
+\t\treturn p.cachedWriteError()\n // 追加: コメントトークン処理後のフラッシュを削除
 	case ProcInst:\
 	\tif t.Target == \"xml\" || !isNameString(t.Target) {\
 	\t\treturn fmt.Errorf(\"xml: EncodeToken of ProcInst with invalid Target\")\
@@ -218,7 +219,7 @@ func (enc *Encoder) EncodeToken(t Token) error {\
 	\tp.Write(t)\
 	\tp.WriteString(\">\")\
 	}\
-\treturn p.Flush()\n // 削除: 開始要素トークン処理後のフラッシュを削除
+\treturn p.cachedWriteError()\n // 追加: 開始要素トークン処理後のフラッシュを削除
 }\

 type printer struct {

src/pkg/encoding/xml/marshal_test.go

--- a/src/pkg/encoding/xml/marshal_test.go
+++ b/src/pkg/encoding/xml/marshal_test.go
@@ -1076,15 +1076,6 @@ func TestMarshalWriteIOErrors(t *testing.T) {\
 	}\
 }\
 \
-func TestEncodeTokenFlush(t *testing.T) {\n // 削除: 即時フラッシュを検証するテスト関数を削除
-\tvar buf bytes.Buffer\
-\tenc := NewEncoder(&buf)\
-\tenc.EncodeToken(StartElement{Name: Name{Local: \"some-tag\"}})\
-\tif g, w := buf.String(), \"<some-tag>\"; g != w {\
-\t\tt.Errorf(\"Encoder wrote %q, want %q\", g, w)\
-\t}\
-}\
-\n func BenchmarkMarshal(b *testing.B) {\
 \tfor i = 0; i < b.N; i++ {\
 \t\tMarshal(atomValue)\

コアとなるコードの解説

src/pkg/encoding/xml/marshal.go の変更

Encoder.EncodeToken メソッドは、XMLトークンをエンコードする際の主要なロジックを含んでいます。このメソッド内で、printer 型の p を介して実際の書き込みが行われます。

  1. コメントトークン (Comment) の処理部分:

    case Comment:
        p.WriteString("<!--")
        p.Write(t)
        p.WriteString("-->")
        // 変更前: return p.Flush()
        // 変更後: return p.cachedWriteError()
    

    コメントトークンを書き込んだ後、以前は p.Flush() が呼び出され、バッファの内容が強制的に出力されていました。この変更により、p.cachedWriteError() が返されるようになり、フラッシュは行われなくなりました。

  2. 開始要素トークン (StartElement) の処理部分:

    // ... (StartElement の属性や名前の書き込みロジック) ...
    p.Write(t)
    p.WriteString(">")
    // 変更前: return p.Flush()
    // 変更後: return p.cachedWriteError()
    

    開始要素トークンを書き込んだ後も同様に、p.Flush()p.cachedWriteError() に置き換えられました。

これらの変更により、EncodeToken メソッドは、XMLトークンを内部バッファに書き込むだけで、その都度出力ストリームへのフラッシュは行わなくなります。これにより、バッファリングの効率が回復し、I/O操作の回数が減ることで、XMLエンコーディングのパフォーマンスが向上します。

src/pkg/encoding/xml/marshal_test.go の変更

TestEncodeTokenFlush テスト関数が完全に削除されました。

func TestEncodeTokenFlush(t *testing.T) {
    var buf bytes.Buffer
    enc := NewEncoder(&buf)
    enc.EncodeToken(StartElement{Name: Name{Local: "some-tag"}})
    if g, w := buf.String(), "<some-tag>"; g != w {
        t.Errorf("Encoder wrote %q, want %q", g, w)
    }
}

このテストは、EncodeToken が呼び出された直後に bytes.Buffer にデータが書き込まれる(つまり、即座にフラッシュされる)ことを検証していました。しかし、このコミットで即時フラッシュの挙動が取り消されたため、このテストはもはや現在の実装の意図を反映しておらず、削除されました。これは、コードの変更に合わせてテストも適切に更新されたことを示しています。

関連リンク

  • 元のコミット (CL 13004046): https://golang.org/cl/13004046 (このリンクは古いGo Code Reviewツールへのものであり、現在はアクセスできない可能性があります。しかし、コミットメッセージに記載されているため、参考として挙げます。)
  • このコミット (CL 13515043): https://golang.org/cl/13515043 (こちらも同様に古いGo Code Reviewツールへのリンクです。)

参考にした情報源リンク