[インデックス 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.Writer
のFlush
メソッドを呼び出すことで、バッファの内容を基になる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
を介して実際の書き込みが行われます。
-
コメントトークン (
Comment
) の処理部分:case Comment: p.WriteString("<!--") p.Write(t) p.WriteString("-->") // 変更前: return p.Flush() // 変更後: return p.cachedWriteError()
コメントトークンを書き込んだ後、以前は
p.Flush()
が呼び出され、バッファの内容が強制的に出力されていました。この変更により、p.cachedWriteError()
が返されるようになり、フラッシュは行われなくなりました。 -
開始要素トークン (
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ツールへのリンクです。)
参考にした情報源リンク
- Go言語の
encoding/xml
パッケージのドキュメント: https://pkg.go.dev/encoding/xml - Go言語の
bufio
パッケージのドキュメント (バッファリングの概念理解のため): https://pkg.go.dev/bufio - Go言語の
io
パッケージのドキュメント (Writerインターフェースの理解のため): https://pkg.go.dev/io - Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- 一般的なI/Oバッファリングに関する情報源 (例: Wikipedia, 技術ブログなど)