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

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

このコミットは、Go言語の実験的なHTMLパーサーであるexp/htmlパッケージにおける重要な最適化と堅牢性向上を目的としています。具体的には、HTML要素のタグ名を文字列として比較する代わりに、内部的に整数値("アトム")として比較するように変更しています。これにより、パーサーのパフォーマンスが向上し、より効率的な処理が可能になります。

コミット

commit c8fac7b9676a84778280b44684e76f930e7f0bd0
Author: Nigel Tao <nigeltao@golang.org>
Date:   Thu Jun 7 13:46:57 2012 +1000

    exp/html: when parsing, compare atoms (ints) instead of strings.
    
    This is the mechanical part of the 2-part change that started with
    https://golang.org/cl/6305053/
    
    R=rsc
    CC=andybalholm, golang-dev, r
    https://golang.org/cl/6295055

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

https://github.com/golang/go/commit/c8fac7b9676a84778280b44684e76f930e7f0bd0

元コミット内容

exp/html: when parsing, compare atoms (ints) instead of strings.

このコミットは、HTMLパーサーがタグ名を比較する際に、文字列ではなくアトム(整数)を使用するように変更します。これは、https://golang.org/cl/6305053/ で始まった2段階の変更の機械的な部分です。

変更の背景

HTMLパーシングにおいて、要素のタグ名(例: <div>, <span>, <p>)は頻繁に比較されます。従来のパーサーでは、これらのタグ名を文字列として直接比較していました。しかし、文字列比較は、特に大量の要素を処理する場合に、パフォーマンスのボトルネックとなる可能性があります。文字列比較は文字ごとに比較を行うため、計算コストが高く、メモリ割り当ても発生しやすいです。

この問題を解決するため、Go言語のexp/htmlパッケージでは、HTMLタグ名を一意の整数値("アトム")にマッピングするアプローチが導入されました。これにより、文字列比較を整数比較に置き換えることが可能になり、パーシング処理の効率と速度が大幅に向上します。

このコミットは、その変更の「機械的な部分」であり、アトムの導入によって可能になった、既存のコードベースにおける文字列比較から整数比較への置き換えを大規模に行っています。参照されているhttps://golang.org/cl/6305053/は、このアトム化の概念と、a.Atom型および関連する定数の導入に関する最初の変更セットです。このコミットは、その先行する変更によって提供された新しいAPIとデータ構造を、パーサーのロジック全体に適用するものです。

前提知識の解説

HTMLパーシングの基本

HTMLパーシングとは、HTMLドキュメントを読み込み、その構造を解析して、プログラムが扱えるデータ構造(通常はDOMツリー)に変換するプロセスです。このプロセスでは、タグの開始・終了、属性の解析、テキストコンテンツの抽出など、様々な処理が行われます。パーサーは、HTML仕様に厳密に従ってドキュメントを解釈し、エラーを適切に処理する必要があります。

アトム化 (Atomization)

アトム化とは、頻繁に現れる文字列(この場合はHTMLタグ名)を、より効率的に比較できる一意の整数値にマッピングする技術です。例えば、HTMLのタグ名である"div""span""p"といった文字列は、それぞれa.Diva.Spana.Pといった整数定数に対応付けられます。

アトム化の主な利点は以下の通りです。

  1. 高速な比較: 文字列比較は文字数に比例して時間がかかりますが、整数比較は常に一定時間で完了します。これは、特にパーサーのように大量の文字列比較を行うシステムにおいて、顕著なパフォーマンス向上をもたらします。
  2. メモリ効率: 同じ文字列が複数回出現する場合でも、メモリ上にはその文字列のコピーが1つだけ存在し、各参照は対応する整数値を持つだけで済みます。これにより、メモリ使用量を削減できます。
  3. タイプセーフティ: 特定の文字列が特定の意味を持つことをコード上で明示的に表現できるため、誤った文字列が使用されるリスクを減らし、コードの可読性と保守性を向上させます。

Go言語におけるexp/htmlパッケージ

