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

[インデックス 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関数は、以下のようなロジックでコメントテキストを改行文字で分割していました。

  1. まず、テキスト内の改行文字の数を数え、それに基づいて結果のスライス(lines)のサイズを決定します。これにより、余分なメモリ割り当てを避けることができます。
  2. 次に、ループを使ってテキストを走査し、改行文字が見つかるたびに部分文字列を抽出し、linesスライスに格納します。
  3. この実装は、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ファイルにおいて、以下の変更が行われました。

  1. 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).
     //
    
  2. 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言語の標準ライブラリが成熟し、その機能が特定の用途に特化したカスタム実装を置き換えるほど効率的になったことを示しています。これにより、コードベースはより簡潔になり、標準ライブラリの恩恵(継続的な最適化、バグ修正、一貫性など)を享受できるようになります。

関連リンク

参考にした情報源リンク