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

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

このコミットは、Go言語の実験的なHTMLパーシングライブラリである exp/html パッケージに、トークナイザーとパーサーのベンチマークを追加するものです。これにより、HTML処理のパフォーマンス特性を測定し、将来的な最適化のための基準を提供します。特に、Go 1リリースノートのHTML版 (go1.html) をベンチマークの入力データとして使用し、実際のドキュメントに対するパフォーマンスを評価しています。

コミット

commit 034fa90dc113994cbbc4e31ae6a2f7c9d06ed1eb
Author: Nigel Tao <nigeltao@golang.org>
Date:   Wed May 30 13:00:32 2012 +1000

    exp/html: add some tokenizer and parser benchmarks.
    
    $GOROOT/src/pkg/exp/html/testdata/go1.html is an execution of the
    $GOROOT/doc/go1.html template by godoc.
    
    Sample numbers on my linux,amd64 desktop:
    BenchmarkParser      500           4699198 ns/op          16.63 MB/s
    --- BENCH: BenchmarkParser
            parse_test.go:409: 1 iterations, 14653 mallocs per iteration
            parse_test.go:409: 100 iterations, 14651 mallocs per iteration
            parse_test.go:409: 500 iterations, 14651 mallocs per iteration
    BenchmarkRawLevelTokenizer          2000            904957 ns/op          86.37 MB/s
    --- BENCH: BenchmarkRawLevelTokenizer
            token_test.go:657: 1 iterations, 28 mallocs per iteration
            token_test.go:657: 100 iterations, 28 mallocs per iteration
            token_test.go:657: 2000 iterations, 28 mallocs per iteration
    BenchmarkLowLevelTokenizer          2000           1134300 ns/op          68.91 MB/s
    --- BENCH: BenchmarkLowLevelTokenizer
            token_test.go:657: 1 iterations, 41 mallocs per iteration
            token_test.go:657: 100 iterations, 41 mallocs per iteration
            token_test.go:657: 2000 iterations, 41 mallocs per iteration
    BenchmarkHighLevelTokenizer         1000           2096179 ns/op          37.29 MB/s
    --- BENCH: BenchmarkHighLevelTokenizer
            token_test.go:657: 1 iterations, 6616 mallocs per iteration
            token_test.go:657: 100 iterations, 6616 mallocs per iteration
            token_test.go:657: 1000 iterations, 6616 mallocs per iteration
    
    R=rsc
    CC=andybalholm, golang-dev, r
    https://golang.org/cl/6257067

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

https://github.com/golang/go/commit/034fa90dc113994cbbc4e31ae6a2f7c9d06ed1eb

元コミット内容

このコミットは、Go言語の exp/html パッケージに、HTMLトークナイザーとパーサーのベンチマークを追加することを目的としています。コミットメッセージには、ベンチマークの入力データとして $GOROOT/src/pkg/exp/html/testdata/go1.html が使用されていることが明記されています。この go1.html は、$GOROOT/doc/go1.html テンプレートを godoc が実行した結果生成されたものであると説明されています。

さらに、コミットメッセージには、linux/amd64 環境でのベンチマークのサンプル結果が詳細に記載されています。これには、BenchmarkParserBenchmarkRawLevelTokenizerBenchmarkLowLevelTokenizerBenchmarkHighLevelTokenizer の4つのベンチマークが含まれており、それぞれ ns/op (操作あたりのナノ秒)、MB/s (1秒あたりのメガバイト処理量)、および mallocs per iteration (イテレーションあたりのメモリ確保回数) のメトリクスが示されています。これらの数値は、各コンポーネントのパフォーマンスとメモリ使用量のベースラインを提供します。

変更の背景

この変更の背景には、Go言語のHTMLパーシングライブラリ exp/html の性能評価と最適化の必要性があります。exp パッケージは、Go 1リリース時点では標準化されていない実験的なパッケージであり、その性能特性を理解し、安定したGo 1リリースの一部として提供される前に改善の余地があるかを判断することが重要でした。