exp/htmlパッケージは、Go言語でHTML5の仕様に準拠したパーサーを提供する実験的なパッケージです。このパッケージは、WebブラウザがHTMLを解析するのと同じアルゴリズム(HTML5パーシングアルゴリズム)を実装することを目指しています。このパッケージは、Go標準ライブラリのhtmlパッケージの基盤となりました。

技術的詳細

このコミットの核心は、src/pkg/exp/html/parse.goファイルにおけるHTMLタグ名の比較方法の変更です。

  1. a.Atom型の導入と使用:

    • 以前は、HTMLタグ名はstring型として扱われ、Node構造体のDataフィールドに格納されていました。
    • この変更により、Node構造体にDataAtom a.Atomという新しいフィールドが追加されました。a.Atomは、HTMLタグ名に対応する整数定数を定義した型です。
    • 例えば、a.Html, a.Table, a.Tdなどは、それぞれHTMLの<html>, <table>, <td>タグに対応する一意の整数値です。
  2. 文字列比較から整数比較への置き換え:

    • パーサー内の多くの場所で、タグ名を比較するためにswitch n.Datat == tagのような文字列比較が使用されていました。
    • これらの比較は、switch n.DataAtomt == tagAtomのように、a.Atom型の値を用いた整数比較に置き換えられました。
    • これにより、パーシングの各段階で発生するタグ名の比較処理が劇的に高速化されます。
  3. 関数のシグネチャ変更:

    • タグ名を受け取る関数のシグネチャも変更されました。例えば、popUntil(s scope, matchTags ...string)popUntil(s scope, matchTags ...a.Atom)に変更され、文字列の可変長引数ではなく、アトムの可変長引数を受け取るようになりました。
    • addElement関数も、タグ名を文字列とアトムの両方で受け取るように変更され、内部でDataAtomフィールドを設定するようになりました。
  4. defaultScopeStopTagsの型変更:

    • HTMLパーシングのスコープ管理で使用されるdefaultScopeStopTagsマップのキーと値の型が、map[string][]stringからmap[string][]a.Atomに変更されました。これにより、このマップもアトムベースの比較を利用できるようになりました。

これらの変更は、HTMLパーサーの内部ロジック全体にわたって適用されており、タグ名の処理方法の根本的な変更を反映しています。

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

