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

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

このコミットは、Go言語の標準ライブラリである encoding/xml パッケージに対する大規模な改修です。Go 1のリリースに向けて、XMLマーシャリング(Goのデータ構造からXMLへの変換)とアンマーシャリング(XMLからGoのデータ構造への変換)のインターフェースと内部実装を大幅に改善し、既存のバグを修正し、パフォーマンスを向上させることを目的としています。特に、Goの慣習に合わせたインターフェースの統一、MarshalUnmarshalの一貫性の向上、そして型メタデータのキャッシュによる処理速度の劇的な改善が主要な変更点です。

コミット

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のリリースに向けて大幅に改善することを目的としています。主な改善点は以下の通りです。

  • インターフェースを確立された慣習に合わせる
  • MarshalUnmarshalの動作をより一貫させる
  • 多数のバグを修正し、テストを追加する
  • 処理速度を大幅に向上させる
  • コードを整理し、簡素化する

このコミットは、以下の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」を使用してください。

  • XMLNamexml.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は、MarshalUnmarshalの動作が対称的でないという根本的な問題を指摘しています。例えば、ある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要素名の優先順位の対称性: MarshalUnmarshalの間で、XML要素名の決定ロジックに非対称性がありました。具体的には、MarshalXMLNameフィールドの型を優先し、Unmarshalはフィールドタグを優先していました。このコミットでは、両者ともにXMLNameフィールドのタグを最優先するように統一されました。これにより、Goの構造体とXMLの間での変換がより予測可能で一貫性のあるものになりました。また、競合が発生する場合には、より分かりやすいエラーメッセージが提供されるようになりました。
  • 非文字列型属性のマーシャリング: 以前は、intboolなどの非文字列型のフィールドにattrタグを付けても、アンマーシャリングはできましたが、マーシャリング時には無視されていました。このコミットにより、これらの非文字列型属性も適切にXML属性としてマーシャリングされるようになりました。
  • <???>タグの廃止: XML要素名を決定できない場合にMarshalが生成していた不正な<???>タグは廃止されました。代わりに、Goの型名が使用され、それが不可能な場合はエラーが返されるようになりました。これにより、生成されるXMLの妥当性が保証されます。
  • interface{}フィールドのアンマーシャリングの改善: 構造体内にinterface{}型のフィールドが存在する場合でも、アンマーシャリングが中断されなくなりました。

3. 堅牢性の向上

  • 大文字・小文字の区別: すべてのXML要素名と属性名のマッチングは、特殊な処理なしに大文字・小文字を区別して行われるようになりました。これにより、XMLの仕様に厳密に準拠し、曖昧さを排除します。
  • 埋め込み構造体のサポート強化: 埋め込み構造体のマーシャリングとアンマーシャリングが修正され、1レベルより深いフィールドパスでも正しく機能するようになりました。
  • フィールド名競合報告の拡張: フィールド名の競合検出が改善され、すべてのフィールドをカバーするようになりました。特に、埋め込み構造体との競合も正しく検出されるようになりました。
  • XMLタグの末尾の>の禁止: xml:"name>"のようなタグ形式は、以前はattrattr>の曖昧さを解消するために使用されていましたが、マーシャリングサポートが壊れており、このコミットで不要になったため禁止されました。

4. パフォーマンスの劇的な改善

  • 型メタデータのキャッシュ (typeinfo.goの導入): このコミットの最も重要なパフォーマンス改善は、型メタデータのキャッシュの導入です。Goのリフレクションは強力ですが、実行時のオーバーヘッドがあります。encoding/xmlパッケージは、XMLのマーシャリングとアンマーシャリングのために、Goの構造体のフィールド情報、タグ、型情報などを頻繁にリフレクションで取得していました。typeinfo.goファイルが導入され、これらの型情報を一度解析してキャッシュすることで、同じ型の構造体が繰り返し処理される際にリフレクションのコストを大幅に削減できるようになりました。
  • 全体的なアロケーションのクリーンアップ: メモリ割り当て(アロケーション)の最適化も行われました。不要なメモリ割り当てを減らすことで、ガベージコレクションの頻度を減らし、全体的な実行速度を向上させます。

コミットメッセージに記載されているベンチマーク結果は、これらの最適化が非常に効果的であったことを示しています。

ベンチマーク旧 (ns/op)新 (ns/op)改善率 (約)
BenchmarkMarshal48798197992.46倍高速化
BenchmarkUnmarshal3571741285252.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要素名の優先順位など、多岐にわたるテストケースが追加・修正されました。特に、MarshalOnlyUnmarshalOnlyといったフラグがテスト構造体に追加され、マーシャリングとアンマーシャリングの対称性を検証するテストが強化されています。
  • 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.goread_test.goに統合されています。
  • src/pkg/encoding/xml/Makefile: typeinfo.goが追加されたため、ビルドプロセスに新しいファイルを含めるように更新されました。

これらのファイル間の変更は密接に関連しており、typeinfo.goで生成されたメタデータがmarshal.goread.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構造体とmarshalValuemarshalStruct関数が変更されました。

  • 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構造体とunmarshalunmarshalPath関数が変更されました。

  • unmarshal関数の変更:
    • XMLNameフィールドの処理がtypeInfoを利用するように変更され、マーシャリングとの対称性が確保されました。
    • 属性の処理もtypeInfofAttrフラグに基づいて行われるようになりました。
    • 文字データ(fCharData)、コメント(fComment)、内部XML(fInnerXml)、任意の要素(fAny)を保存するためのロジックが、対応するfieldInfoのフラグに基づいて適切に設定されるようになりました。
    • interface{}フィールドのアンマーシャリングが中断されないように、reflect.Interfaceの場合の処理が追加されました。
  • unmarshalPath関数の導入: 以前はunmarshalPathsという関数がありましたが、より汎用的なunmarshalPath関数が導入されました。この関数は、XML要素のパスを再帰的に探索し、typeInfoから取得したフィールド情報に基づいて、対応するGoの構造体フィールドにXMLデータをアンマーシャリングします。これにより、パスベースのマッチングがより効率的かつ正確に行われるようになりました。
  • フィールド名変換ロジックの削除: 以前存在したfieldName関数(XML名からGoのフィールド名に変換し、小文字にするロジック)は削除されました。これは、マッチングが厳密に大文字・小文字を区別するようになったため、不要になったためです。

これらの変更により、encoding/xmlパッケージはGoの慣習に沿った、より堅牢で高性能なXML処理ライブラリへと進化しました。

関連リンク

参考にした情報源リンク