特に、Go 1のリリースノート自体がHTMLドキュメントとして提供されることからもわかるように、HTMLのパースはWebアプリケーションやツールにおいて基本的な機能です。そのため、この機能の効率性と堅牢性はGo言語エコシステムにとって非常に重要です。ベンチマークを追加することで、開発者はコード変更がパフォーマンスに与える影響を定量的に評価できるようになり、リグレッションの検出や最適化の推進に役立ちます。

また、コミットメッセージに記載されている go1.html は、Go 1リリースノートのHTML版であり、実際の複雑なHTMLドキュメントをベンチマークの入力として使用することで、より現実的なパフォーマンス測定が可能になります。これは、単なる合成データではなく、実際のユースケースに近いシナリオでの性能を把握するための重要なステップです。

前提知識の解説

Go言語のベンチマーク

Go言語には、標準ライブラリ testing パッケージにベンチマーク機能が組み込まれています。ベンチマーク関数は BenchmarkXxx という命名規則に従い、*testing.B 型の引数を取ります。

  • testing.B: ベンチマーク実行のためのコンテキストを提供します。
    • b.N: ベンチマーク関数が実行されるイテレーション回数。Goのテストフレームワークが自動的に調整し、安定した測定結果が得られるようにします。
    • b.SetBytes(n int64): 処理されたバイト数を設定します。これにより、MB/s (1秒あたりのメガバイト処理量) のメトリクスが計算されます。
    • b.ResetTimer(): タイマーをリセットします。セットアップコードの時間を測定から除外するために使用されます。
    • b.StopTimer(): タイマーを停止します。クリーンアップコードの時間を測定から除外するために使用されます。
    • b.Logf(format string, args ...interface{}): ベンチマーク結果にログメッセージを出力します。
  • ns/op: 1操作あたりのナノ秒。処理速度の指標です。値が小さいほど高速です。
  • MB/s: 1秒あたりのメガバイト処理量。データ処理スループットの指標です。値が大きいほど高速です。
  • mallocs per iteration: 1イテレーションあたりのメモリ確保回数。メモリ割り当ての効率性を示します。値が小さいほどメモリ効率が良いことを意味します。runtime.ReadMemStats を使用して、ベンチマーク実行前後のメモリ統計を比較することで計算されます。

HTMLパーシングの基本

HTMLパーシングは、HTMLドキュメントを解析し、その構造と内容をコンピュータが理解できる形式(通常はDOMツリー)に変換するプロセスです。このプロセスは通常、以下の主要な段階に分けられます。

  1. トークナイゼーション (Lexing): 入力されたHTML文字列を、意味のある最小単位である「トークン」のストリームに分解します。例えば、<p> は開始タグトークン、Hello は文字データトークン、</p> は終了タグトークンになります。
  2. ツリー構築 (Parsing): トークンのストリームを受け取り、それらをHTMLの仕様に基づいてDOM(Document Object Model)ツリーとして構築します。この段階で、タグのネスト関係や属性などが解析され、ツリー構造が形成されます。

exp/html パッケージ

exp/html は、Go言語でHTMLを解析するための実験的なパッケージです。Go 1リリース時点では、まだ標準ライブラリの一部として安定版には含まれていませんでしたが、HTML処理の重要なコンポーネントとして開発が進められていました。このパッケージは、HTML5の仕様に準拠した堅牢なパーサーを提供することを目指しています。

ioutil.ReadFilebytes.NewBuffer

  • ioutil.ReadFile(filename string) ([]byte, error): 指定されたファイルの内容をすべて読み込み、バイトスライスとして返します。
  • bytes.NewBuffer(buf []byte) *Buffer: バイトスライスから新しい Buffer を作成します。Bufferio.Reader インターフェースを実装しており、Parse 関数のような io.Reader を引数に取る関数にバイトデータを渡すのに便利です。