変更は主にsrc/pkg/exp/html/parse.goファイルに集中しています。

  • defaultScopeStopTagsの定義:

    --- a/src/pkg/exp/html/parse.go
    +++ b/src/pkg/exp/html/parse.go
    @@ -54,10 +54,10 @@ func (p *parser) top() *Node {
     
     // Stop tags for use in popUntil. These come from section 12.2.3.2.
     var (
    -	defaultScopeStopTags = map[string][]string{
    -		"":     {"applet", "caption", "html", "table", "td", "th", "marquee", "object"},
    -		"math": {"annotation-xml", "mi", "mn", "mo", "ms", "mtext"},
    -		"svg":  {"desc", "foreignObject", "title"},
    +	defaultScopeStopTags = map[string][]a.Atom{
    +		"":     {a.Applet, a.Caption, a.Html, a.Table, a.Td, a.Th, a.Marquee, a.Object},
    +		"math": {a.AnnotationXml, a.Mi, a.Mn, a.Mo, a.Ms, a.Mtext},
    +		"svg":  {a.Desc, a.ForeignObject, a.Title},
     	}
     )
    
  • popUntil関数のシグネチャ変更と内部比較:

    --- a/src/pkg/exp/html/parse.go
    +++ b/src/pkg/exp/html/parse.go
    @@ -90,7 +90,7 @@ const (
     // no higher element in the stack that was also in the stop tags). For example,
     // popUntil(tableScope, "table") returns true and leaves:
     // ["html", "body", "font"]
    -func (p *parser) popUntil(s scope, matchTags ...string) bool {
    +func (p *parser) popUntil(s scope, matchTags ...a.Atom) bool {
     	if i := p.indexOfElementInScope(s, matchTags...); i != -1 {
     		p.oe = p.oe[:i]
     		return true
    @@ -101,12 +101,12 @@ func (p *parser) popUntil(s scope, matchTags ...string) bool {
     // indexOfElementInScope returns the index in p.oe of the highest element whose
     // tag is in matchTags that is in scope. If no matching element is in scope, it
     // returns -1.
    -func (p *parser) indexOfElementInScope(s scope, matchTags ...string) int {
    +func (p *parser) indexOfElementInScope(s scope, matchTags ...a.Atom) int {
     	for i := len(p.oe) - 1; i >= 0; i-- {
    -		tag := p.oe[i].Data
    +		tagAtom := p.oe[i].DataAtom
     		if p.oe[i].Namespace == "" {
     			for _, t := range matchTags {
    -				if t == tag {
    +				if t == tagAtom {
     					return i
     				}
     			}
    
  • addElement関数のシグネチャ変更とDataAtomの設定:

    --- a/src/pkg/exp/html/parse.go
    +++ b/src/pkg/exp/html/parse.go
    @@ -278,17 +278,20 @@ func (p *parser) addText(text string) {
     }
     
     // addElement calls addChild with an element node.
    -func (p *parser) addElement(tag string, attr []Attribute) {
    +// TODO: tagAtom, tag and attr are almost always p.tok.DataAtom, p.tok.Data, p.tok.Attr.
    +// The common case should be a no-arg addElement method.
    +func (p *parser) addElement(tagAtom a.Atom, tag string, attr []Attribute) {
     	p.addChild(&Node{
    -		Type: ElementNode,
    -		Data: tag, // TODO: also set DataAtom.
    -		Attr: attr,
    +		Type:     ElementNode,
    +		DataAtom: tagAtom,
    +		Data:     tag,
    +		Attr:     attr,
     	})
     }
     
     // Section 12.2.3.3.
    -func (p *parser) addFormattingElement(tag string, attr []Attribute) {
    -	p.addElement(tag, attr)
    +func (p *parser) addFormattingElement(tagAtom a.Atom, tag string, attr []Attribute) {
    +	p.addElement(tagAtom, tag, attr)
     
     	// Implement the Noah's Ark clause, but with three per family instead of two.
     	identicalElements := 0
    
  • パーシングロジック内のswitch文でのDataAtomの使用:

    --- a/src/pkg/exp/html/parse.go
    +++ b/src/pkg/exp/html/parse.go
    @@ -410,28 +413,28 @@ func (p *parser) resetInsertionMode() {
     		tn = p.context
     	}
     
    -	switch n.Data {
    -	case "select":
    +	switch n.DataAtom {
    +	case a.Select:
     		p.im = inSelectIM
    -	case "td", "th":
    +	case a.Td, a.Th:
     		p.im = inCellIM
    -	case "tr":
    +	case a.Tr:
     		p.im = inRowIM
    -	case "tbody", "thead", "tfoot":
    +	case a.Tbody, a.Thead, a.Tfoot:
     		p.im = inTableBodyIM
    -	case "caption":
    +	case a.Caption:
     		p.im = inCaptionIM
    -	case "colgroup":
    +	case a.Colgroup:
     		p.im = inColumnGroupIM
    -	case "table":
    +	case a.Table:
     		p.im = inTableIM
    -	case "head":
    +	case a.Head:
     		p.im = inBodyIM
    -	case "body":
    +	case a.Body:
     		p.im = inBodyIM
    -	case "frameset":
    +	case a.Frameset:
     		p.im = inFramesetIM
    -	case "html":
    +	case a.Html:
     		p.im = beforeHeadIM
     	default:
     		continue
    

これらの変更は、parse.goファイル全体にわたって数百箇所に及び、文字列ベースのタグ名処理からアトムベースの処理への完全な移行を示しています。

コアとなるコードの解説

このコミットの主要な変更は、HTMLパーサーがタグ名を扱う方法を根本的に変えることです。

  1. Node.DataAtomフィールド:

    • HTMLドキュメントの各要素はNode構造体で表現されます。以前は、タグ名がNode.Datastring型)に格納されていました。
    • この変更により、Node構造体にDataAtom a.Atomという新しいフィールドが追加されました。a.Atomは、exp/html/atomパッケージで定義された整数定数であり、各HTMLタグ名に対応します。
    • これにより、タグ名を文字列として保持するだけでなく、そのアトム表現も保持するようになります。
  2. a.Atom定数による比較:

    • パーサーのロジックでは、現在のトークンやスタック上の要素のタグ名に基づいて、様々な条件分岐や処理が行われます。
    • 例えば、switch p.tok.Data(トークンの文字列データで分岐)やif tag == "table"(文字列比較)のようなコードが多数存在しました。
    • これらの文字列比較は、switch p.tok.DataAtomif tagAtom == a.Tableのように、対応するa.Atom定数を用いた整数比較に置き換えられました。
    • 整数比較はCPUサイクルが少なく、文字列比較に比べてはるかに高速です。これは、特に大規模なHTMLドキュメントを解析する際に、パーサー全体のパフォーマンスに大きな影響を与えます。
  3. 関数の引数としてのa.Atom:

    • popUntil, indexOfElementInScope, elementInScopeなどの関数は、以前はタグ名をstring型の可変長引数として受け取っていました。
    • これらの関数のシグネチャが変更され、a.Atom型の可変長引数を受け取るようになりました。これにより、関数内部でのタグ名比較もアトムベースで行われるようになります。
  4. addElementaddFormattingElementの変更:

    • これらの関数は、新しい要素ノードを作成し、それをパーサーのスタックに追加する役割を担います。
    • 変更後、これらの関数はタグの文字列表現(tag string)とアトム表現(tagAtom a.Atom)の両方を受け取るようになりました。
    • これにより、作成されるNodeにはDataDataAtomの両方のフィールドが適切に設定され、後続の処理でアトムベースの比較が利用できるようになります。

このコミットは、HTMLパーシングのパフォーマンスを向上させるための重要なステップであり、Go言語のexp/htmlパッケージがより効率的で堅牢なパーサーへと進化する上で不可欠な変更でした。

関連リンク

参考にした情報源リンク

このコミットは、Go言語の実験的なHTMLパーサーであるexp/htmlパッケージにおける重要な最適化と堅牢性向上を目的としています。具体的には、HTML要素のタグ名を文字列として比較する代わりに、内部的に整数値("アトム")として比較するように変更しています。これにより、パーサーのパフォーマンスが向上し、より効率的な処理が可能になります。

コミット

commit c8fac7b9676a84778280b44684e76f930e7f0bd0
Author: Nigel Tao <nigeltao@golang.org>
Date:   Thu Jun 7 13:46:57 2012 +1000

    exp/html: when parsing, compare atoms (ints) instead of strings.
    
    This is the mechanical part of the 2-part change that started with
    https://golang.org/cl/6305053/
    
    R=rsc
    CC=andybalholm, golang-dev, r
    https://golang.org/cl/6295055

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

https://github.com/golang/go/commit/c8fac7b9676a84778280b44684e76f930e7f0bd0

元コミット内容

exp/html: when parsing, compare atoms (ints) instead of strings.

このコミットは、HTMLパーサーがタグ名を比較する際に、文字列ではなくアトム(整数)を使用するように変更します。これは、https://golang.org/cl/6305053/ で始まった2段階の変更の機械的な部分です。

変更の背景

HTMLパーシングにおいて、要素のタグ名(例: <div>, <span>, <p>)は頻繁に比較されます。従来のパーサーでは、これらのタグ名を文字列として直接比較していました。しかし、文字列比較は、特に大量の要素を処理する場合に、パフォーマンスのボトルネックとなる可能性があります。文字列比較は文字ごとに比較を行うため、計算コストが高く、メモリ割り当ても発生しやすいです。

この問題を解決するため、Go言語のexp/htmlパッケージでは、HTMLタグ名を一意の整数値("アトム")にマッピングするアプローチが導入されました。これにより、文字列比較を整数比較に置き換えることが可能になり、パーシング処理の効率と速度が大幅に向上します。

このコミットは、その変更の「機械的な部分」であり、アトムの導入によって可能になった、既存のコードベースにおける文字列比較から整数比較への置き換えを大規模に行っています。参照されているhttps://golang.org/cl/6305053/は、このアトム化の概念と、a.Atom型および関連する定数の導入に関する最初の変更セットです。このコミットは、その先行する変更によって提供された新しいAPIとデータ構造を、パーサーのロジック全体に適用するものです。

前提知識の解説

HTMLパーシングの基本

HTMLパーシングとは、HTMLドキュメントを読み込み、その構造を解析して、プログラムが扱えるデータ構造(通常はDOMツリー)に変換するプロセスです。このプロセスでは、タグの開始・終了、属性の解析、テキストコンテンツの抽出など、様々な処理が行われます。パーサーは、HTML仕様に厳密に従ってドキュメントを解釈し、エラーを適切に処理する必要があります。

アトム化 (Atomization)

アトム化とは、頻繁に現れる文字列(この場合はHTMLタグ名)を、より効率的に比較できる一意の整数値にマッピングする技術です。例えば、HTMLのタグ名である"div""span""p"といった文字列は、それぞれa.Diva.Spana.Pといった整数定数に対応付けられます。

アトム化の主な利点は以下の通りです。

  1. 高速な比較: 文字列比較は文字数に比例して時間がかかりますが、整数比較は常に一定時間で完了します。これは、特にパーサーのように大量の文字列比較を行うシステムにおいて、顕著なパフォーマンス向上をもたらします。
  2. メモリ効率: 同じ文字列が複数回出現する場合でも、メモリ上にはその文字列のコピーが1つだけ存在し、各参照は対応する整数値を持つだけで済みます。これにより、メモリ使用量を削減できます。
  3. タイプセーフティ: 特定の文字列が特定の意味を持つことをコード上で明示的に表現できるため、誤った文字列が使用されるリスクを減らし、コードの可読性と保守性を向上させます。

Go言語におけるexp/htmlパッケージ

exp/htmlパッケージは、Go言語でHTML5の仕様に準拠したパーサーを提供する実験的なパッケージです。このパッケージは、WebブラウザがHTMLを解析するのと同じアルゴリズム(HTML5パーシングアルゴリズム)を実装することを目指しています。このパッケージは、Go標準ライブラリのhtmlパッケージの基盤となりました。

技術的詳細

このコミットの核心は、src/pkg/exp/html/parse.goファイルにおけるHTMLタグ名の比較方法の変更です。

  1. a.Atom型の導入と使用:

    • 以前は、HTMLタグ名はstring型として扱われ、Node構造体のDataフィールドに格納されていました。
    • この変更により、Node構造体にDataAtom a.Atomという新しいフィールドが追加されました。a.Atomは、HTMLタグ名に対応する整数定数を定義した型です。
    • 例えば、a.Html, a.Table, a.Tdなどは、それぞれHTMLの<html>, <table>, <td>タグに対応する一意の整数値です。
  2. 文字列比較から整数比較への置き換え:

    • パーサー内の多くの場所で、タグ名を比較するためにswitch n.Datat == tagのような文字列比較が使用されていました。
    • これらの比較は、switch n.DataAtomt == tagAtomのように、a.Atom型の値を用いた整数比較に置き換えられました。
    • これにより、パーシングの各段階で発生するタグ名の比較処理が劇的に高速化されます。
  3. 関数のシグネチャ変更:

    • タグ名を受け取る関数のシグネチャも変更されました。例えば、popUntil(s scope, matchTags ...string)popUntil(s scope, matchTags ...a.Atom)に変更され、文字列の可変長引数ではなく、アトムの可変長引数を受け取るようになりました。
    • addElement関数も、タグ名を文字列とアトムの両方で受け取るように変更され、内部でDataAtomフィールドを設定するようになりました。
  4. defaultScopeStopTagsの型変更:

    • HTMLパーシングのスコープ管理で使用されるdefaultScopeStopTagsマップのキーと値の型が、map[string][]stringからmap[string][]a.Atomに変更されました。これにより、このマップもアトムベースの比較を利用できるようになりました。

これらの変更は、HTMLパーサーの内部ロジック全体にわたって適用されており、タグ名の処理方法の根本的な変更を反映しています。

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

変更は主にsrc/pkg/exp/html/parse.goファイルに集中しています。

  • defaultScopeStopTagsの定義:

    --- a/src/pkg/exp/html/parse.go
    +++ b/src/pkg/exp/html/parse.go
    @@ -54,10 +54,10 @@ func (p *parser) top() *Node {
     
     // Stop tags for use in popUntil. These come from section 12.2.3.2.
     var (
    -	defaultScopeStopTags = map[string][]string{
    -		"":     {"applet", "caption", "html", "table", "td", "th", "marquee", "object"},
    -		"math": {"annotation-xml", "mi", "mn", "mo", "ms", "mtext"},
    -		"svg":  {"desc", "foreignObject", "title"},
    +	defaultScopeStopTags = map[string][]a.Atom{
    +		"":     {a.Applet, a.Caption, a.Html, a.Table, a.Td, a.Th, a.Marquee, a.Object},
    +		"math": {a.AnnotationXml, a.Mi, a.Mn, a.Mo, a.Ms, a.Mtext},
    +		"svg":  {a.Desc, a.ForeignObject, a.Title},
     	}
     )
    
  • popUntil関数のシグネチャ変更と内部比較:

    --- a/src/pkg/exp/html/parse.go
    +++ b/src/pkg/exp/html/parse.go
    @@ -90,7 +90,7 @@ const (
     // no higher element in the stack that was also in the stop tags). For example,
     // popUntil(tableScope, "table") returns true and leaves:
     // ["html", "body", "font"]
    -func (p *parser) popUntil(s scope, matchTags ...string) bool {
    +func (p *parser) popUntil(s scope, matchTags ...a.Atom) bool {
     	if i := p.indexOfElementInScope(s, matchTags...); i != -1 {
     		p.oe = p.oe[:i]
     		return true
    @@ -101,12 +101,12 @@ func (p *parser) popUntil(s scope, matchTags ...string) bool {
     // indexOfElementInScope returns the index in p.oe of the highest element whose
     // tag is in matchTags that is in scope. If no matching element is in scope, it
     // returns -1.
    -func (p *parser) indexOfElementInScope(s scope, matchTags ...string) int {
    +func (p *parser) indexOfElementInScope(s scope, matchTags ...a.Atom) int {
     	for i := len(p.oe) - 1; i >= 0; i-- {
    -		tag := p.oe[i].Data
    +		tagAtom := p.oe[i].DataAtom
     		if p.oe[i].Namespace == "" {
     			for _, t := range matchTags {
    -				if t == tag {
    +				if t == tagAtom {
     					return i
     				}
     			}
    
  • addElement関数のシグネチャ変更とDataAtomの設定:

    --- a/src/pkg/exp/html/parse.go
    +++ b/src/pkg/exp/html/parse.go
    @@ -278,17 +278,20 @@ func (p *parser) addText(text string) {
     }
     
     // addElement calls addChild with an element node.
    -func (p *parser) addElement(tag string, attr []Attribute) {
    +// TODO: tagAtom, tag and attr are almost always p.tok.DataAtom, p.tok.Data, p.tok.Attr.
    +// The common case should be a no-arg addElement method.
    +func (p *parser) addElement(tagAtom a.Atom, tag string, attr []Attribute) {
     	p.addChild(&Node{
    -		Type: ElementNode,
    -		Data: tag, // TODO: also set DataAtom.
    -		Attr: attr,
    +		Type:     ElementNode,
    +		DataAtom: tagAtom,
    +		Data:     tag,
    +		Attr:     attr,
     	})
     }
     
     // Section 12.2.3.3.
    -func (p *parser) addFormattingElement(tag string, attr []Attribute) {
    -	p.addElement(tag, attr)
    +func (p *parser) addFormattingElement(tagAtom a.Atom, tag string, attr []Attribute) {
    +	p.addElement(tagAtom, tag, attr)
     
     	// Implement the Noah's Ark clause, but with three per family instead of two.
     	identicalElements := 0
    
  • パーシングロジック内のswitch文でのDataAtomの使用:

    --- a/src/pkg/exp/html/parse.go
    +++ b/src/pkg/exp/html/parse.go
    @@ -410,28 +413,28 @@ func (p *parser) resetInsertionMode() {
     		tn = p.context
     	}
     
    -	switch n.Data {
    -	case "select":
    +	switch n.DataAtom {
    +	case a.Select:
     		p.im = inSelectIM
    -	case "td", "th":
    +	case a.Td, a.Th:
     		p.im = inCellIM
    -	case "tr":
    +	case a.Tr:
     		p.im = inRowIM
    -	case "tbody", "thead", "tfoot":
    +	case a.Tbody, a.Thead, a.Tfoot:
     		p.im = inTableBodyIM
    -	case "caption":
    +	case a.Caption:
     		p.im = inCaptionIM
    -	case "colgroup":
    +	case a.Colgroup:
     		p.im = inColumnGroupIM
    -	case "table":
    +	case a.Table:
     		p.im = inTableIM
    -	case "head":
    +	case a.Head:
     		p.im = inBodyIM
    -	case "body":
    +	case a.Body:
     		p.im = inBodyIM
    -	case "frameset":
    +	case a.Frameset:
     		p.im = inFramesetIM
    -	case "html":
    +	case a.Html:
     		p.im = beforeHeadIM
     	default:
     		continue
    

これらの変更は、parse.goファイル全体にわたって数百箇所に及び、文字列ベースのタグ名処理からアトムベースの処理への完全な移行を示しています。

コアとなるコードの解説

このコミットの主要な変更は、HTMLパーサーがタグ名を扱う方法を根本的に変えることです。

  1. Node.DataAtomフィールド:

    • HTMLドキュメントの各要素はNode構造体で表現されます。以前は、タグ名がNode.Datastring型)に格納されていました。
    • この変更により、Node構造体にDataAtom a.Atomという新しいフィールドが追加されました。a.Atomは、exp/html/atomパッケージで定義された整数定数であり、各HTMLタグ名に対応します。
    • これにより、タグ名を文字列として保持するだけでなく、そのアトム表現も保持するようになります。
  2. a.Atom定数による比較:

    • パーサーのロジックでは、現在のトークンやスタック上の要素のタグ名に基づいて、様々な条件分岐や処理が行われます。
    • 例えば、switch p.tok.Data(トークンの文字列データで分岐)やif tag == "table"(文字列比較)のようなコードが多数存在しました。
    • これらの文字列比較は、switch p.tok.DataAtomif tagAtom == a.Tableのように、対応するa.Atom定数を用いた整数比較に置き換えられました。
    • 整数比較はCPUサイクルが少なく、文字列比較に比べてはるかに高速です。これは、特に大規模なHTMLドキュメントを解析する際に、パーサー全体のパフォーマンスに大きな影響を与えます。
  3. 関数の引数としてのa.Atom:

    • popUntil, indexOfElementInScope, elementInScopeなどの関数は、以前はタグ名をstring型の可変長引数として受け取っていました。
    • これらの関数のシグネチャが変更され、a.Atom型の可変長引数を受け取るようになりました。これにより、関数内部でのタグ名比較もアトムベースで行われるようになります。
  4. addElementaddFormattingElementの変更:

    • これらの関数は、新しい要素ノードを作成し、それをパーサーのスタックに追加する役割を担います。
    • 変更後、これらの関数はタグの文字列表現(tag string)とアトム表現(tagAtom a.Atom)の両方を受け取るようになりました。
    • これにより、作成されるNodeにはDataDataAtomの両方のフィールドが適切に設定され、後続の処理でアトムベースの比較が利用できるようになります。

このコミットは、HTMLパーシングのパフォーマンスを向上させるための重要なステップであり、Go言語のexp/htmlパッケージがより効率的で堅牢なパーサーへと進化する上で不可欠な変更でした。

関連リンク

参考にした情報源リンク