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

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

このコミットは、Go言語のランタイムにおけるappend操作のパフォーマンスチューニングに関するものです。特に、Intelプラットフォーム(amd64および386アーキテクチャ)でのスライスおよび文字列の追加(append)処理における「crossover」値を調整することで、小規模な追加操作の効率を向上させています。

コミット

commit 8ce8adbe7a194cba2819b533b603d92df40fe799
Author: Dave Cheney <dave@cheney.net>
Date:   Tue Aug 6 07:51:37 2013 +1000

    runtime: tune append crossover on amd64 and 386
    
    Fixes #4963.
    
    Sets the append crossover to 0 on intel platforms.
    
    Results for linux/amd64 Core i5 SNB
    
    benchmark                     old ns/op    new ns/op    delta
    BenchmarkAppend                     102          104   +1.96%
    BenchmarkAppend1Byte                 10           11   +0.92%
    BenchmarkAppend4Bytes                15           11  -28.10%
    BenchmarkAppend7Bytes                17           12  -32.58%
    BenchmarkAppend8Bytes                18           12  -36.17%
    BenchmarkAppend15Bytes               24           11  -55.02%
    BenchmarkAppend16Bytes               25           11  -56.03%
    BenchmarkAppend32Bytes               11           12   +4.31%
    BenchmarkAppendStr1Byte               8            9  +13.99%
    BenchmarkAppendStr4Bytes             11            9  -17.52%
    BenchmarkAppendStr8Bytes             14            9  -35.70%
    BenchmarkAppendStr16Bytes            21            9  -55.19%
    BenchmarkAppendStr32Bytes            10           10   -5.66%
    BenchmarkAppendSpecialCase           49           52   +7.96%
    
    Results for linux/386 Atom(TM) CPU 330 @ 1.60GHz
    
    benchmark                     old ns/op    new ns/op    delta
    BenchmarkAppend                     219          218   -0.46%
    BenchmarkAppend1Byte                 75           72   -3.44%
    BenchmarkAppend4Bytes                92           73  -19.87%
    BenchmarkAppend7Bytes               108           74  -31.20%
    BenchmarkAppend8Bytes               116           74  -35.95%
    BenchmarkAppend15Bytes              162           77  -52.22%
    BenchmarkAppend16Bytes              169           77  -54.20%
    BenchmarkAppend32Bytes               88           86   -2.38%
    BenchmarkAppendStr1Byte              57           59   +3.32%
    BenchmarkAppendStr4Bytes             72           59  -17.40%
    BenchmarkAppendStr8Bytes             92           60  -34.70%
    BenchmarkAppendStr16Bytes           141           63  -54.89%
    BenchmarkAppendStr32Bytes            75           73   -2.64%
    BenchmarkAppendSpecialCase          270          270   +0.00%
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/12440044

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

https://github.com/golang/go/commit/8ce8adbe7a194cba2819b533b603d92df40fe799

元コミット内容

このコミットは、Goランタイムのappend操作におけるappendCrossoverの値をamd64および386アーキテクチャ向けに調整するものです。具体的には、これらのIntelプラットフォームにおいてappendCrossoverの値を0に設定することで、小規模なデータ追加時のパフォーマンスを改善しています。コミットメッセージには、変更前後のベンチマーク結果が詳細に記載されており、特に小容量のバイト列や文字列の追加において顕著な性能向上が見られます。

変更の背景

Go言語のスライスや文字列のappend操作は、内部的にランタイムによって効率的に処理されます。この処理には、追加されるデータのサイズに応じて異なる最適化戦略が適用されることがあります。appendCrossoverは、この最適化戦略を決定する閾値の一つです。

従来のGoランタイムでは、append操作において、追加されるデータのサイズが特定の閾値(appendCrossover)以下の場合には、よりシンプルなバイト単位のコピー(whileループなど)が使用され、閾値を超える場合には、memmoveのようなより最適化されたメモリコピー関数が使用されるというロジックが存在しました。

しかし、Intelプラットフォーム(amd64および386)において、このappendCrossoverのデフォルト値(16バイト)が、小規模な追加操作のパフォーマンスにとって最適ではないことが判明しました。特に、1バイトから16バイト程度の非常に小さなデータの追加において、memmoveのような汎用的なメモリコピー関数を呼び出すオーバーヘッドが、単純なバイトコピーのオーバーヘッドを上回ってしまうケースがありました。