runtime.GC()runtime.ReadMemStats

  • runtime.GC(): ガベージコレクタを手動で実行します。ベンチマークの前に呼び出すことで、以前のテストやセットアップによるメモリ割り当ての影響を最小限に抑え、よりクリーンな状態でメモリ使用量を測定できます。
  • runtime.ReadMemStats(m *MemStats): 現在のメモリ割り当て統計を MemStats 構造体に読み込みます。これにより、ベンチマーク実行中に発生したメモリ割り当て(mallocs)の数を正確に追跡できます。

技術的詳細

このコミットで追加されたベンチマークは、exp/html パッケージの主要なコンポーネントであるトークナイザーとパーサーの性能を評価するために設計されています。

BenchmarkParser

このベンチマークは、exp/html パッケージのHTMLパーサー全体の性能を測定します。

  1. ioutil.ReadFile("testdata/go1.html") で、Go 1リリースノートのHTMLコンテンツを読み込みます。このファイルは、実際のWebページに近いサイズと複雑さを持つため、現実的なシナリオでのパーサーの性能を評価するのに適しています。
  2. b.SetBytes(int64(len(buf))) で、処理されるバイト数を設定します。これにより、MB/s のスループットが計算されます。
  3. runtime.GC() を呼び出してガベージコレクションを実行し、メモリ状態をクリーンにします。
  4. runtime.ReadMemStats(&ms) でベンチマーク開始前のメモリ統計を取得し、mallocs の初期値を記録します。
  5. b.ResetTimer() でタイマーをリセットし、ファイル読み込みなどのセットアップ時間を測定から除外します。
  6. ループ内で Parse(bytes.NewBuffer(buf)) を呼び出し、読み込んだHTMLコンテンツを繰り返しパースします。
  7. b.StopTimer() でタイマーを停止します。
  8. 再度 runtime.ReadMemStats(&ms) を呼び出してベンチマーク終了後のメモリ統計を取得し、mallocs の差分から1イテレーションあたりのメモリ確保回数を計算します。

トークナイザーのベンチマーク (BenchmarkRawLevelTokenizer, BenchmarkLowLevelTokenizer, BenchmarkHighLevelTokenizer)

これらのベンチマークは、HTMLトークナイザーの異なる抽象度レベルでの性能を測定します。HTMLパーシングは通常、まず入力ストリームをトークンに分解するトークナイゼーションの段階を経るため、トークナイザーの性能は全体のパーシング性能に大きく影響します。

  • BenchmarkRawLevelTokenizer: 最も低レベルのトークナイゼーション性能を測定します。これは、HTML仕様に厳密に従い、生のバイトストリームから直接トークンを生成する処理に焦点を当てていると考えられます。サンプル結果では、mallocs per iteration が非常に少なく、効率的な処理が行われていることが示唆されます。
  • BenchmarkLowLevelTokenizer: RawLevelTokenizer よりもわずかに高レベルのトークナイゼーション性能を測定します。これは、おそらく一部の基本的な前処理や状態管理が含まれる可能性がありますが、まだ比較的低オーバーヘッドな処理です。
  • BenchmarkHighLevelTokenizer: 最も高レベルのトークナイゼーション性能を測定します。これは、より複雑なロジック、例えば文字エンコーディングの処理、特殊文字のエスケープ解除、またはより多くの状態管理が含まれる可能性があります。サンプル結果では、mallocs per iteration が他のトークナイザーベンチマークと比較して大幅に増加しており、より多くのメモリ割り当てが発生していることがわかります。

これらのベンチマークは、token_test.go 内で定義されており、go1.html を入力として使用して、各トークナイザーの実装がどれだけ効率的に動作するかを評価します。異なるレベルのトークナイザーのベンチマークを比較することで、どの抽象度レベルでパフォーマンスボトルネックが存在するのか、あるいはメモリ割り当てが集中しているのかを特定するのに役立ちます。

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

