[インデックス 11151] ファイルの概要
このコミットは、Go言語の標準ライブラリである encoding/xml
パッケージに対する大規模な改修です。Go 1のリリースに向けて、XMLマーシャリング(Goのデータ構造からXMLへの変換)とアンマーシャリング(XMLからGoのデータ構造への変換)のインターフェースと内部実装を大幅に改善し、既存のバグを修正し、パフォーマンスを向上させることを目的としています。特に、Goの慣習に合わせたインターフェースの統一、Marshal
とUnmarshal
の一貫性の向上、そして型メタデータのキャッシュによる処理速度の劇的な改善が主要な変更点です。
コミット
commit 1627b46eaa6403775611017e91cceae2e45662b2
Author: Gustavo Niemeyer <gustavo@niemeyer.net>
Date: Fri Jan 13 11:05:19 2012 +0100
xml: major Go 1 fixup
This CL improves the xml package in the following ways:
- makes its interface match established conventions
- brings Marshal and Unmarshal closer together
- fixes a large number of bugs and adds tests
- improves speed significantly
- organizes and simplifies the code
Fixes #2426.
Fixes #2406.
Fixes #1989.
What follows is a detailed list of those changes.
- All matching is case sensitive without special processing
to the field name or xml tag in an attempt to match them.
Customize the field tag as desired to match the correct XML
elements.
- Flags are ",flag" rather than "flag". The names "attr",
"chardata", etc, may be used to name actual XML elements.
- Overriding of attribute names is possible with "name,attr".
- Attribute fields are marshalled properly if they have
non-string types. Previously they were unmarshalled, but were
ignored at marshalling time.
- Comment fields tagged with ",comment" are marshalled properly,
rather than being marshalled as normal fields.
- The handling of the Any field has been replaced by the ",any"
flag to avoid unexpected results when using the field name for
other purposes, and has also been fixed to interact properly
with name paths. Previously the feature would not function
if any field in the type had a name path in its tag.
- Embedded struct support fixed and cleaned so it works when
marshalling and also when using field paths deeper than one level.
- Conflict reporting on field names have been expanded to cover
all fields. Previously it'd catch only conflicts of paths
deeper than one level. Also interacts correctly with embedded
structs now.
- A trailing '>' is disallowed in xml tags. It used to be
supported for removing the ambiguity between "attr" and "attr>",
but the marshalling support for that was broken, and it's now
unnecessary. Use "name" instead of "name>".
- Fixed docs to point out that a XMLName doesn't have to be
an xml.Name (e.g. a struct{} is a good fit too). The code was
already working like that.
- Fixed asymmetry in the precedence of XML element names between
marshalling and unmarshalling. Marshal would consider the XMLName
of the field type before the field tag, while unmarshalling would
do the opposite. Now both respect the tag of the XMLName field
first, and a nice error message is provided in case an attempt
is made to name a field with its tag in a way that would
conflict with the underlying type's XMLName field.
- Do not marshal broken "<???>" tags when in doubt. Use the type
name, and error out if that's not possible.
- Do not break down unmarshalling if there's an interface{} field
in a struct.
- Significant speed boost due to caching of type metadata and
overall allocation clean ups. The following timings reflect
processing of the the atom test data:
Old:
BenchmarkMarshal 50000 48798 ns/op
BenchmarkUnmarshal 5000 357174 ns/op
New:
BenchmarkMarshal 100000 19799 ns/op
BenchmarkUnmarshal 10000 128525 ns/op
R=cw, gustavo, kevlar, adg, rogpeppe, fullung, christoph, rsc
CC=golang-dev
https://golang.org/cl/5503078
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1627b46eaa6403775611017e91cceae2e45662b2
元コミット内容
このコミットは、Go言語の encoding/xml
パッケージをGo 1のリリースに向けて大幅に改善することを目的としています。主な改善点は以下の通りです。
- インターフェースを確立された慣習に合わせる
Marshal
とUnmarshal
の動作をより一貫させる- 多数のバグを修正し、テストを追加する
- 処理速度を大幅に向上させる
- コードを整理し、簡素化する
このコミットは、以下のIssueを修正します。
- Fixes #2426.
- Fixes #2406.
- Fixes #1989.
以下に、これらの変更の詳細なリストを示します。
-
すべてのマッチングは、フィールド名やXMLタグを一致させようとする特殊な処理なしに、大文字と小文字を区別して行われます。正しいXML要素に一致させるには、フィールドタグを適切にカスタマイズしてください。
-
フラグは「,flag」形式になり、「flag」形式ではなくなりました。「attr」、「chardata」などの名前は、実際のXML要素の名前として使用できるようになりました。
-
属性名のオーバーライドは、「name,attr」形式で可能になりました。
-
属性フィールドが非文字列型の場合でも、適切にマーシャリングされるようになりました。以前はアンマーシャリングされていましたが、マーシャリング時には無視されていました。
-
「,comment」タグが付けられたコメントフィールドは、通常のフィールドとしてマーシャリングされるのではなく、適切にマーシャリングされるようになりました。
-
Any
フィールドの処理は、「,any」フラグに置き換えられました。これにより、フィールド名を他の目的で使用する際の予期せぬ結果を回避し、名前パスと適切に連携するように修正されました。以前は、型のいずれかのフィールドにタグに名前パスが含まれている場合、この機能は機能しませんでした。 -
埋め込み構造体のサポートが修正され、マーシャリング時および1レベルより深いフィールドパスを使用する場合にも機能するようにクリーンアップされました。
-
フィールド名の競合報告がすべてのフィールドをカバーするように拡張されました。以前は、1レベルより深いパスの競合のみを検出していました。また、埋め込み構造体とも正しく連携するようになりました。
-
XMLタグの末尾の「>」は許可されなくなりました。「attr」と「attr>」の間の曖昧さを解消するために以前はサポートされていましたが、そのマーシャリングサポートは壊れており、現在は不要です。「name>」の代わりに「name」を使用してください。
-
XMLName
がxml.Name
である必要がないこと(例:struct{}
も適している)を指摘するようにドキュメントが修正されました。コードはすでにそのように機能していました。 -
マーシャリングとアンマーシャリングの間でXML要素名の優先順位の非対称性が修正されました。
Marshal
はフィールドタグの前にフィールド型のXMLName
を考慮していましたが、Unmarshal
は逆でした。現在では、両方ともXMLName
フィールドのタグを最初に尊重し、フィールドにそのタグで名前を付けようとした場合に、基になる型のXMLName
フィールドと競合する可能性がある場合に、適切なエラーメッセージが提供されます。 -
疑わしい場合は、壊れた「??>」タグをマーシャリングしないようにしました。型名を使用し、それが不可能な場合はエラーを返します。
-
構造体に
interface{}
フィールドがある場合でも、アンマーシャリングが中断されないようにしました。 -
型メタデータのキャッシュと全体的なアロケーションのクリーンアップにより、大幅な速度向上が実現しました。以下のタイミングは、atomテストデータの処理を反映しています。
旧:
BenchmarkMarshal 50000 48798 ns/op BenchmarkUnmarshal 5000 357174 ns/op
新:
BenchmarkMarshal 100000 19799 ns/op BenchmarkUnmarshal 10000 128525 ns/op
変更の背景
このコミットは、Go言語の初期バージョン(Go 1リリース前)における encoding/xml
パッケージのいくつかの問題に対処するために行われました。当時の encoding/xml
パッケージは、XMLのマーシャリングとアンマーシャリングにおいて、直感的でない動作やバグ、パフォーマンスの課題を抱えていました。
具体的には、コミットメッセージで言及されている以下のIssueが修正対象となっています。
- Issue #2426:
xml: Marshal and Unmarshal should be symmetric
: このIssueは、Marshal
とUnmarshal
の動作が対称的でないという根本的な問題を指摘しています。例えば、あるGoの構造体をXMLにマーシャリングし、そのXMLを再度Goの構造体にアンマーシャリングした際に、元の構造体と異なる結果になる場合がありました。これは、開発者がXMLデータを扱う上で予測不可能な挙動を引き起こし、信頼性を損なう大きな問題でした。 - Issue #2406:
xml: Marshal should not generate ??? tags
: このIssueは、Marshal
関数が、Goの構造体からXML要素名を決定できない場合に「??>」という無効なXMLタグを生成してしまう問題を指摘しています。これは、生成されるXMLが不正な形式となり、他のXMLパーサーで処理できない原因となっていました。 - Issue #1989:
xml: embedded structs are not marshalled
: このIssueは、Goの構造体における埋め込み構造体がXMLマーシャリング時に適切に処理されないというバグを指摘しています。埋め込み構造体はGoの強力な機能の一つであり、これがXML処理で正しく機能しないことは、開発の柔軟性を大きく制限していました。
これらの問題に加え、当時の encoding/xml
パッケージはパフォーマンス面でも改善の余地がありました。特に、大規模なXMLデータを扱う際に、処理速度がボトルネックとなることがありました。
このコミットは、Go 1の安定版リリースに向けて、これらの根本的な問題を解決し、encoding/xml
パッケージをより堅牢で、使いやすく、高性能なものにすることを目的としています。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語およびXMLに関する基本的な知識が必要です。
Go言語の構造体とタグ
Go言語の構造体(struct
)は、異なる型のフィールドをまとめるための複合データ型です。構造体の各フィールドには「タグ」(struct tag
)と呼ばれる文字列を付与することができます。このタグは、リフレクション(実行時に型情報を検査・操作する機能)を通じてアクセスでき、主にGoの標準ライブラリやサードパーティライブラリが、構造体のフィールドを外部データ形式(JSON、XML、データベースなど)にマッピングする際のメタデータとして利用します。
例:
type Person struct {
Name string `json:"person_name" xml:"name"`
Age int `json:"person_age" xml:"age,attr"` // XML属性として扱う
}
上記の例では、Name
フィールドにはjson:"person_name"
とxml:"name"
というタグが付与されており、Age
フィールドにはxml:"age,attr"
というタグが付与されています。encoding/xml
パッケージは、このタグを読み取り、XML要素名や属性名、その他の特殊な処理(例:属性、文字データ、コメントなど)を決定します。
XMLの基本構造
XML(Extensible Markup Language)は、データを構造化するためのマークアップ言語です。主な構成要素は以下の通りです。
- 要素(Element):
<tag>content</tag>
の形式で、XML文書の基本的な構成単位です。 - 属性(Attribute):
<tag attribute_name="value">
の形式で、要素に追加情報を与えます。 - 文字データ(Character Data): 要素の開始タグと終了タグの間に含まれるテキストデータです。
- コメント(Comment):
<!-- comment content -->
の形式で、XML文書内の注釈です。パーサーによって無視されます。 - 名前空間(Namespace): XML要素名や属性名の衝突を避けるために使用されます。
xmlns
属性で定義されます。
Goの encoding/xml
パッケージ
encoding/xml
パッケージは、Goの構造体とXML文書の間でデータを変換(マーシャリングとアンマーシャリング)するための機能を提供します。
xml.Marshal(v interface{}) ([]byte, error)
: Goの任意のデータ構造v
をXMLバイト列に変換します。xml.Unmarshal(data []byte, v interface{}) error
: XMLバイト列data
をGoのデータ構造v
に変換します。
このパッケージは、構造体タグを利用して、GoのフィールドとXML要素/属性のマッピングを制御します。
リフレクションとパフォーマンス
Goのリフレクションは、プログラムの実行時に型情報を検査し、値を動的に操作する強力な機能です。encoding/xml
パッケージも内部でリフレクションを多用して、構造体のフィールド情報やタグを読み取ります。しかし、リフレクションは一般的に、コンパイル時に型が確定している通常の操作に比べてオーバーヘッドが大きく、パフォーマンスに影響を与える可能性があります。そのため、頻繁にアクセスされる型情報などをキャッシュすることで、リフレクションのパフォーマンスコストを削減する最適化が重要になります。
技術的詳細
このコミットは、encoding/xml
パッケージの多くの側面を改善しており、その技術的詳細は多岐にわたります。
1. タグの慣習とセマンティクスの変更
- フラグの形式変更 (
",flag"
へ): 以前はxml:"attr"
のように直接フラグを指定していましたが、この変更によりxml:",attr"
のようにカンマで始まる形式になりました。これにより、xml:"attr"
のように指定した場合に、attr
という名前のXML要素として扱われるようになり、attr
という名前の属性として扱いたい場合はxml:",attr"
と明示的に指定する必要があるという明確な区別が導入されました。これは、タグのセマンティクスをより直感的にし、XML要素名と特殊な処理フラグの衝突を避けるための重要な変更です。 - 属性名のオーバーライド (
"name,attr"
): 属性としてマーシャリングされるフィールドに対して、Goのフィールド名とは異なるXML属性名を指定できるようになりました。例えば、xml:"my_attribute,attr"
とすることで、Goのフィールド名がMyAttribute
であっても、XML上ではmy_attribute
という属性名で出力されます。 - コメントフィールドの適切なマーシャリング (
",comment"
):xml:",comment"
タグが付けられたフィールドは、XMLコメントとして適切にマーシャリングされるようになりました。以前は通常の要素として扱われることがあり、XMLのセマンティクスに反していました。 Any
フィールドの置き換え (",any"
): 以前はAny
という特定のフィールド名が特殊な意味を持っていましたが、これがxml:",any"
というタグに置き換えられました。これにより、Any
というフィールド名を他の目的で使用しても、予期せぬ挙動が発生しなくなりました。また、名前パスとの連携も改善されました。
2. マーシャリングとアンマーシャリングの一貫性向上
- XML要素名の優先順位の対称性:
Marshal
とUnmarshal
の間で、XML要素名の決定ロジックに非対称性がありました。具体的には、Marshal
はXMLName
フィールドの型を優先し、Unmarshal
はフィールドタグを優先していました。このコミットでは、両者ともにXMLName
フィールドのタグを最優先するように統一されました。これにより、Goの構造体とXMLの間での変換がより予測可能で一貫性のあるものになりました。また、競合が発生する場合には、より分かりやすいエラーメッセージが提供されるようになりました。 - 非文字列型属性のマーシャリング: 以前は、
int
やbool
などの非文字列型のフィールドにattr
タグを付けても、アンマーシャリングはできましたが、マーシャリング時には無視されていました。このコミットにより、これらの非文字列型属性も適切にXML属性としてマーシャリングされるようになりました。 <???>
タグの廃止: XML要素名を決定できない場合にMarshal
が生成していた不正な<???>
タグは廃止されました。代わりに、Goの型名が使用され、それが不可能な場合はエラーが返されるようになりました。これにより、生成されるXMLの妥当性が保証されます。interface{}
フィールドのアンマーシャリングの改善: 構造体内にinterface{}
型のフィールドが存在する場合でも、アンマーシャリングが中断されなくなりました。
3. 堅牢性の向上
- 大文字・小文字の区別: すべてのXML要素名と属性名のマッチングは、特殊な処理なしに大文字・小文字を区別して行われるようになりました。これにより、XMLの仕様に厳密に準拠し、曖昧さを排除します。
- 埋め込み構造体のサポート強化: 埋め込み構造体のマーシャリングとアンマーシャリングが修正され、1レベルより深いフィールドパスでも正しく機能するようになりました。
- フィールド名競合報告の拡張: フィールド名の競合検出が改善され、すべてのフィールドをカバーするようになりました。特に、埋め込み構造体との競合も正しく検出されるようになりました。
- XMLタグの末尾の
>
の禁止:xml:"name>"
のようなタグ形式は、以前はattr
とattr>
の曖昧さを解消するために使用されていましたが、マーシャリングサポートが壊れており、このコミットで不要になったため禁止されました。
4. パフォーマンスの劇的な改善
- 型メタデータのキャッシュ (
typeinfo.go
の導入): このコミットの最も重要なパフォーマンス改善は、型メタデータのキャッシュの導入です。Goのリフレクションは強力ですが、実行時のオーバーヘッドがあります。encoding/xml
パッケージは、XMLのマーシャリングとアンマーシャリングのために、Goの構造体のフィールド情報、タグ、型情報などを頻繁にリフレクションで取得していました。typeinfo.go
ファイルが導入され、これらの型情報を一度解析してキャッシュすることで、同じ型の構造体が繰り返し処理される際にリフレクションのコストを大幅に削減できるようになりました。 - 全体的なアロケーションのクリーンアップ: メモリ割り当て(アロケーション)の最適化も行われました。不要なメモリ割り当てを減らすことで、ガベージコレクションの頻度を減らし、全体的な実行速度を向上させます。
コミットメッセージに記載されているベンチマーク結果は、これらの最適化が非常に効果的であったことを示しています。
ベンチマーク | 旧 (ns/op) | 新 (ns/op) | 改善率 (約) |
---|---|---|---|
BenchmarkMarshal | 48798 | 19799 | 2.46倍高速化 |
BenchmarkUnmarshal | 357174 | 128525 | 2.78倍高速化 |
これらの改善により、encoding/xml
パッケージはGo 1の標準ライブラリとして、より実用的で高性能なものとなりました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、以下のファイルに集中しています。
src/pkg/encoding/xml/marshal.go
: XMLマーシャリングのロジックを実装しているファイルです。タグの新しい慣習、属性の処理、コメントの処理、埋め込み構造体のサポート、XML要素名の優先順位の変更など、多くのマーシャリング関連の修正と改善が行われました。src/pkg/encoding/xml/read.go
: XMLアンマーシャリングのロジックを実装しているファイルです。タグの新しい慣習、属性の処理、コメントの処理、Any
フィールドの置き換え、埋め込み構造体のサポート、XML要素名の優先順位の変更など、多くのアンマーシャリング関連の修正と改善が行われました。src/pkg/encoding/xml/typeinfo.go
: 新規追加されたファイルです。Goの型情報を解析し、XMLマーシャリング/アンマーシャリングに必要なメタデータをキャッシュするためのロジックが実装されています。これがパフォーマンス向上の主要な要因です。src/pkg/encoding/xml/marshal_test.go
:marshal.go
の変更に対応するテストファイルです。新しいタグの慣習、属性、コメント、埋め込み構造体、XML要素名の優先順位など、多岐にわたるテストケースが追加・修正されました。特に、MarshalOnly
やUnmarshalOnly
といったフラグがテスト構造体に追加され、マーシャリングとアンマーシャリングの対称性を検証するテストが強化されています。src/pkg/encoding/xml/read_test.go
:read.go
の変更に対応するテストファイルです。アンマーシャリングの新しい挙動、特にパスベースのマッチングやエラーハンドリングに関するテストが更新されました。src/pkg/encoding/xml/atom_test.go
: AtomフィードのXML構造を扱うテストファイルです。XML要素名の大文字・小文字の区別に関する変更(例:<Title>
から<title>
への変更)が反映されています。src/pkg/encoding/xml/embed_test.go
: 埋め込み構造体に関する古いテストファイルで、このコミットで削除されました。新しいテストはmarshal_test.go
やread_test.go
に統合されています。src/pkg/encoding/xml/Makefile
:typeinfo.go
が追加されたため、ビルドプロセスに新しいファイルを含めるように更新されました。
これらのファイル間の変更は密接に関連しており、typeinfo.go
で生成されたメタデータがmarshal.go
とread.go
で利用され、それらの変更がテストファイルで検証されるという流れになっています。
コアとなるコードの解説
typeinfo.go
(新規追加)
このファイルは、Goの構造体のリフレクション情報を解析し、XMLのマーシャリングおよびアンマーシャリングに必要なメタデータ(フィールド名、タグ、属性、コメント、埋め込み構造体の情報など)を抽出してキャッシュする役割を担います。
主要な構造体と関数:
typeInfo
構造体: 特定のGoの型(reflect.Type
)に関するXML関連のメタデータを保持します。これには、XMLNameフィールドの情報、各フィールドのfieldInfo
のリストなどが含まれます。fieldInfo
構造体: 構造体の個々のフィールドに関するXML関連のメタデータ(XML要素名、名前空間、タグフラグ、インデックスなど)を保持します。getTypeInfo(typ reflect.Type) (*typeInfo, error)
関数: 指定されたreflect.Type
に対応するtypeInfo
を返します。この関数は、一度解析したtypeInfo
を内部のマップでキャッシュすることで、同じ型が繰り返し処理される際のパフォーマンスオーバーヘッドを削減します。
このキャッシュメカニズムにより、XMLのマーシャリング/アンマーシャリング処理のたびにリフレクションをフルに実行する必要がなくなり、コミットメッセージに示されているような劇的な速度向上が実現しました。
marshal.go
(マーシャリングロジックの変更)
marshal.go
では、主にprinter
構造体とmarshalValue
、marshalStruct
関数が変更されました。
marshalValue
関数のシグネチャ変更: 以前はmarshalValue(val reflect.Value, name string)
でしたが、marshalValue(val reflect.Value, finfo *fieldInfo)
に変更されました。これにより、マーシャリング時にフィールドのメタデータ(fieldInfo
)を直接利用できるようになり、XML要素名の決定ロジックがtypeinfo.go
で提供される情報に基づいて行われるようになりました。- XML要素名の決定ロジックの統一:
typeInfo
から取得した情報に基づいて、XML要素名の優先順位(XMLName
フィールドのタグ、フィールドタグ、型名)が統一的に適用されるようになりました。これにより、Unmarshal
との対称性が向上しました。 - 属性の処理の改善:
fAttr
フラグを持つフィールドが属性として適切にマーシャリングされるようになりました。非文字列型の属性もfmt.Sprint
を使用して文字列に変換され、マーシャリングされます。 - コメントの処理の追加:
fComment
フラグを持つフィールドが<!-- ... -->
形式のXMLコメントとしてマーシャリングされるようになりました。コメント内に--
が含まれる場合はエラーを返すバリデーションも追加されています。 marshalStruct
関数の導入: 構造体のフィールドを処理するロジックがmarshalStruct
という新しい関数に切り出されました。これにより、コードの整理と簡素化が図られています。この関数は、typeInfo
から取得した各フィールドのfieldInfo
を反復処理し、そのフラグ(fCharData
,fComment
,fInnerXml
,fElement
)に基づいて適切なマーシャリング処理を行います。
read.go
(アンマーシャリングロジックの変更)
read.go
では、主にParser
構造体とunmarshal
、unmarshalPath
関数が変更されました。
unmarshal
関数の変更:XMLName
フィールドの処理がtypeInfo
を利用するように変更され、マーシャリングとの対称性が確保されました。- 属性の処理も
typeInfo
のfAttr
フラグに基づいて行われるようになりました。 - 文字データ(
fCharData
)、コメント(fComment
)、内部XML(fInnerXml
)、任意の要素(fAny
)を保存するためのロジックが、対応するfieldInfo
のフラグに基づいて適切に設定されるようになりました。 interface{}
フィールドのアンマーシャリングが中断されないように、reflect.Interface
の場合の処理が追加されました。
unmarshalPath
関数の導入: 以前はunmarshalPaths
という関数がありましたが、より汎用的なunmarshalPath
関数が導入されました。この関数は、XML要素のパスを再帰的に探索し、typeInfo
から取得したフィールド情報に基づいて、対応するGoの構造体フィールドにXMLデータをアンマーシャリングします。これにより、パスベースのマッチングがより効率的かつ正確に行われるようになりました。- フィールド名変換ロジックの削除: 以前存在した
fieldName
関数(XML名からGoのフィールド名に変換し、小文字にするロジック)は削除されました。これは、マッチングが厳密に大文字・小文字を区別するようになったため、不要になったためです。
これらの変更により、encoding/xml
パッケージはGoの慣習に沿った、より堅牢で高性能なXML処理ライブラリへと進化しました。
関連リンク
- Go CL 5503078: https://golang.org/cl/5503078
参考にした情報源リンク
- Go Issue #2426:
xml: Marshal and Unmarshal should be symmetric
- https://github.com/golang/go/issues/2426 - Go Issue #2406:
xml: Marshal should not generate ??? tags
- https://github.com/golang/go/issues/2406 - Go Issue #1989:
xml: embedded structs are not marshalled
- https://github.com/golang/go/issues/1989 - Go Documentation:
encoding/xml
package - https://pkg.go.dev/encoding/xml (現在のドキュメントは変更後の内容を反映しています) - Go Blog: Go 1 and the Future of Go Programs - https://go.dev/blog/go1 (Go 1のリリースに関する一般的な情報)