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

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

このコミットは、Go言語の標準ライブラリstringsパッケージ内のバイト置換処理、特にbyteReplacer.Replaceメソッドのパフォーマンス改善を目的としています。変更は主に以下の2つのファイルにわたります。

  • src/pkg/strings/replace.go: バイト置換ロジックの実装ファイル。byteReplacer構造体とそのWriteStringメソッド、およびNewReplacer関数の変更が含まれます。
  • src/pkg/strings/replace_test.go: stringsパッケージのテストファイル。新しいベンチマークが追加され、既存のベンチマーク名が変更されています。

コミット

strings: speed up byteReplacer.Replace

benchmark                         old ns/op    new ns/op    delta
BenchmarkByteReplacerWriteString       7359         3661  -50.25%

LGTM=dave
R=golang-codereviews, dave
CC=golang-codereviews
https://golang.org/cl/102550043

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

https://github.com/golang/go/commit/382c461a89bf2ee1ab91ba9c193f5cb7d257246c

元コミット内容

strings: speed up byteReplacer.Replace

benchmark                         old ns/op    new ns/op    delta
BenchmarkByteReplacerWriteString       7359         3661  -50.25%

LGTM=dave
R=golang-codereviews, dave
CC=golang-codereviews
https://golang.org/cl/102550043

変更の背景

このコミットの主な目的は、Go言語のstringsパッケージにおけるバイト単位の文字列置換処理のパフォーマンスを向上させることです。特に、byteReplacerという内部構造体が担当する処理において、ベンチマークで50%以上の高速化が達成されています。

文字列置換は、テキスト処理において非常に頻繁に行われる操作であり、その効率はアプリケーション全体のパフォーマンスに大きく影響します。特に、大量のテキストデータに対して繰り返し置換を行う場合、わずかな最適化でも全体として大きな改善に繋がります。

この変更は、byteReplacer.WriteStringメソッド内のループ処理における条件分岐を削減することで、CPUのブランチ予測の効率を高め、命令パイプラインのストールを減らすことを狙っています。これにより、より高速なバイト置換が可能になります。

前提知識の解説

stringsパッケージとReplacer

Go言語の標準ライブラリstringsパッケージは、文字列操作のための多くのユーティリティ関数を提供しています。その中でもReplacer型は、複数の置換ルールを効率的に適用するための構造体です。NewReplacer関数を使ってReplacerのインスタンスを作成し、ReplaceメソッドやWriteStringメソッドを使って文字列の置換を行います。

byteReplacer

strings.Replacerは、内部的に置換対象の文字列の特性に応じて異なる実装を選択します。このコミットで焦点となっているbyteReplacerは、置換対象と置換後の文字列がすべて単一のバイトである場合に特化して使用される内部的な最適化された実装です。例えば、strings.NewReplacer("a", "A", "b", "B")のように、1バイト文字を別の1バイト文字に置換する場合に利用されます。

byteReplacerは、256個のバイト値それぞれに対して、置換が必要かどうかを示すビットマップ(oldフィールド)と、置換後のバイト値を格納する配列(newフィールド)を持っています。

io.Writer

io.WriterはGo言語における基本的なインターフェースの一つで、データを書き込むための抽象化を提供します。WriteStringメソッドは、このio.Writerインターフェースを実装する型に対して文字列を書き込むために使用されます。byteReplacer.WriteStringは、置換処理の結果を直接io.Writerに書き出すことで、中間的な文字列生成を避けることができます。

byteBitmap

byteBitmapは、256個のバイト値のそれぞれが、特定の条件(この場合は置換対象であるかどうか)を満たすかどうかを効率的にチェックするためのビットマップ構造です。各バイト値に対応するビットがセットされていれば、そのバイトは置換対象であることを示します。

CPUのブランチ予測とパイプライン処理

現代のCPUは、命令の実行を高速化するためにパイプライン処理を行います。これは、複数の命令を同時に異なるステージで処理する技術です。しかし、条件分岐(if文など)があると、CPUは次に実行すべき命令を予測する必要があります(ブランチ予測)。予測が外れると、パイプラインがフラッシュされ、新しい命令をフェッチし直す必要があり、これがパフォーマンスの低下(パイプラインストール)を引き起こします。