このコミットは、この非効率性を解消し、小規模なappend操作のパフォーマンスを向上させることを目的としています。

前提知識の解説

Goのスライスとappend関数

Go言語のスライスは、配列をラップした動的なデータ構造です。スライスは、内部的にポインタ、長さ(len)、容量(cap)の3つの要素で構成されます。append関数は、スライスに要素を追加するために使用されます。

append関数が呼び出されると、Goランタイムは以下のロジックに従って動作します。

  1. 容量の確認: 現在のスライスの容量が、追加される要素を格納するのに十分であるかを確認します。
  2. 再割り当て: 容量が不足している場合、ランタイムはより大きな新しい基底配列を割り当て、既存の要素を新しい配列にコピーし、追加される要素をその後に配置します。この際、新しい容量は通常、元の容量の2倍(またはそれ以上)に拡張されます。
  3. 要素のコピー: 容量が十分な場合、または新しい配列が割り当てられた場合、追加される要素がスライスの末尾にコピーされます。

appendCrossover

appendCrossoverは、Goランタイムがスライスや文字列のappend操作を行う際に、内部的に使用するメモリコピー戦略を切り替えるための閾値です。この値は、アーキテクチャ固有のヘッダーファイル(例: src/pkg/runtime/arch_amd64.h)で定義されています。

  • w <= appendCrossover の場合: 追加されるバイト数 wappendCrossover 以下の場合、ランタイムは通常、シンプルなループによるバイト単位のコピーを行います。これは、非常に小さなコピーサイズの場合に、memmoveのような最適化された関数を呼び出すオーバーヘッドを避けるためです。
  • w > appendCrossover の場合: 追加されるバイト数 wappendCrossover を超える場合、ランタイムは通常、memmoveのような、より高速で最適化されたメモリコピー関数を使用します。これらの関数は、大量のデータを効率的にコピーするためにアセンブリ言語などで最適化されています。

このコミットの目的は、Intelプラットフォームにおいて、このappendCrossoverの値を0に設定することです。これにより、追加されるデータのサイズに関わらず、常にmemmoveのような最適化されたメモリコピー関数が使用されるようになります。

ベンチマーク結果の読み方

コミットメッセージに記載されているベンチマーク結果は、go test -bench=.コマンドなどで取得されるパフォーマンス測定値です。

  • ns/op (nanoseconds per operation): 1回の操作にかかる平均ナノ秒。この値が小さいほど、パフォーマンスが良いことを示します。
  • delta: 変更前(old)と変更後(new)のパフォーマンスの差をパーセンテージで示します。負の値はパフォーマンスの向上(時間が短縮された)、正の値はパフォーマンスの低下(時間が長くなった)を示します。

技術的詳細

このコミットの核心は、appendCrossoverの値を0に設定することです。

従来のGoランタイムでは、appendCrossover16に設定されていました。これは、16バイト以下の追加操作ではバイト単位のコピーを、16バイトを超える追加操作ではmemmoveを使用するという意味です。しかし、Intelプロセッサの進化により、小規模なメモリコピーにおいてもmemmoveのような最適化されたルーチンが非常に効率的になっていました。むしろ、appendCrossoverの条件分岐や、バイト単位のループ処理のオーバーヘッドが、memmoveの呼び出しオーバーヘッドよりも大きくなるケースが発生していました。

appendCrossover = 0と設定することで、runtime·appendsliceおよびruntime·appendstr関数内の条件分岐if(w <= appendCrossover)が常に偽(w > 0の場合)となり、結果として常にelseブロックのmemmove相当の処理が実行されるようになります。

ただし、slice.cの変更を見ると、if(appendCrossover > 0 && w <= appendCrossover)という条件に変わっています。これは、appendCrossover0の場合には、この条件全体がfalseとなり、常にelseブロック(memmove相当の処理)が実行されることを意味します。つまり、appendCrossover0に設定されたプラットフォームでは、追加されるデータのサイズに関わらず、常に最適化されたメモリコピー関数が使用されるようになります。

