[インデックス 13006] ファイルの概要
このコミットは、Go言語の標準ライブラリであるbytes
パッケージに、ARMアーキテクチャ向けのアセンブリ最適化されたEqual
関数の実装を追加するものです。これにより、特にARMプロセッサ上でのバイトスライス比較のパフォーマンスが大幅に向上しています。
コミット
commit d472d3faf17490e1c9b1c38d78ebe65baead30fa
Author: Dave Cheney <dave@cheney.net>
Date: Wed May 2 12:10:24 2012 +1000
bytes: add assembly version of Equal for ARM
BenchmarkEqual32 662 159 -75.98%
BenchmarkEqual4K 76545 13719 -82.08%
BenchmarkEqual4M 90136700 23588870 -73.83%
BenchmarkEqual64M 2147483647 1419616000 -42.63%
BenchmarkEqual32 48.32 201.15 4.16x
BenchmarkEqual4K 53.51 298.56 5.58x
BenchmarkEqual4M 46.53 177.81 3.82x
BenchmarkEqual64M 27.12 47.27 1.74x
R=golang-dev, qyzhai, minux.ma, rsc, iant, nigeltao
CC=golang-dev
https://golang.org/cl/6118049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d472d3faf17490e1c9b1c38d78ebe65baead30fa
元コミット内容
このコミットは、Go言語のbytes
パッケージ内のEqual
関数に、ARMアーキテクチャ向けのアセンブリ言語による最適化バージョンを追加するものです。コミットメッセージには、ベンチマーク結果が示されており、様々なサイズのバイトスライス(32バイト、4KB、4MB、64MB)に対するEqual
関数の実行時間が大幅に改善されていることがわかります。
具体的には、以下のパフォーマンス向上が報告されています。
BenchmarkEqual32
: 実行時間が75.98%削減され、4.16倍高速化。BenchmarkEqual4K
: 実行時間が82.08%削減され、5.58倍高速化。BenchmarkEqual4M
: 実行時間が73.83%削減され、3.82倍高速化。BenchmarkEqual64M
: 実行時間が42.63%削減され、1.74倍高速化。
これらの結果は、特に小規模から中規模のバイトスライスにおいて顕著な改善が見られることを示しています。
変更の背景
Go言語は、クロスプラットフォーム対応を重視しており、様々なCPUアーキテクチャで動作します。しかし、汎用的なGoコードは、特定のアーキテクチャの特性を最大限に活かせない場合があります。特に、頻繁に実行される低レベルの操作(例: バイトスライスの比較、メモリコピーなど)では、アセンブリ言語による最適化がパフォーマンスに大きな影響を与えることがあります。
bytes.Equal
関数は、2つのバイトスライスが等しいかどうかを比較する基本的な操作であり、多くのGoプログラムで内部的に利用されます。例えば、ネットワークプロトコルの処理、ファイルI/O、ハッシュ計算など、バイトスライスの比較がボトルネックとなるシナリオは多岐にわたります。
このコミットの背景には、ARMアーキテクチャ上でのGoプログラムの実行性能を向上させるという明確な目的があります。当時、ARMプロセッサはモバイルデバイスや組み込みシステムで広く採用され始めており、Go言語がこれらのプラットフォームでより効率的に動作することが求められていました。アセンブリ言語を使用することで、CPUのレジスタを直接操作し、ループの最適化や分岐予測の改善など、Goコンパイラが生成するコードでは実現が難しい低レベルの最適化が可能になります。
前提知識の解説
Go言語のbytes
パッケージ
bytes
パッケージは、バイトスライス([]byte
)を操作するためのユーティリティ関数を提供します。Equal
関数は、このパッケージの基本的な機能の一つで、2つのバイトスライスの内容が完全に一致するかどうかを効率的に比較します。
アセンブリ言語とGo
Go言語は通常、Goコンパイラによって機械語にコンパイルされますが、パフォーマンスがクリティカルな部分では、特定のアセンブリ言語で書かれたコードをGoプログラムに組み込むことができます。Goのアセンブリは、Plan 9アセンブラの文法に基づいており、一般的なx86やARMのアセンブリとは異なる独自の記法を持ちます。
TEXT
: 関数の開始を宣言します。例:TEXT ·Equal(SB),7,$0
は、Equal
という名前の関数を定義し、スタックフレームサイズや引数のオフセットなどを指定します。SB
(Static Base): グローバルシンボルや外部シンボルを参照するための擬似レジスタです。FP
(Frame Pointer): 現在のスタックフレームの引数やローカル変数を参照するための擬似レジスタです。- レジスタ: ARMプロセッサには、R0からR15までの汎用レジスタがあります。Goのアセンブリでは、これらのレジスタを直接使用してデータを操作します。
- 命令:
MOVW
: ワード(4バイト)を移動します。CMP
: 2つのオペランドを比較し、フラグレジスタを設定します。B.NE
(Branch if Not Equal): 比較結果が等しくない場合に分岐します。B.EQ
(Branch if Equal): 比較結果が等しい場合に分岐します。ADD
: 加算を行います。MOVBU.P
: バイトをロードし、アドレスをインクリメントします(Post-indexed addressing)。これは、ループ内で配列やスライスを効率的に走査する際に使用されます。RET
: 関数から戻ります。
ベンチマーク
Go言語には、標準でベンチマーク機能が組み込まれています。go test -bench=.
コマンドを実行することで、Benchmark
プレフィックスを持つ関数が実行され、そのパフォーマンスが測定されます。コミットメッセージに記載されているベンチマーク結果は、この機能によって得られたものです。
技術的詳細
このコミットで追加されたアセンブリコードは、bytes.Equal
関数のARMアーキテクチャ向けの実装です。Goのbytes.Equal
は、通常、2つのバイトスライスの長さが同じであるかを確認し、その後、各要素を順に比較していきます。アセンブリ実装では、この比較処理をCPUのレジスタと命令を直接使って最適化します。
アセンブリコードの主な最適化ポイントは以下の通りです。
- 長さの比較: まず、2つのバイトスライスの長さが等しいかを確認します。長さが異なる場合、それらは等しくないため、すぐに結果を返します。これはGoの汎用実装でも行われますが、アセンブリではより直接的にレジスタ操作で行われます。
- バイトごとの比較ループ: 長さが同じ場合、アセンブリコードはバイトごとに比較するループに入ります。
MOVBU.P
命令を使用することで、メモリからバイトをロードすると同時に、ポインタを次のバイトに自動的に進めることができます。これにより、ループ内の命令数を減らし、効率を高めます。- 比較結果が等しくない場合、すぐにループを終了し、
false
を返します。 - すべてのバイトが比較され、すべてが等しい場合、ループを終了し、
true
を返します。
- レジスタの活用: 頻繁にアクセスされるデータ(スライスのアドレス、長さ、現在のポインタなど)をCPUレジスタに保持することで、メモリへのアクセス回数を減らし、処理速度を向上させます。
このアセンブリ実装は、Goコンパイラが生成する汎用的なループよりも、ARMプロセッサのパイプライン処理やキャッシュ効率をより考慮した設計になっています。特に、MOVBU.P
のようなポストインデックスアドレッシングモードは、連続したメモリ領域へのアクセスを最適化する上で非常に有効です。
コアとなるコードの変更箇所
変更はsrc/pkg/bytes/asm_arm.s
ファイルに対して行われています。
--- a/src/pkg/bytes/asm_arm.s
+++ b/src/pkg/bytes/asm_arm.s
@@ -27,4 +27,30 @@ _notfound:
RET
TEXT ·Equal(SB),7,$0
- B ·equalPortable(SB)
+ MOVW alen+4(FP), R1
+ MOVW blen+16(FP), R3
+
+ CMP R1, R3 // unequal lengths are not equal
+ B.NE _notequal
+
+ MOVW aptr+0(FP), R0
+ MOVW bptr+12(FP), R2
+ ADD R0, R1 // end
+
+_next:
+ CMP R0, R1
+ B.EQ _equal // reached the end
+ MOVBU.P 1(R0), R4
+ MOVBU.P 1(R2), R5
+ CMP R4, R5
+ B.EQ _next
+
+_notequal:
+ MOVW $0, R0
+ MOVW R0, equal+24(FP)
+ RET
+
+_equal:
+ MOVW $1, R0
+ MOVW R0, equal+24(FP)
+ RET
この差分は、Equal
関数の実装が、以前のB ·equalPortable(SB)
(Goで書かれたポータブルな実装への分岐)から、ARMアセンブリによる直接的な実装に置き換えられたことを示しています。
コアとなるコードの解説
追加されたARMアセンブリコードのEqual
関数は以下のロジックで動作します。
TEXT ·Equal(SB),7,$0
MOVW alen+4(FP), R1 // スライスaの長さをR1にロード
MOVW blen+16(FP), R3 // スライスbの長さをR3にロード
CMP R1, R3 // R1 (aの長さ) と R3 (bの長さ) を比較
B.NE _notequal // 長さが異なる場合、_notequalへ分岐(falseを返す)
MOVW aptr+0(FP), R0 // スライスaの先頭アドレスをR0にロード
MOVW bptr+12(FP), R2 // スライスbの先頭アドレスをR2にロード
ADD R0, R1 // R0 (aの先頭アドレス) に R1 (aの長さ) を加算し、R1に格納。これはスライスaの終端アドレスとなる。
_next:
CMP R0, R1 // R0 (現在のaのポインタ) と R1 (aの終端アドレス) を比較
B.EQ _equal // ポインタが終端に達した場合、_equalへ分岐(trueを返す)
MOVBU.P 1(R0), R4 // R0が指すアドレスから1バイトをR4にロードし、R0を1バイト進める
MOVBU.P 1(R2), R5 // R2が指すアドレスから1バイトをR5にロードし、R2を1バイト進める
CMP R4, R5 // R4 (aのバイト) と R5 (bのバイト) を比較
B.EQ _next // バイトが等しい場合、_nextへ分岐して次のバイトを比較
_notequal:
MOVW $0, R0 // R0に0をロード(falseを表す)
MOVW R0, equal+24(FP) // 戻り値の場所にR0の値を格納
RET // 関数から戻る
_equal:
MOVW $1, R0 // R0に1をロード(trueを表す)
MOVW R0, equal+24(FP) // 戻り値の場所にR0の値を格納
RET // 関数から戻る
詳細な解説:
-
引数のロード:
alen+4(FP)
とblen+16(FP)
は、スタックフレーム上の引数(スライスの長さ)へのオフセットを示します。Goのスライスは、ポインタ、長さ、容量の3つの要素で構成されるため、それぞれの引数(a
とb
)の長さフィールドにアクセスするためにオフセットが使用されます。aptr+0(FP)
とbptr+12(FP)
は、同様にスライスの先頭ポインタへのオフセットです。
-
長さの比較:
CMP R1, R3
で2つのスライスの長さを比較します。B.NE _notequal
は、長さが等しくない(Not Equal)場合に、_notequal
ラベルにジャンプし、false
を返します。
-
ループの初期化:
ADD R0, R1
は、スライスa
の先頭アドレス(R0)に長さ(R1)を加算し、その結果をR1に格納します。これにより、R1はスライスa
の終端アドレス(比較を終了するアドレス)として機能します。
-
バイトごとの比較ループ (
_next
):CMP R0, R1
: 現在のポインタR0が終端アドレスR1に達したかどうかをチェックします。B.EQ _equal
: ポインタが終端に達した場合(すべてのバイトが比較され、等しかった場合)、_equal
ラベルにジャンプし、true
を返します。MOVBU.P 1(R0), R4
: R0が指すメモリ位置から符号なしバイトをR4レジスタにロードし、同時にR0レジスタの値を1だけインクリメントします。これは「ポストインデックスアドレッシング」と呼ばれるもので、ループ内でポインタを自動的に進めるため、効率的です。MOVBU.P 1(R2), R5
: 同様に、R2が指すメモリ位置からバイトをR5にロードし、R2を1インクリメントします。CMP R4, R5
: ロードした2つのバイトを比較します。B.EQ _next
: 2つのバイトが等しい場合、_next
ラベルにジャンプしてループを続行します。
-
結果の返却:
_notequal
: 長さが異なるか、途中で異なるバイトが見つかった場合に到達します。R0に0
(false)を設定し、戻り値の場所に格納して関数を終了します。_equal
: すべてのバイトが等しかった場合に到達します。R0に1
(true)を設定し、戻り値の場所に格納して関数を終了します。
このアセンブリコードは、Goコンパイラが生成する一般的なループよりも、ARMプロセッサの命令セットとレジスタをより効率的に利用することで、バイトスライス比較のパフォーマンスを最大化しています。特に、MOVBU.P
命令は、メモリからの連続的なデータロードとポインタの更新を単一の命令で行えるため、ループのオーバーヘッドを削減し、高速化に貢献しています。
関連リンク
- Go CL 6118049: https://golang.org/cl/6118049 (このコミットに対応するGoのコードレビューシステム上のチェンジリスト)
参考にした情報源リンク
- Go言語の
bytes
パッケージ: https://pkg.go.dev/bytes - Goのアセンブリ言語: https://go.dev/doc/asm
- ARMアーキテクチャの命令セット(一般的な情報源)
- Goのベンチマーク: https://go.dev/doc/tutorial/add-benchmarks
- Dave Cheneyのブログ (Goのパフォーマンスに関する記事など): https://dave.cheney.net/ (当時の情報源として)
- Plan 9アセンブラの文法に関する情報 (Goのアセンブリのベース): https://9p.io/sys/doc/asm.html```
[インデックス 13006] ファイルの概要
このコミットは、Go言語の標準ライブラリであるbytes
パッケージに、ARMアーキテクチャ向けのアセンブリ最適化されたEqual
関数の実装を追加するものです。これにより、特にARMプロセッサ上でのバイトスライス比較のパフォーマンスが大幅に向上しています。
コミット
commit d472d3faf17490e1c9b1c38d78ebe65baead30fa
Author: Dave Cheney <dave@cheney.net>
Date: Wed May 2 12:10:24 2012 +1000
bytes: add assembly version of Equal for ARM
BenchmarkEqual32 662 159 -75.98%
BenchmarkEqual4K 76545 13719 -82.08%
BenchmarkEqual4M 90136700 23588870 -73.83%
BenchmarkEqual64M 2147483647 1419616000 -42.63%
BenchmarkEqual32 48.32 201.15 4.16x
BenchmarkEqual4K 53.51 298.56 5.58x
BenchmarkEqual4M 46.53 177.81 3.82x
BenchmarkEqual64M 27.12 47.27 1.74x
R=golang-dev, qyzhai, minux.ma, rsc, iant, nigeltao
CC=golang-dev
https://golang.org/cl/6118049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d472d3faf17490e1c9b1c38d78ebe65baead30fa
元コミット内容
このコミットは、Go言語のbytes
パッケージ内のEqual
関数に、ARMアーキテクチャ向けのアセンブリ言語による最適化バージョンを追加するものです。コミットメッセージには、ベンチマーク結果が示されており、様々なサイズのバイトスライス(32バイト、4KB、4MB、64MB)に対するEqual
関数の実行時間が大幅に改善されていることがわかります。
具体的には、以下のパフォーマンス向上が報告されています。
BenchmarkEqual32
: 実行時間が75.98%削減され、4.16倍高速化。BenchmarkEqual4K
: 実行時間が82.08%削減され、5.58倍高速化。BenchmarkEqual4M
: 実行時間が73.83%削減され、3.82倍高速化。BenchmarkEqual64M
: 実行時間が42.63%削減され、1.74倍高速化。
これらの結果は、特に小規模から中規模のバイトスライスにおいて顕著な改善が見られることを示しています。
変更の背景
Go言語は、クロスプラットフォーム対応を重視しており、様々なCPUアーキテクチャで動作します。しかし、汎用的なGoコードは、特定のアーキテクチャの特性を最大限に活かせない場合があります。特に、頻繁に実行される低レベルの操作(例: バイトスライスの比較、メモリコピーなど)では、アセンブリ言語による最適化がパフォーマンスに大きな影響を与えることがあります。
bytes.Equal
関数は、2つのバイトスライスが等しいかどうかを比較する基本的な操作であり、多くのGoプログラムで内部的に利用されます。例えば、ネットワークプロトコルの処理、ファイルI/O、ハッシュ計算など、バイトスライスの比較がボトルネックとなるシナリオは多岐にわたります。
このコミットの背景には、ARMアーキテクチャ上でのGoプログラムの実行性能を向上させるという明確な目的があります。当時、ARMプロセッサはモバイルデバイスや組み込みシステムで広く採用され始めており、Go言語がこれらのプラットフォームでより効率的に動作することが求められていました。アセンブリ言語を使用することで、CPUのレジスタを直接操作し、ループの最適化や分岐予測の改善など、Goコンパイラが生成するコードでは実現が難しい低レベルの最適化が可能になります。
前提知識の解説
Go言語のbytes
パッケージ
bytes
パッケージは、バイトスライス([]byte
)を操作するためのユーティリティ関数を提供します。Equal
関数は、このパッケージの基本的な機能の一つで、2つのバイトスライスの内容が完全に一致するかどうかを効率的に比較します。
アセンブリ言語とGo
Go言語は通常、Goコンパイラによって機械語にコンパイルされますが、パフォーマンスがクリティカルな部分では、特定のアセンブリ言語で書かれたコードをGoプログラムに組み込むことができます。Goのアセンブリは、Plan 9アセンブラの文法に基づいており、一般的なx86やARMのアセンブリとは異なる独自の記法を持ちます。
TEXT
: 関数の開始を宣言します。例:TEXT ·Equal(SB),7,$0
は、Equal
という名前の関数を定義し、スタックフレームサイズや引数のオフセットなどを指定します。SB
(Static Base): グローバルシンボルや外部シンボルを参照するための擬似レジスタです。FP
(Frame Pointer): 現在のスタックフレームの引数やローカル変数を参照するための擬似レジスタです。- レジスタ: ARMプロセッサには、R0からR15までの汎用レジスタがあります。Goのアセンブリでは、これらのレジスタを直接使用してデータを操作します。
- 命令:
MOVW
: ワード(4バイト)を移動します。CMP
: 2つのオペランドを比較し、フラグレジスタを設定します。B.NE
(Branch if Not Equal): 比較結果が等しくない場合に分岐します。B.EQ
(Branch if Equal): 比較結果が等しい場合に分岐します。ADD
: 加算を行います。MOVBU.P
: バイトをロードし、アドレスをインクリメントします(Post-indexed addressing)。これは、ループ内で配列やスライスを効率的に走査する際に使用されます。RET
: 関数から戻ります。
ベンチマーク
Go言語には、標準でベンチマーク機能が組み込まれています。go test -bench=.
コマンドを実行することで、Benchmark
プレフィックスを持つ関数が実行され、そのパフォーマンスが測定されます。コミットメッセージに記載されているベンチマーク結果は、この機能によって得られたものです。
技術的詳細
このコミットで追加されたアセンブリコードは、bytes.Equal
関数のARMアーキテクチャ向けの実装です。Goのbytes.Equal
は、通常、2つのバイトスライスの長さが同じであるかを確認し、その後、各要素を順に比較していきます。アセンブリ実装では、この比較処理をCPUのレジスタと命令を直接使って最適化します。
アセンブリコードの主な最適化ポイントは以下の通りです。
- 長さの比較: まず、2つのバイトスライスの長さが等しいかを確認します。長さが異なる場合、それらは等しくないため、すぐに結果を返します。これはGoの汎用実装でも行われますが、アセンブリではより直接的にレジスタ操作で行われます。
- バイトごとの比較ループ: 長さが同じ場合、アセンブリコードはバイトごとに比較するループに入ります。
MOVBU.P
命令を使用することで、メモリからバイトをロードすると同時に、ポインタを次のバイトに自動的に進めることができます。これにより、ループ内の命令数を減らし、効率を高めます。- 比較結果が等しくない場合、すぐにループを終了し、
false
を返します。 - すべてのバイトが比較され、すべてが等しい場合、ループを終了し、
true
を返します。
- レジスタの活用: 頻繁にアクセスされるデータ(スライスのアドレス、長さ、現在のポインタなど)をCPUレジスタに保持することで、メモリへのアクセス回数を減らし、処理速度を向上させます。
このアセンブリ実装は、Goコンパイラが生成する汎用的なループよりも、ARMプロセッサのパイプライン処理やキャッシュ効率をより考慮した設計になっています。特に、MOVBU.P
のようなポストインデックスアドレッシングモードは、連続したメモリ領域へのアクセスを最適化する上で非常に有効です。
コアとなるコードの変更箇所
変更はsrc/pkg/bytes/asm_arm.s
ファイルに対して行われています。
--- a/src/pkg/bytes/asm_arm.s
+++ b/src/pkg/bytes/asm_arm.s
@@ -27,4 +27,30 @@ _notfound:
RET
TEXT ·Equal(SB),7,$0
- B ·equalPortable(SB)
+ MOVW alen+4(FP), R1
+ MOVW blen+16(FP), R3
+
+ CMP R1, R3 // unequal lengths are not equal
+ B.NE _notequal
+
+ MOVW aptr+0(FP), R0
+ MOVW bptr+12(FP), R2
+ ADD R0, R1 // end
+
+_next:
+ CMP R0, R1
+ B.EQ _equal // reached the end
+ MOVBU.P 1(R0), R4
+ MOVBU.P 1(R2), R5
+ CMP R4, R5
+ B.EQ _next
+
+_notequal:
+ MOVW $0, R0
+ MOVW R0, equal+24(FP)
+ RET
+
+_equal:
+ MOVW $1, R0
+ MOVW R0, equal+24(FP)
+ RET
この差分は、Equal
関数の実装が、以前のB ·equalPortable(SB)
(Goで書かれたポータブルな実装への分岐)から、ARMアセンブリによる直接的な実装に置き換えられたことを示しています。
コアとなるコードの解説
追加されたARMアセンブリコードのEqual
関数は以下のロジックで動作します。
TEXT ·Equal(SB),7,$0
MOVW alen+4(FP), R1 // スライスaの長さをR1にロード
MOVW blen+16(FP), R3 // スライスbの長さをR3にロード
CMP R1, R3 // R1 (aの長さ) と R3 (bの長さ) を比較
B.NE _notequal // 長さが異なる場合、_notequalへ分岐(falseを返す)
MOVW aptr+0(FP), R0 // スライスaの先頭アドレスをR0にロード
MOVW bptr+12(FP), R2 // スライスbの先頭アドレスをR2にロード
ADD R0, R1 // R0 (aの先頭アドレス) に R1 (aの長さ) を加算し、R1に格納。これはスライスaの終端アドレスとなる。
_next:
CMP R0, R1 // R0 (現在のaのポインタ) と R1 (aの終端アドレス) を比較
B.EQ _equal // ポインタが終端に達した場合、_equalへ分岐(trueを返す)
MOVBU.P 1(R0), R4 // R0が指すアドレスから1バイトをR4にロードし、R0を1バイト進める
MOVBU.P 1(R2), R5 // R2が指すアドレスから1バイトをR5にロードし、R2を1バイト進める
CMP R4, R5 // R4 (aのバイト) と R5 (bのバイト) を比較
B.EQ _next // バイトが等しい場合、_nextへ分岐して次のバイトを比較
_notequal:
MOVW $0, R0 // R0に0をロード(falseを表す)
MOVW R0, equal+24(FP) // 戻り値の場所にR0の値を格納
RET // 関数から戻る
_equal:
MOVW $1, R0 // R0に1をロード(trueを表す)
MOVW R0, equal+24(FP) // 戻り値の場所にR0の値を格納
RET // 関数から戻る
詳細な解説:
-
引数のロード:
alen+4(FP)
とblen+16(FP)
は、スタックフレーム上の引数(スライスの長さ)へのオフセットを示します。Goのスライスは、ポインタ、長さ、容量の3つの要素で構成されるため、それぞれの引数(a
とb
)の長さフィールドにアクセスするためにオフセットが使用されます。aptr+0(FP)
とbptr+12(FP)
は、同様にスライスの先頭ポインタへのオフセットです。
-
長さの比較:
CMP R1, R3
で2つのスライスの長さを比較します。B.NE _notequal
は、長さが等しくない(Not Equal)場合に、_notequal
ラベルにジャンプし、false
を返します。
-
ループの初期化:
ADD R0, R1
は、スライスa
の先頭アドレス(R0)に長さ(R1)を加算し、その結果をR1に格納します。これにより、R1はスライスa
の終端アドレス(比較を終了するアドレス)として機能します。
-
バイトごとの比較ループ (
_next
):CMP R0, R1
: 現在のポインタR0が終端アドレスR1に達したかどうかをチェックします。B.EQ _equal
: ポインタが終端に達した場合(すべてのバイトが比較され、等しかった場合)、_equal
ラベルにジャンプし、true
を返します。MOVBU.P 1(R0), R4
: R0が指すメモリ位置から符号なしバイトをR4レジスタにロードし、同時にR0レジスタの値を1だけインクリメントします。これは「ポストインデックスアドレッシング」と呼ばれるもので、ループ内でポインタを自動的に進めるため、効率的です。MOVBU.P 1(R2), R5
: 同様に、R2が指すメモリ位置からバイトをR5にロードし、R2を1インクリメントします。CMP R4, R5
: ロードした2つのバイトを比較します。B.EQ _next
: 2つのバイトが等しい場合、_next
ラベルにジャンプしてループを続行します。
-
結果の返却:
_notequal
: 長さが異なるか、途中で異なるバイトが見つかった場合に到達します。R0に0
(false)を設定し、戻り値の場所に格納して関数を終了します。_equal
: すべてのバイトが等しかった場合に到達します。R0に1
(true)を設定し、戻り値の場所に格納して関数を終了します。
このアセンブリコードは、Goコンパイラが生成する一般的なループよりも、ARMプロセッサの命令セットとレジスタをより効率的に利用することで、バイトスライス比較のパフォーマンスを最大化しています。特に、MOVBU.P
命令は、メモリからの連続的なデータロードとポインタの更新を単一の命令で行えるため、ループのオーバーヘッドを削減し、高速化に貢献しています。
関連リンク
- Go CL 6118049: https://golang.org/cl/6118049 (このコミットに対応するGoのコードレビューシステム上のチェンジリスト)
参考にした情報源リンク
- Go言語の
bytes
パッケージ: https://pkg.go.dev/bytes - Goのアセンブリ言語: https://go.dev/doc/asm
- ARMアーキテクチャの命令セット(一般的な情報源)
- Goのベンチマーク: https://go.dev/doc/tutorial/add-benchmarks
- Dave Cheneyのブログ (Goのパフォーマンスに関する記事など): https://dave.cheney.net/ (当時の情報源として)
- Plan 9アセンブラの文法に関する情報 (Goのアセンブリのベース): https://9p.io/sys/doc/asm.html