このコミットの最適化は、このブランチ予測のオーバーヘッドを削減することに焦点を当てています。

技術的詳細

このコミットの核心は、byteReplacer.WriteStringメソッド内のループから条件分岐を排除することによるパフォーマンス向上です。

変更前は、byteReplacer.WriteStringメソッド内で、入力文字列の各バイトbに対して、まずr.old.isSet(b)という条件チェックを行っていました。このチェックは、現在のバイトbが置換対象のバイトであるかどうかを判断するためのものです。もし置換対象であれば、buf[i] = r.new[b]として置換後のバイトを書き込んでいました。

// 変更前 (src/pkg/strings/replace.go)
for i, b := range buf[:ncopy] {
    if r.old.isSet(b) { // ここで条件分岐が発生
        buf[i] = r.new[b]
    }
}

このif文は、ループの各イテレーションで評価されるため、CPUのブランチ予測に影響を与え、予測ミスが発生するとパイプラインストールを引き起こし、パフォーマンスが低下する可能性がありました。

この問題を解決するため、コミットでは以下の2つの変更が行われました。

  1. byteReplacer.new配列の初期化の変更: NewReplacer関数内でbyteReplacerが初期化される際に、bb.new配列が変更されました。変更前は、bb.new[i]には置換対象のバイトに対する新しいバイト値のみが設定され、置換対象ではないバイトに対するエントリは未定義(またはデフォルト値)でした。 変更後は、bb.new配列のすべての要素が初期化されます。具体的には、置換対象ではないバイトiに対しては、bb.new[i]byte(i)(つまり、元のバイト値そのまま)に設定されます。

    // 変更後 (src/pkg/strings/replace.go)
    if allNewBytes {
        bb := &byteReplacer{}
        for i := range bb.new { // 全てのバイト値に対して初期化
            bb.new[i] = byte(i)
        }
        // ... 既存の置換ルールに基づいてbb.newを更新 ...
    }
    

    これにより、bb.new配列は常に、元のバイト値が与えられた場合に、置換後のバイト値(置換対象であれば新しい値、そうでなければ元の値)を返す「ルックアップテーブル」として機能するようになります。

  2. byteReplacer.WriteStringから条件分岐の削除: byteReplacer.WriteStringメソッド内のループからif r.old.isSet(b)という条件分岐が削除されました。

    // 変更後 (src/pkg/strings/replace.go)
    for i, b := range buf[:ncopy] {
        buf[i] = r.new[b] // 条件分岐なしで常に代入
    }
    

    この変更により、ループ内の各バイト処理は常にbuf[i] = r.new[b]という単純な配列ルックアップと代入操作になります。r.new[b]は、前述の初期化により、bが置換対象であれば置換後の値、そうでなければb自身の値を返します。結果として、条件分岐が不要になり、CPUのブランチ予測ミスが解消され、パイプライン処理がより効率的に行われるようになります。

これらの変更により、特に置換対象のバイトが少ない場合や、置換処理が頻繁に呼び出される場合に、顕著なパフォーマンス向上が期待できます。ベンチマーク結果が示すように、この最適化は非常に効果的でした。

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

src/pkg/strings/replace.go