このコミットでは、主に以下の3つのファイルが変更されています。

  1. src/pkg/exp/html/parse_test.go:

    • import 文に io/ioutilruntime が追加されています。
    • BenchmarkParser 関数が追加されています。この関数は、testdata/go1.html を読み込み、exp/html パッケージの Parse 関数をベンチマークします。メモリ割り当ての追跡も行っています。
  2. src/pkg/exp/html/testdata/go1.html:

    • 新しいファイルとして追加されています。このファイルは、Go 1リリースノートのHTMLコンテンツを含んでおり、ベンチマークの入力データとして使用されます。そのサイズは2237行にも及び、実際のWebページに近い複雑さを持っています。
  3. src/pkg/exp/html/token_test.go:

    • import 文に io/ioutilruntime が追加されています。
    • BenchmarkRawLevelTokenizerBenchmarkLowLevelTokenizerBenchmarkHighLevelTokenizer の各ベンチマーク関数が追加されています。これらの関数は、testdata/go1.html を読み込み、exp/html パッケージの異なるレベルのトークナイザーの性能をベンチマークします。こちらもメモリ割り当ての追跡を含んでいます。

コアとなるコードの解説

parse_test.go における BenchmarkParser

func BenchmarkParser(b *testing.B) {
	buf, err := ioutil.ReadFile("testdata/go1.html")
	if err != nil {
		b.Fatalf("could not read testdata/go1.html: %v", err)
	}
	b.SetBytes(int64(len(buf)))
	runtime.GC() // ガベージコレクションを実行し、メモリをクリーンにする
	var ms runtime.MemStats
	runtime.ReadMemStats(&ms) // ベンチマーク開始前のメモリ統計を取得
	mallocs := ms.Mallocs
	b.ResetTimer() // タイマーをリセットし、セットアップ時間を測定から除外
	for i := 0; i < b.N; i++ {
		Parse(bytes.NewBuffer(buf)) // HTMLコンテンツをパース
	}
	b.StopTimer() // タイマーを停止
	runtime.ReadMemStats(&ms) // ベンチマーク終了後のメモリ統計を取得
	mallocs = ms.Mallocs - mallocs
	b.Logf("%d iterations, %d mallocs per iteration\n", b.N, int(mallocs)/b.N) // メモリ割り当て数をログに出力
}

この関数は、go1.html ファイルを読み込み、その内容を bytes.NewBuffer を介して Parse 関数に渡し、HTMLドキュメントのパース処理をベンチマークします。b.SetBytes で処理バイト数を設定し、runtime.GC()runtime.ReadMemStats を利用して、パース処理中のメモリ割り当て回数を詳細に測定しています。これにより、パーサーの速度だけでなく、メモリ効率も評価できます。

token_test.go におけるトークナイザーベンチマーク

token_test.go に追加された3つのベンチマーク関数 (BenchmarkRawLevelTokenizer, BenchmarkLowLevelTokenizer, BenchmarkHighLevelTokenizer) は、それぞれ異なるレベルのHTMLトークナイゼーション処理を測定します。これらの関数も BenchmarkParser と同様に go1.html を入力として使用し、b.SetBytesruntime.ReadMemStats を用いて、処理速度とメモリ割り当てを測定します。

これらのベンチマークは、exp/html パッケージの内部実装におけるトークナイゼーションの各段階の性能を個別に評価することを可能にします。例えば、RawLevelTokenizer が非常に少ないメモリ割り当てで高速に動作する一方で、HighLevelTokenizer がより多くのメモリを消費し、速度が低下する場合、それは高レベルの処理に最適化の余地があることを示唆します。

これらのベンチマークの追加は、exp/html パッケージの性能特性を深く理解し、将来的な改善のための具体的なデータを提供することを目的としています。

関連リンク

参考にした情報源リンク

  • Go 1 Release Notes (コミットに含まれる go1.html の内容)
  • Go言語の公式ドキュメントおよびパッケージリファレンス
  • Go言語のベンチマークに関する一般的な知識
  • HTML5パーシングアルゴリズムの概要 (一般的な知識)