この変更により、特に小規模なデータ(1バイトから16バイト程度)の追加において、条件分岐のオーバーヘッドや非効率なループ処理が回避され、memmoveによる高速なコピーが常に適用されるため、パフォーマンスが大幅に向上します。ベンチマーク結果からも、BenchmarkAppend4BytesからBenchmarkAppend16Bytes、および対応する文字列ベンチマークで顕著な改善が見られることが確認できます。

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

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

  1. src/pkg/runtime/append_test.go: ベンチマークテストの追加。
  2. src/pkg/runtime/arch_386.h: 386アーキテクチャのappendCrossoverの定義変更。
  3. src/pkg/runtime/arch_amd64.h: amd64アーキテクチャのappendCrossoverの定義変更。
  4. src/pkg/runtime/slice.c: append操作の内部ロジックの変更。

src/pkg/runtime/append_test.go

--- a/src/pkg/runtime/append_test.go
+++ b/src/pkg/runtime/append_test.go
@@ -38,10 +38,18 @@ func BenchmarkAppend4Bytes(b *testing.B) {
 	benchmarkAppendBytes(b, 4)
 }
 
+func BenchmarkAppend7Bytes(b *testing.B) {
+\tbenchmarkAppendBytes(b, 7)
+}
+\n func BenchmarkAppend8Bytes(b *testing.B) {
 	benchmarkAppendBytes(b, 8)
 }
 
+func BenchmarkAppend15Bytes(b *testing.B) {
+\tbenchmarkAppendBytes(b, 15)
+}\n
 func BenchmarkAppend16Bytes(b *testing.B) {
 	benchmarkAppendBytes(b, 16)
 }

このファイルでは、BenchmarkAppend7BytesBenchmarkAppend15Bytesという新しいベンチマーク関数が追加されています。これは、appendCrossoverの閾値付近のパフォーマンスをより詳細に測定するために導入されたものです。

src/pkg/runtime/arch_386.h

--- a/src/pkg/runtime/arch_386.h
+++ b/src/pkg/runtime/arch_386.h
@@ -6,6 +6,6 @@ enum {
 	thechar = '8',
 	BigEndian = 0,
 	CacheLineSize = 64,
-	appendCrossover = 16,
+	appendCrossover = 0,
 	PCQuantum = 1
 };

386アーキテクチャの定義ファイルで、appendCrossoverの値が16から0に変更されています。

src/pkg/runtime/arch_amd64.h

--- a/src/pkg/runtime/arch_amd64.h
+++ b/src/pkg/runtime/arch_amd64.h
@@ -6,6 +6,6 @@ enum {
 	thechar = '6',
 	BigEndian = 0,
 	CacheLineSize = 64,
-	appendCrossover = 16,
+	appendCrossover = 0,
 	PCQuantum = 1
 };

amd64アーキテクチャの定義ファイルで、appendCrossoverの値が16から0に変更されています。

src/pkg/runtime/slice.c

--- a/src/pkg/runtime/slice.c
+++ b/src/pkg/runtime/slice.c
@@ -95,7 +95,7 @@ runtime·appendslice(SliceType *t, Slice x, Slice y, Slice ret)
 	p = ret.array+ret.len*w;
 	q = y.array;
 	w *= y.len;