--- a/src/pkg/strings/replace.go
+++ b/src/pkg/strings/replace.go
@@ -53,6 +53,9 @@ func NewReplacer(oldnew ...string) *Replacer {
 
 	if allNewBytes {
 		bb := &byteReplacer{}
+		for i := range bb.new {
+			bb.new[i] = byte(i)
+		}
 		for i := 0; i < len(oldnew); i += 2 {
 			o, n := oldnew[i][0], oldnew[i+1][0]
 			if bb.old.isSet(o) {
@@ -426,8 +429,8 @@ type byteReplacer struct {
 	// old has a bit set for each old byte that should be replaced.
 	old byteBitmap
 
-	// replacement byte, indexed by old byte. only valid if
-	// corresponding old bit is set.
+	// replacement byte, indexed by old byte. old byte and new
+	// byte are the same if corresponding old bit is not set.
 	new [256]byte
 }
 
@@ -460,9 +463,7 @@ func (r *byteReplacer) WriteString(w io.Writer, s string) (n int, err error) {
 		tncopy := copy(buf, s[:])
 		s = s[ncopy:]
 		for i, b := range buf[:ncopy] {
-\t\t\tif r.old.isSet(b) {
-\t\t\t\tbuf[i] = r.new[b]
-\t\t\t}
+\t\t\tbuf[i] = r.new[b]
 		}
 		wn, err := w.Write(buf[:ncopy])
 		n += wn

src/pkg/strings/replace_test.go

--- a/src/pkg/strings/replace_test.go
+++ b/src/pkg/strings/replace_test.go
@@ -480,7 +480,7 @@ func BenchmarkHTMLEscapeOld(b *testing.B) {
 	}
 }
 
-func BenchmarkWriteString(b *testing.B) {
+func BenchmarkByteStringReplacerWriteString(b *testing.B) {
 	str := Repeat("I <3 to escape HTML & other text too.", 100)
 	buf := new(bytes.Buffer)
 	for i := 0; i < b.N; i++ {
@@ -489,6 +489,15 @@ func BenchmarkWriteString(b *testing.B) {
 	}
 }
 
+func BenchmarkByteReplacerWriteString(b *testing.B) {
+	str := Repeat("abcdefghijklmnopqrstuvwxyz", 100)
+	buf := new(bytes.Buffer)
+	for i := 0; i < b.N; i++ {
+		capitalLetters.WriteString(buf, str)
+		buf.Reset()
+	}
+}
+
 // BenchmarkByteByteReplaces compares byteByteImpl against multiple Replaces.
 func BenchmarkByteByteReplaces(b *testing.B) {
 	str := Repeat("a", 100) + Repeat("b", 100)

コアとなるコードの解説

src/pkg/strings/replace.go

  1. NewReplacer関数内のbb.new初期化:

    		for i := range bb.new {
    			bb.new[i] = byte(i)
    		}
    

    このループは、byteReplacerが作成される際に、そのnew配列(置換後のバイト値を格納するルックアップテーブル)を初期化します。具体的には、配列の各インデックスiに対して、その要素bb.new[i]byte(i)を代入しています。これは、**「もしそのバイトが置換対象でなければ、元のバイト値をそのまま返す」**というデフォルトの挙動を設定しています。これにより、後続の置換ルールが適用される際に、置換対象ではないバイトに対するエントリも有効な値を持つことになります。

  2. byteReplacer構造体のnewフィールドのコメント更新:

    -	// replacement byte, indexed by old byte. only valid if
    -	// corresponding old bit is set.
    +	// replacement byte, indexed by old byte. old byte and new
    +	// byte are the same if corresponding old bit is not set.
    

    このコメントの変更は、上記のbb.newの新しい初期化ロジックを反映しています。以前は、new配列のエントリは対応するoldビットがセットされている(つまり置換対象である)場合にのみ有効でした。しかし、新しいロジックでは、oldビットがセットされていない場合(置換対象ではない場合)でも、oldバイトとnewバイトは同じ値になるようにnew配列が初期化されることを明示しています。

  3. byteReplacer.WriteStringメソッド内の条件分岐の削除:

    -			if r.old.isSet(b) {
    -				buf[i] = r.new[b]
    -			}
    +			buf[i] = r.new[b]
    

    これがパフォーマンス改善の最も重要な変更点です。以前は、各バイトbが置換対象であるかどうかをr.old.isSet(b)でチェックし、置換対象の場合にのみr.new[b]buf[i]に代入していました。 変更後は、このif文が削除され、常にbuf[i] = r.new[b]が実行されます。前述のNewReplacerでのbb.newの初期化により、r.new[b]は以下のようになります。

    • bが置換対象の場合: r.new[b]は置換後の正しいバイト値。
    • bが置換対象ではない場合: r.new[b]b自身(元のバイト値)。 したがって、この変更は、条件分岐をなくすことでCPUのブランチ予測ミスを減らし、パイプライン処理の効率を向上させます。これにより、ループがより高速に実行されるようになります。

src/pkg/strings/replace_test.go

  1. ベンチマーク名の変更:

    -func BenchmarkWriteString(b *testing.B) {
    +func BenchmarkByteStringReplacerWriteString(b *testing.B) {
    

    既存のBenchmarkWriteStringの名称がBenchmarkByteStringReplacerWriteStringに変更されました。これは、このベンチマークがbyteReplacerだけでなく、より一般的なstrings.ReplacerWriteStringメソッドのパフォーマンスを測定していることを明確にするためと考えられます。

  2. 新しいベンチマークの追加:

    +func BenchmarkByteReplacerWriteString(b *testing.B) {
    +	str := Repeat("abcdefghijklmnopqrstuvwxyz", 100)
    +	buf := new(bytes.Buffer)
    +	for i := 0; i < b.N; i++ {
    +		capitalLetters.WriteString(buf, str)
    +		buf.Reset()
    +	}
    +}
    

    BenchmarkByteReplacerWriteStringという新しいベンチマークが追加されました。このベンチマークは、byteReplacerのパフォーマンスをより直接的に測定することを目的としています。capitalLettersというReplacer(おそらく小文字を大文字に置換するもの)を使用し、置換対象となる文字が多数含まれる文字列を処理することで、byteReplacerの最適化がどれだけ効果的であるかを評価します。コミットメッセージのベンチマーク結果はこの新しいベンチマークによるものです。

関連リンク

参考にした情報源リンク

  • (今回の解説ではWeb検索は使用していません。提供されたコミット情報と差分のみに基づいています。) I have generated the detailed explanation in Markdown format, following all the specified instructions and chapter structure. I have included the technical details, background, and core code changes. I did not need to use google_web_search as the provided diff and commit message were sufficient for a comprehensive explanation. The output is now ready to be printed to standard output.# [インデックス 19582] ファイルの概要

このコミットは、Go言語の標準ライブラリstringsパッケージ内のバイト置換処理、特にbyteReplacer.Replaceメソッドのパフォーマンス改善を目的としています。変更は主に以下の2つのファイルにわたります。

  • src/pkg/strings/replace.go: バイト置換ロジックの実装ファイル。byteReplacer構造体とそのWriteStringメソッド、およびNewReplacer関数の変更が含まれます。
  • src/pkg/strings/replace_test.go: stringsパッケージのテストファイル。新しいベンチマークが追加され、既存のベンチマーク名が変更されています。

コミット

strings: speed up byteReplacer.Replace

benchmark                         old ns/op    new ns/op    delta
BenchmarkByteReplacerWriteString       7359         3661  -50.25%

LGTM=dave
R=golang-codereviews, dave
CC=golang-codereviews
https://golang.org/cl/102550043

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

https://github.com/golang/go/commit/382c461a89bf2ee1ab91ba9c193f5cb7d257246c

元コミット内容

strings: speed up byteReplacer.Replace

benchmark                         old ns/op    new ns/op    delta
BenchmarkByteReplacerWriteString       7359         3661  -50.25%

LGTM=dave
R=golang-codereviews, dave
CC=golang-codereviews
https://golang.org/cl/102550043

変更の背景

このコミットの主な目的は、Go言語のstringsパッケージにおけるバイト単位の文字列置換処理のパフォーマンスを向上させることです。特に、byteReplacerという内部構造体が担当する処理において、ベンチマークで50%以上の高速化が達成されています。

文字列置換は、テキスト処理において非常に頻繁に行われる操作であり、その効率はアプリケーション全体のパフォーマンスに大きく影響します。特に、大量のテキストデータに対して繰り返し置換を行う場合、わずかな最適化でも全体として大きな改善に繋がります。

この変更は、byteReplacer.WriteStringメソッド内のループ処理における条件分岐を削減することで、CPUのブランチ予測の効率を高め、命令パイプラインのストールを減らすことを狙っています。これにより、より高速なバイト置換が可能になります。

前提知識の解説

stringsパッケージとReplacer

Go言語の標準ライブラリstringsパッケージは、文字列操作のための多くのユーティリティ関数を提供しています。その中でもReplacer型は、複数の置換ルールを効率的に適用するための構造体です。NewReplacer関数を使ってReplacerのインスタンスを作成し、ReplaceメソッドやWriteStringメソッドを使って文字列の置換を行います。

byteReplacer

strings.Replacerは、内部的に置換対象の文字列の特性に応じて異なる実装を選択します。このコミットで焦点となっているbyteReplacerは、置換対象と置換後の文字列がすべて単一のバイトである場合に特化して使用される内部的な最適化された実装です。例えば、strings.NewReplacer("a", "A", "b", "B")のように、1バイト文字を別の1バイト文字に置換する場合に利用されます。

byteReplacerは、256個のバイト値それぞれに対して、置換が必要かどうかを示すビットマップ(oldフィールド)と、置換後のバイト値を格納する配列(newフィールド)を持っています。

io.Writer

io.WriterはGo言語における基本的なインターフェースの一つで、データを書き込むための抽象化を提供します。WriteStringメソッドは、このio.Writerインターフェースを実装する型に対して文字列を書き込むために使用されます。byteReplacer.WriteStringは、置換処理の結果を直接io.Writerに書き出すことで、中間的な文字列生成を避けることができます。

byteBitmap

byteBitmapは、256個のバイト値のそれぞれが、特定の条件(この場合は置換対象であるかどうか)を満たすかどうかを効率的にチェックするためのビットマップ構造です。各バイト値に対応するビットがセットされていれば、そのバイトは置換対象であることを示します。

CPUのブランチ予測とパイプライン処理

現代のCPUは、命令の実行を高速化するためにパイプライン処理を行います。これは、複数の命令を同時に異なるステージで処理する技術です。しかし、条件分岐(if文など)があると、CPUは次に実行すべき命令を予測する必要があります(ブランチ予測)。予測が外れると、パイプラインがフラッシュされ、新しい命令をフェッチし直す必要があり、これがパフォーマンスの低下(パイプラインストール)を引き起こします。

このコミットの最適化は、このブランチ予測のオーバーヘッドを削減することに焦点を当てています。

技術的詳細

このコミットの核心は、byteReplacer.WriteStringメソッド内のループから条件分岐を排除することによるパフォーマンス向上です。

変更前は、byteReplacer.WriteStringメソッド内で、入力文字列の各バイトbに対して、まずr.old.isSet(b)という条件チェックを行っていました。このチェックは、現在のバイトbが置換対象のバイトであるかどうかを判断するためのものです。もし置換対象であれば、buf[i] = r.new[b]として置換後のバイトを書き込んでいました。

// 変更前 (src/pkg/strings/replace.go)
for i, b := range buf[:ncopy] {
    if r.old.isSet(b) { // ここで条件分岐が発生
        buf[i] = r.new[b]
    }
}

このif文は、ループの各イテレーションで評価されるため、CPUのブランチ予測に影響を与え、予測ミスが発生するとパイプラインストールを引き起こし、パフォーマンスが低下する可能性がありました。

この問題を解決するため、コミットでは以下の2つの変更が行われました。

  1. byteReplacer.new配列の初期化の変更: NewReplacer関数内でbyteReplacerが初期化される際に、bb.new配列が変更されました。変更前は、bb.new[i]には置換対象のバイトに対する新しいバイト値のみが設定され、置換対象ではないバイトに対するエントリは未定義(またはデフォルト値)でした。 変更後は、bb.new配列のすべての要素が初期化されます。具体的には、置換対象ではないバイトiに対しては、bb.new[i]byte(i)(つまり、元のバイト値そのまま)に設定されます。

    // 変更後 (src/pkg/strings/replace.go)
    if allNewBytes {
        bb := &byteReplacer{}
        for i := range bb.new { // 全てのバイト値に対して初期化
            bb.new[i] = byte(i)
        }
        // ... 既存の置換ルールに基づいてbb.newを更新 ...
    }
    

    これにより、bb.new配列は常に、元のバイト値が与えられた場合に、置換後のバイト値(置換対象であれば新しい値、そうでなければ元の値)を返す「ルックアップテーブル」として機能するようになります。

  2. byteReplacer.WriteStringから条件分岐の削除: byteReplacer.WriteStringメソッド内のループからif r.old.isSet(b)という条件分岐が削除されました。

    // 変更後 (src/pkg/strings/replace.go)
    for i, b := range buf[:ncopy] {
        buf[i] = r.new[b] // 条件分岐なしで常に代入
    }
    

    この変更により、ループ内の各バイト処理は常にbuf[i] = r.new[b]という単純な配列ルックアップと代入操作になります。r.new[b]は、前述の初期化により、bが置換対象であれば置換後の値、そうでなければb自身の値を返します。結果として、条件分岐が不要になり、CPUのブランチ予測ミスが解消され、パイプライン処理がより効率的に行われるようになります。

これらの変更により、特に置換対象のバイトが少ない場合や、置換処理が頻繁に呼び出される場合に、顕著なパフォーマンス向上が期待できます。ベンチマーク結果が示すように、この最適化は非常に効果的でした。

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

src/pkg/strings/replace.go

--- a/src/pkg/strings/replace.go
+++ b/src/pkg/strings/replace.go
@@ -53,6 +53,9 @@ func NewReplacer(oldnew ...string) *Replacer {
 
 	if allNewBytes {
 		bb := &byteReplacer{}
+		for i := range bb.new {
+			bb.new[i] = byte(i)
+		}
 		for i := 0; i < len(oldnew); i += 2 {
 			o, n := oldnew[i][0], oldnew[i+1][0]
 			if bb.old.isSet(o) {
@@ -426,8 +429,8 @@ type byteReplacer struct {
 	// old has a bit set for each old byte that should be replaced.
 	old byteBitmap
 
-	// replacement byte, indexed by old byte. only valid if
-	// corresponding old bit is set.
+	// replacement byte, indexed by old byte. old byte and new
+	// byte are the same if corresponding old bit is not set.
 	new [256]byte
 }
 
@@ -460,9 +463,7 @@ func (r *byteReplacer) WriteString(w io.Writer, s string) (n int, err error) {
 		tncopy := copy(buf, s[:])
 		s = s[ncopy:]
 		for i, b := range buf[:ncopy] {
-\t\t\tif r.old.isSet(b) {
-\t\t\t\tbuf[i] = r.new[b]
-\t\t\t}
+\t\t\tbuf[i] = r.new[b]
 		}
 		wn, err := w.Write(buf[:ncopy])
 		n += wn

src/pkg/strings/replace_test.go

--- a/src/pkg/strings/replace_test.go
+++ b/src/pkg/strings/replace_test.go
@@ -480,7 +480,7 @@ func BenchmarkHTMLEscapeOld(b *testing.B) {
 	}
 }
 
-func BenchmarkWriteString(b *testing.B) {
+func BenchmarkByteStringReplacerWriteString(b *testing.B) {
 	str := Repeat("I <3 to escape HTML & other text too.", 100)
 	buf := new(bytes.Buffer)
 	for i := 0; i < b.N; i++ {
@@ -489,6 +489,15 @@ func BenchmarkWriteString(b *testing.B) {
 	}
 }
 
+func BenchmarkByteReplacerWriteString(b *testing.B) {
+	str := Repeat("abcdefghijklmnopqrstuvwxyz", 100)
+	buf := new(bytes.Buffer)
+	for i := 0; i < b.N; i++ {
+		capitalLetters.WriteString(buf, str)
+		buf.Reset()
+	}
+}
+
 // BenchmarkByteByteReplaces compares byteByteImpl against multiple Replaces.
 func BenchmarkByteByteReplaces(b *testing.B) {
 	str := Repeat("a", 100) + Repeat("b", 100)

コアとなるコードの解説

src/pkg/strings/replace.go

  1. NewReplacer関数内のbb.new初期化:

    		for i := range bb.new {
    			bb.new[i] = byte(i)
    		}
    

    このループは、byteReplacerが作成される際に、そのnew配列(置換後のバイト値を格納するルックアップテーブル)を初期化します。具体的には、配列の各インデックスiに対して、その要素bb.new[i]byte(i)を代入しています。これは、**「もしそのバイトが置換対象でなければ、元のバイト値をそのまま返す」**というデフォルトの挙動を設定しています。これにより、後続の置換ルールが適用される際に、置換対象ではないバイトに対するエントリも有効な値を持つことになります。

  2. byteReplacer構造体のnewフィールドのコメント更新:

    -	// replacement byte, indexed by old byte. only valid if
    -	// corresponding old bit is set.
    +	// replacement byte, indexed by old byte. old byte and new
    +	// byte are the same if corresponding old bit is not set.
    

    このコメントの変更は、上記のbb.newの新しい初期化ロジックを反映しています。以前は、new配列のエントリは対応するoldビットがセットされている(つまり置換対象である)場合にのみ有効でした。しかし、新しいロジックでは、oldビットがセットされていない場合(置換対象ではない場合)でも、oldバイトとnewバイトは同じ値になるようにnew配列が初期化されることを明示しています。

  3. byteReplacer.WriteStringメソッド内の条件分岐の削除:

    -			if r.old.isSet(b) {
    -				buf[i] = r.new[b]
    -			}
    +			buf[i] = r.new[b]
    

    これがパフォーマンス改善の最も重要な変更点です。以前は、各バイトbが置換対象であるかどうかをr.old.isSet(b)でチェックし、置換対象の場合にのみr.new[b]buf[i]に代入していました。 変更後は、このif文が削除され、常にbuf[i] = r.new[b]が実行されます。前述のNewReplacerでのbb.newの初期化により、r.new[b]は以下のようになります。

    • bが置換対象の場合: r.new[b]は置換後の正しいバイト値。
    • bが置換対象ではない場合: r.new[b]b自身(元のバイト値)。 したがって、この変更は、条件分岐をなくすことでCPUのブランチ予測ミスを減らし、パイプライン処理の効率を向上させます。これにより、ループがより高速に実行されるようになります。

src/pkg/strings/replace_test.go

  1. ベンチマーク名の変更:

    -func BenchmarkWriteString(b *testing.B) {
    +func BenchmarkByteStringReplacerWriteString(b *testing.B) {
    

    既存のBenchmarkWriteStringの名称がBenchmarkByteStringReplacerWriteStringに変更されました。これは、このベンチマークがbyteReplacerだけでなく、より一般的なstrings.ReplacerWriteStringメソッドのパフォーマンスを測定していることを明確にするためと考えられます。

  2. 新しいベンチマークの追加:

    +func BenchmarkByteReplacerWriteString(b *testing.B) {
    +	str := Repeat("abcdefghijklmnopqrstuvwxyz", 100)
    +	buf := new(bytes.Buffer)
    +	for i := 0; i < b.N; i++ {
    +		capitalLetters.WriteString(buf, str)
    +		buf.Reset()
    +	}
    +}
    

    BenchmarkByteReplacerWriteStringという新しいベンチマークが追加されました。このベンチマークは、byteReplacerのパフォーマンスをより直接的に測定することを目的としています。capitalLettersというReplacer(おそらく小文字を大文字に置換するもの)を使用し、置換対象となる文字が多数含まれる文字列を処理することで、byteReplacerの最適化がどれだけ効果的であるかを評価します。コミットメッセージのベンチマーク結果はこの新しいベンチマークによるものです。

関連リンク

参考にした情報源リンク

  • (今回の解説ではWeb検索は使用していません。提供されたコミット情報と差分のみに基づいています。)