[インデックス 15996] ファイルの概要
このコミットは、Go言語の標準ライブラリであるgo/printer
パッケージ内のコード整形処理において、コメントのテキストを行に分割する既存のカスタム関数split
を、strings
パッケージの標準関数strings.Split
に置き換える変更です。この変更は、strings
パッケージのパフォーマンス改善により、カスタム実装の必要性がなくなったことを示しています。
コミット
commit f38811979cefe7cd61b34c5580e2d10f65f243db
Author: Robert Griesemer <gri@golang.org>
Date: Thu Mar 28 15:47:39 2013 -0700
go/printer: use strings.Split instead of specialized code
With the faster strings package, the difference between
the specialized code and strings.Split is in the noise:
benchmark old ns/op new ns/op delta
BenchmarkPrint 16724291 16686729 -0.22%
(Measured on a Mac Pro, 2.8GHz Quad-core Intel Xeon,
4GB 800 MHz DDR2, Mac OS X 10.8.3)
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/8100044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f38811979cefe7cd61b34c5580e2d10f65f243db
元コミット内容
go/printer: use strings.Split instead of specialized code
このコミットは、go/printer
パッケージにおいて、特殊なコード(カスタムのsplit
関数)の代わりにstrings.Split
を使用するように変更します。これは、strings
パッケージが高速化されたため、特殊なコードとstrings.Split
のパフォーマンス差が無視できるレベルになったためです。ベンチマーク結果として、BenchmarkPrint
においてわずかながらパフォーマンスが向上していることが示されています(-0.22%)。
変更の背景
Go言語のgo/printer
パッケージは、Goのソースコードを整形(フォーマット)するためのツールです。このパッケージは、コメントを含むコード要素を適切に配置し、読みやすい形式で出力する役割を担っています。
以前のバージョンでは、コメントテキストを改行文字(\n
)で行に分割する際に、標準ライブラリのstrings.Split
関数ではなく、printer.go
内に独自に実装されたsplit
関数が使用されていました。これは、当時のstrings.Split
のパフォーマンスが、この特定の用途においてはカスタム実装よりも劣っていたためと考えられます。コメント内のコードには、// (using strings.Split(text, "\\n") is significantly slower for // this specific purpose, as measured with: go test -bench=Print)
という記述があり、実際にベンチマークによってそのパフォーマンス差が確認されていたことが示されています。
しかし、このコミットが作成された時点(2013年3月)までに、Go言語のstrings
パッケージ、特にstrings.Split
関数の内部実装が最適化され、そのパフォーマンスが大幅に向上しました。その結果、カスタムのsplit
関数を使用するメリットが失われ、むしろ標準ライブラリの関数を使用する方がコードの簡潔性、保守性、そしてGo言語のイディオムに沿うという点で望ましい状況になりました。
コミットメッセージに記載されているベンチマーク結果(BenchmarkPrint
で-0.22%の改善)は、この変更がパフォーマンスに悪影響を与えないどころか、わずかながら改善をもたらすことを裏付けています。これは、標準ライブラリの進化が、特定の最適化されたカスタムコードを不要にする典型的な例と言えます。
前提知識の解説
- Go言語の
go/printer
パッケージ: Go言語のソースコードを整形するためのパッケージです。go fmt
コマンドの基盤としても利用されており、AST(抽象構文木)を解析し、Goの公式なスタイルガイドに沿ってコードを再構築・出力します。コメントの整形もその重要な機能の一つです。 strings.Split
関数: Go言語の標準ライブラリstrings
パッケージに含まれる関数で、指定された区切り文字(デリミタ)に基づいて文字列を部分文字列のスライスに分割します。例えば、strings.Split("a,b,c", ",")
は[]string{"a", "b", "c"}
を返します。- パフォーマンス最適化とベンチマーク:
ソフトウェア開発において、特定の処理の実行速度を向上させることをパフォーマンス最適化と呼びます。Go言語には、
go test -bench
コマンドを使用してコードのパフォーマンスを測定する組み込みのベンチマークツールがあります。これにより、コード変更がパフォーマンスに与える影響を定量的に評価できます。このコミットでは、BenchmarkPrint
というベンチマークが使用され、ns/op
(1操作あたりのナノ秒)という単位で実行時間が比較されています。 - カスタム実装と標準ライブラリ: プログラミングにおいて、特定の機能を実現するために独自のコード(カスタム実装)を書くか、言語やフレームワークが提供する標準ライブラリの機能を使用するかは常に考慮すべき点です。カスタム実装は特定の要件に最適化できる可能性がありますが、一般的には標準ライブラリの方がテストされ、最適化されており、保守性も高い傾向にあります。パフォーマンスが同等になった場合、標準ライブラリの利用が推奨されます。
- Go 1.1 (2013年5月リリース):
このコミットは2013年3月に行われており、Go 1.1のリリース直前の時期にあたります。Go 1.1では、ランタイム、コンパイラ、標準ライブラリの多くの部分でパフォーマンスの改善が図られました。
strings
パッケージの高速化もその一環として行われた可能性が高いです。
技術的詳細
このコミットの核心は、src/pkg/go/printer/printer.go
ファイル内のsplit
関数の削除と、その呼び出し箇所の変更です。
削除されたsplit
関数は、以下のようなロジックでコメントテキストを改行文字で分割していました。
- まず、テキスト内の改行文字の数を数え、それに基づいて結果のスライス(
lines
)のサイズを決定します。これにより、余分なメモリ割り当てを避けることができます。 - 次に、ループを使ってテキストを走査し、改行文字が見つかるたびに部分文字列を抽出し、
lines
スライスに格納します。 - この実装は、
strings.Split
が内部でどのように動作するか(特に、結果のスライスを動的に拡張する可能性や、一時的な文字列の生成方法)を考慮し、特定のシナリオ(コメントテキストの分割)においてstrings.Split
よりも効率的であると判断されていました。
しかし、Go言語の進化に伴い、strings.Split
の内部実装が改善されました。具体的には、メモリ割り当ての効率化や、文字列操作の低レベルな最適化が進んだと考えられます。これにより、strings.Split
がカスタムのsplit
関数と同等、あるいはそれ以上のパフォーマンスを発揮するようになりました。
コミットメッセージに示されているベンチマーク結果は、この最適化の効果を明確に示しています。
BenchmarkPrint old ns/op: 16724291
BenchmarkPrint new ns/op: 16686729
delta: -0.22%
これは、go/printer
パッケージ全体の印刷処理のベンチマークであり、split
関数の変更が全体に与える影響を示しています。わずかながらもパフォーマンスが向上していることは、strings.Split
の効率性がカスタム実装を上回ったことを示唆しています。
この変更により、go/printer
パッケージは、より簡潔で標準的なGoのイディオムに沿ったコードベースになりました。カスタム実装を削除することで、コードの行数が減り、将来的なメンテナンスコストも削減されます。
コアとなるコードの変更箇所
src/pkg/go/printer/printer.go
ファイルにおいて、以下の変更が行われました。
-
split
関数の削除: 約30行にわたるカスタムのsplit
関数が完全に削除されました。この関数は、コメントテキストを改行文字で手動で分割するロジックを含んでいました。--- a/src/pkg/go/printer/printer.go +++ b/src/pkg/go/printer/printer.go @@ -395,35 +395,6 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *as } } -// Split comment text into lines -// (using strings.Split(text, "\n") is significantly slower for -// this specific purpose, as measured with: go test -bench=Print) -// -func split(text string) []string { - // count lines (comment text never ends in a newline) - n := 1 - for i := 0; i < len(text); i++ { - if text[i] == '\n' { - n++ - } - } - - // split - lines := make([]string, n) - n = 0 - i := 0 - for j := 0; j < len(text); j++ { - if text[j] == '\n' { - lines[n] = text[i:j] // exclude newline - i = j + 1 // discard newline - n++ - } - } - lines[n] = text[i:] - - return lines -} - // Returns true if s contains only white space // (only tabs and blanks can appear in the printer's context). //
-
writeComment
関数内のsplit
関数の呼び出し箇所の変更:writeComment
関数内で、コメントテキストを分割するためにsplit(text)
を呼び出していた箇所が、strings.Split(text, "\n")
に置き換えられました。--- a/src/pkg/go/printer/printer.go +++ b/src/pkg/go/printer/printer.go @@ -616,7 +587,7 @@ func (p *printer) writeComment(comment *ast.Comment) { // for /*-style comments, print line by line and let the // write function take care of the proper indentation - lines := split(text) + lines := strings.Split(text, "\n") // The comment started in the first column but is going // to be indented. For an idempotent result, add indentation
コアとなるコードの解説
変更のあったsrc/pkg/go/printer/printer.go
ファイルは、Goのコード整形ツールであるgo/printer
パッケージの主要な部分を構成しています。このファイルには、抽象構文木(AST)を走査し、Goのフォーマット規則に従ってコードを文字列として出力するためのロジックが含まれています。
特に、writeComment
関数は、/* ... */
形式のブロックコメントや// ...
形式の行コメントを整形して出力する役割を担っています。コメントが複数行にわたる場合、各行を適切に処理し、インデントを調整する必要があります。
変更前は、このwriteComment
関数内で、コメントの生テキストを行ごとに分割するために、ファイル内に定義されたカスタムのsplit
関数が使われていました。このカスタム関数は、Goの文字列スライス操作とループを駆使して、改行文字を区切りとして文字列を分割していました。コメントに記載されているように、これは当時のstrings.Split
よりも高速であるとベンチマークで確認されていたためです。
変更後は、このカスタムsplit
関数が削除され、代わりにGo標準ライブラリのstrings.Split(text, "\n")
が直接使用されるようになりました。これは、strings
パッケージの継続的な最適化により、strings.Split
が十分に高速になり、カスタム実装のパフォーマンス上の優位性がなくなったことを意味します。
この変更は、Go言語の標準ライブラリが成熟し、その機能が特定の用途に特化したカスタム実装を置き換えるほど効率的になったことを示しています。これにより、コードベースはより簡潔になり、標準ライブラリの恩恵(継続的な最適化、バグ修正、一貫性など)を享受できるようになります。
関連リンク
- Go言語の
strings
パッケージドキュメント: https://pkg.go.dev/strings - Go言語の
go/printer
パッケージドキュメント: https://pkg.go.dev/go/printer - Go言語のベンチマークに関する公式ドキュメント: https://go.dev/doc/articles/go_benchmarking
- Go 1.1 Release Notes (2013年5月): https://go.dev/doc/go1.1 (このコミットの背景にある標準ライブラリのパフォーマンス改善の文脈を理解するのに役立ちます)
参考にした情報源リンク
- コミットハッシュ:
f38811979cefe7cd61b34c5580e2d10f65f243db
- GitHub上のコミットページ: https://github.com/golang/go/commit/f38811979cefe7cd61b34c5580e2d10f65f243db
- Go CL 8100044: https://golang.org/cl/8100044 (Goのコードレビューシステムにおけるこの変更のレビューページ)
- Web検索: "Go strings.Split performance improvement 2013" (Go言語の
strings.Split
関数の2013年頃のパフォーマンス改善に関する一般的な情報収集)