-	if(w <= appendCrossover) {
+	if(appendCrossover > 0 && w <= appendCrossover) {
 		if(p <= q || w <= p-q) // No overlap.
 			while(w-- > 0)
 				*p++ = *q++;
@@ -148,7 +148,7 @@ runtime·appendstr(SliceType *t, Slice x, String y, Slice ret)
 	w = y.len;
 	p = ret.array+ret.len;
 	q = y.str;
-	if(w <= appendCrossover) {
+	if(appendCrossover > 0 && w <= appendCrossover) {
 		while(w-- > 0)
 			*p++ = *q++;
 	} else {

runtime·appendsliceruntime·appendstr関数の内部ロジックが変更されています。条件分岐がif(w <= appendCrossover)からif(appendCrossover > 0 && w <= appendCrossover)に変更されました。

コアとなるコードの解説

src/pkg/runtime/slice.cにおける変更が、このコミットの動作の鍵となります。

変更前のコード:

if(w <= appendCrossover) {
    // バイト単位のコピー
} else {
    // memmove相当の最適化されたコピー
}

変更後のコード:

if(appendCrossover > 0 && w <= appendCrossover) {
    // バイト単位のコピー
} else {
    // memmove相当の最適化されたコピー
}

この変更により、appendCrossover0に設定されたamd64および386アーキテクチャでは、appendCrossover > 0という条件が常にfalseとなるため、ifブロック内のバイト単位のコピー処理が実行されなくなります。結果として、追加されるデータのサイズwがどれほど小さくても、常にelseブロック内のmemmove相当の最適化されたメモリコピー処理が実行されるようになります。

これは、現代のIntelプロセッサにおいて、memmoveのような最適化されたルーチンが非常に効率的であり、たとえ数バイトのコピーであっても、条件分岐やループのオーバーヘッドを考慮すると、そちらの方が高速であるという知見に基づいています。

ベンチマーク結果は、この変更が意図した効果をもたらしたことを明確に示しています。特に、BenchmarkAppend4BytesからBenchmarkAppend16Bytes、および対応する文字列ベンチマークにおいて、ns/opが大幅に減少し、パフォーマンスが大きく向上していることがわかります。これは、これらの小規模な追加操作が、より効率的なmemmoveパスを通るようになったためです。

関連リンク

  • Go Issue #4963: runtime: tune append crossover on amd64 and 386
    • このコミットが修正したGoのIssueです。詳細な議論や背景情報が記載されている可能性があります。
  • Goのappend関数のドキュメント: https://pkg.go.dev/builtin#append
  • Goの内部的なスライス実装に関する記事(一般的な情報源)

参考にした情報源リンク

  • Goのソースコード(特にsrc/pkg/runtime/ディレクトリ内のファイル)
  • GoのIssueトラッカー(#4963)
  • Goのベンチマークに関する一般的な情報
  • メモリコピー最適化に関する一般的なコンピュータアーキテクチャの知識
  • Dave Cheney氏のブログや関連するGoのパフォーマンスに関する記事(もしあれば)
    • (注: 特定のブログ記事への直接リンクは、検索結果に基づいて追加する必要がありますが、ここでは一般的な情報源として記載しています。)

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

このコミットは、Go言語のランタイムにおけるappend操作のパフォーマンスチューニングに関するものです。特に、Intelプラットフォーム(amd64および386アーキテクチャ)でのスライスおよび文字列の追加(append)処理における「crossover」値を調整することで、小規模な追加操作の効率を向上させています。

コミット

commit 8ce8adbe7a194cba2819b533b603d92df40fe799
Author: Dave Cheney <dave@cheney.net>
Date:   Tue Aug 6 07:51:37 2013 +1000

    runtime: tune append crossover on amd64 and 386
    
    Fixes #4963.
    
    Sets the append crossover to 0 on intel platforms.
    
    Results for linux/amd64 Core i5 SNB
    
    benchmark                     old ns/op    new ns/op    delta
    BenchmarkAppend                     102          104   +1.96%
    BenchmarkAppend1Byte                 10           11   +0.92%
    BenchmarkAppend4Bytes                15           11  -28.10%
    BenchmarkAppend7Bytes                17           12  -32.58%
    BenchmarkAppend8Bytes                18           12  -36.17%
    BenchmarkAppend15Bytes               24           11  -55.02%
    BenchmarkAppend16Bytes               25           11  -56.03%
    BenchmarkAppend32Bytes               11           12   +4.31%
    BenchmarkAppendStr1Byte               8            9  +13.99%
    BenchmarkAppendStr4Bytes             11            9  -17.52%
    BenchmarkAppendStr8Bytes             14            9  -35.70%
    BenchmarkAppendStr16Bytes            21            9  -55.19%
    BenchmarkAppendStr32Bytes            10           10   -5.66%
    BenchmarkAppendSpecialCase           49           52   +7.96%
    
    Results for linux/386 Atom(TM) CPU 330 @ 1.60GHz
    
    benchmark                     old ns/op    new ns/op    delta
    BenchmarkAppend                     219          218   -0.46%
    BenchmarkAppend1Byte                 75           72   -3.44%
    BenchmarkAppend4Bytes                92           73  -19.87%
    BenchmarkAppend7Bytes               108           74  -31.20%
    BenchmarkAppend8Bytes               116           74  -35.95%
    BenchmarkAppend15Bytes              162           77  -52.22%
    BenchmarkAppend16Bytes              169           77  -54.20%
    BenchmarkAppend32Bytes               88           86   -2.38%
    BenchmarkAppendStr1Byte              57           59   +3.32%
    BenchmarkAppendStr4Bytes             72           59  -17.40%
    BenchmarkAppendStr8Bytes             92           60  -34.70%
    BenchmarkAppendStr16Bytes           141           63  -54.89%
    BenchmarkAppendStr32Bytes            75           73   -2.64%
    BenchmarkAppendSpecialCase          270          270   +0.00%
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/12440044

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

https://github.com/golang/go/commit/8ce8adbe7a194cba2819b533b603d92df40fe799

元コミット内容

このコミットは、Goランタイムのappend操作におけるappendCrossoverの値をamd64および386アーキテクチャ向けに調整するものです。具体的には、これらのIntelプラットフォームにおいてappendCrossoverの値を0に設定することで、小規模なデータ追加時のパフォーマンスを改善しています。コミットメッセージには、変更前後のベンチマーク結果が詳細に記載されており、特に小容量のバイト列や文字列の追加において顕著な性能向上が見られます。

変更の背景

Go言語のスライスや文字列のappend操作は、内部的にランタイムによって効率的に処理されます。この処理には、追加されるデータのサイズに応じて異なる最適化戦略が適用されることがあります。appendCrossoverは、この最適化戦略を決定する閾値の一つです。

従来のGoランタイムでは、append操作において、追加されるデータのサイズが特定の閾値(appendCrossover)以下の場合には、よりシンプルなバイト単位のコピー(whileループなど)が使用され、閾値を超える場合には、memmoveのようなより最適化されたメモリコピー関数が使用されるというロジックが存在しました。

しかし、Intelプラットフォーム(amd64および386)において、このappendCrossoverのデフォルト値(16バイト)が、小規模な追加操作のパフォーマンスにとって最適ではないことが判明しました。特に、1バイトから16バイト程度の非常に小さなデータの追加において、memmoveのような汎用的なメモリコピー関数を呼び出すオーバーヘッドが、単純なバイトコピーのオーバーヘッドを上回ってしまうケースがありました。

このコミットは、この非効率性を解消し、小規模なappend操作のパフォーマンスを向上させることを目的としています。

前提知識の解説

Goのスライスとappend関数

Go言語のスライスは、配列をラップした動的なデータ構造です。スライスは、内部的にポインタ、長さ(len)、容量(cap)の3つの要素で構成されます。append関数は、スライスに要素を追加するために使用されます。

append関数が呼び出されると、Goランタイムは以下のロジックに従って動作します。

  1. 容量の確認: 現在のスライスの容量が、追加される要素を格納するのに十分であるかを確認します。
  2. 再割り当て: 容量が不足している場合、ランタイムはより大きな新しい基底配列を割り当て、既存の要素を新しい配列にコピーし、追加される要素をその後に配置します。この際、新しい容量は通常、元の容量の2倍(またはそれ以上)に拡張されます。
  3. 要素のコピー: 容量が十分な場合、または新しい配列が割り当てられた場合、追加される要素がスライスの末尾にコピーされます。

appendCrossover

appendCrossoverは、Goランタイムがスライスや文字列のappend操作を行う際に、内部的に使用するメモリコピー戦略を切り替えるための閾値です。この値は、アーキテクチャ固有のヘッダーファイル(例: src/pkg/runtime/arch_amd64.h)で定義されています。

  • w <= appendCrossover の場合: 追加されるバイト数 wappendCrossover 以下の場合、ランタイムは通常、シンプルなループによるバイト単位のコピーを行います。これは、非常に小さなコピーサイズの場合に、memmoveのような最適化された関数を呼び出すオーバーヘッドを避けるためです。
  • w > appendCrossover の場合: 追加されるバイト数 wappendCrossover を超える場合、ランタイムは通常、memmoveのような、より高速で最適化されたメモリコピー関数を使用します。これらの関数は、大量のデータを効率的にコピーするためにアセンブリ言語などで最適化されています。

このコミットの目的は、Intelプラットフォームにおいて、このappendCrossoverの値を0に設定することです。これにより、追加されるデータのサイズに関わらず、常にmemmoveのような最適化されたメモリコピー関数が使用されるようになります。

ベンチマーク結果の読み方

コミットメッセージに記載されているベンチマーク結果は、go test -bench=.コマンドなどで取得されるパフォーマンス測定値です。

  • ns/op (nanoseconds per operation): 1回の操作にかかる平均ナノ秒。この値が小さいほど、パフォーマンスが良いことを示します。
  • delta: 変更前(old)と変更後(new)のパフォーマンスの差をパーセンテージで示します。負の値はパフォーマンスの向上(時間が短縮された)、正の値はパフォーマンスの低下(時間が長くなった)を示します。

技術的詳細

このコミットの核心は、appendCrossoverの値を0に設定することです。

従来のGoランタイムでは、appendCrossover16に設定されていました。これは、16バイト以下の追加操作ではバイト単位のコピーを、16バイトを超える追加操作ではmemmoveを使用するという意味です。しかし、Intelプロセッサの進化により、小規模なメモリコピーにおいてもmemmoveのような最適化されたルーチンが非常に効率的になっていました。むしろ、appendCrossoverの条件分岐や、バイト単位のループ処理のオーバーヘッドが、memmoveの呼び出しオーバーヘッドよりも大きくなるケースが発生していました。

appendCrossover = 0と設定することで、runtime·appendsliceおよびruntime·appendstr関数内の条件分岐if(w <= appendCrossover)が常に偽(w > 0の場合)となり、結果として常にelseブロックのmemmove相当の処理が実行されるようになります。

ただし、slice.cの変更を見ると、if(appendCrossover > 0 && w <= appendCrossover)という条件に変わっています。これは、appendCrossover0の場合には、この条件全体がfalseとなり、常にelseブロック(memmove相当の処理)が実行されることを意味します。つまり、appendCrossover0に設定されたプラットフォームでは、追加されるデータのサイズに関わらず、常に最適化されたメモリコピー関数が使用されるようになります。

この変更により、特に小規模なデータ(1バイトから16バイト程度)の追加において、条件分岐のオーバーヘッドや非効率なループ処理が回避され、memmoveによる高速なコピーが常に適用されるため、パフォーマンスが大幅に向上します。ベンチマーク結果からも、BenchmarkAppend4BytesからBenchmarkAppend16Bytes、および対応する文字列ベンチマークで顕著な改善が見られることが確認できます。

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

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

  1. src/pkg/runtime/append_test.go: ベンチマークテストの追加。
  2. src/pkg/runtime/arch_386.h: 386アーキテクチャのappendCrossoverの定義変更。
  3. src/pkg/runtime/arch_amd64.h: amd64アーキテクチャのappendCrossoverの定義変更。
  4. src/pkg/runtime/slice.c: append操作の内部ロジックの変更。

src/pkg/runtime/append_test.go

--- a/src/pkg/runtime/append_test.go
+++ b/src/pkg/runtime/append_test.go
@@ -38,10 +38,18 @@ func BenchmarkAppend4Bytes(b *testing.B) {
 	benchmarkAppendBytes(b, 4)
 }
 
+func BenchmarkAppend7Bytes(b *testing.B) {\n\tbenchmarkAppendBytes(b, 7)\n}\n+\n func BenchmarkAppend8Bytes(b *testing.B) {
 	benchmarkAppendBytes(b, 8)
 }
 
+func BenchmarkAppend15Bytes(b *testing.B) {\n\tbenchmarkAppendBytes(b, 15)\n}\n+\n func BenchmarkAppend16Bytes(b *testing.B) {
 	benchmarkAppendBytes(b, 16)
 }

このファイルでは、BenchmarkAppend7BytesBenchmarkAppend15Bytesという新しいベンチマーク関数が追加されています。これは、appendCrossoverの閾値付近のパフォーマンスをより詳細に測定するために導入されたものです。

src/pkg/runtime/arch_386.h

--- a/src/pkg/runtime/arch_386.h
+++ b/src/pkg/runtime/arch_386.h
@@ -6,6 +6,6 @@ enum {
 	thechar = '8',
 	BigEndian = 0,
 	CacheLineSize = 64,
-	appendCrossover = 16,
+	appendCrossover = 0,
 	PCQuantum = 1
 };

386アーキテクチャの定義ファイルで、appendCrossoverの値が16から0に変更されています。

src/pkg/runtime/arch_amd64.h

--- a/src/pkg/runtime/arch_amd64.h
+++ b/src/pkg/runtime/arch_amd64.h
@@ -6,6 +6,6 @@ enum {
 	thechar = '6',
 	BigEndian = 0,
 	CacheLineSize = 64,
-	appendCrossover = 16,
+	appendCrossover = 0,
 	PCQuantum = 1
 };

amd64アーキテクチャの定義ファイルで、appendCrossoverの値が16から0に変更されています。

src/pkg/runtime/slice.c

--- a/src/pkg/runtime/slice.c
+++ b/src/pkg/runtime/slice.c
@@ -95,7 +95,7 @@ runtime·appendslice(SliceType *t, Slice x, Slice y, Slice ret)
 	p = ret.array+ret.len*w;
 	q = y.array;
 	w *= y.len;
-	if(w <= appendCrossover) {
+	if(appendCrossover > 0 && w <= appendCrossover) {
 		if(p <= q || w <= p-q) // No overlap.
 			while(w-- > 0)
 				*p++ = *q++;
@@ -148,7 +148,7 @@ runtime·appendstr(SliceType *t, Slice x, String y, Slice ret)
 	w = y.len;
 	p = ret.array+ret.len;
 	q = y.str;
-	if(w <= appendCrossover) {
+	if(appendCrossover > 0 && w <= appendCrossover) {
 		while(w-- > 0)
 			*p++ = *q++;
 	} else {

runtime·appendsliceruntime·appendstr関数の内部ロジックが変更されています。条件分岐がif(w <= appendCrossover)からif(appendCrossover > 0 && w <= appendCrossover)に変更されました。

コアとなるコードの解説

src/pkg/runtime/slice.cにおける変更が、このコミットの動作の鍵となります。

変更前のコード:

if(w <= appendCrossover) {
    // バイト単位のコピー
} else {
    // memmove相当の最適化されたコピー
}

変更後のコード:

if(appendCrossover > 0 && w <= appendCrossover) {
    // バイト単位のコピー
} else {
    // memmove相当の最適化されたコピー
}

この変更により、appendCrossover0に設定されたamd64および386アーキテクチャでは、appendCrossover > 0という条件が常にfalseとなるため、ifブロック内のバイト単位のコピー処理が実行されなくなります。結果として、追加されるデータのサイズwがどれほど小さくても、常にelseブロック内のmemmove相当の最適化されたメモリコピー処理が実行されるようになります。

これは、現代のIntelプロセッサにおいて、memmoveのような最適化されたルーチンが非常に効率的であり、たとえ数バイトのコピーであっても、条件分岐やループのオーバーヘッドを考慮すると、そちらの方が高速であるという知見に基づいています。

ベンチマーク結果は、この変更が意図した効果をもたらしたことを明確に示しています。特に、BenchmarkAppend4BytesからBenchmarkAppend16Bytes、および対応する文字列ベンチマークにおいて、ns/opが大幅に減少し、パフォーマンスが大きく向上していることがわかります。これは、これらの小規模な追加操作が、より効率的なmemmoveパスを通るようになったためです。

関連リンク

  • Go Issue #4963: runtime: tune append crossover on amd64 and 386
    • このコミットが修正したGoのIssueです。詳細な議論や背景情報が記載されている可能性があります。
  • Goのappend関数のドキュメント: https://pkg.go.dev/builtin#append
  • Goの内部的なスライス実装に関する記事(一般的な情報源)

参考にした情報源リンク

  • Goのソースコード(特にsrc/pkg/runtime/ディレクトリ内のファイル)
  • GoのIssueトラッカー(#4963)
  • Goのベンチマークに関する一般的な情報
  • メモリコピー最適化に関する一般的なコンピュータアーキテクチャの知識
  • Dave Cheney氏のブログや関連するGoのパフォーマンスに関する記事(もしあれば)
    • (注: 特定のブログ記事への直接リンクは、検索結果に基づいて追加する必要がありますが、ここでは一般的な情報源として記載しています。)