[インデックス 11400] ファイルの概要
このコミットは、Go言語の標準ライブラリであるmath/big
パッケージ内のarith_amd64.s
ファイルに対する変更です。arith_amd64.s
は、AMD64アーキテクチャ向けに最適化された任意精度演算(math/big
パッケージの機能)のためのアセンブリコードを含んでいます。具体的には、数値のビット長を計算するbitLen
関数の実装が修正されています。
コミット
このコミットは、math/big
パッケージのbitLen
関数の戻り値の型がint
であることを考慮し、AMD64アーキテクチャ上でより適切な32ビット移動命令(MOVL
)を使用するように変更しています。これにより、コードの効率性が向上します。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1e09031f7f9293ba032c88abb55b373d341f01bf
元コミット内容
math/big: return type of bitLen is an int; use MOVL on amd64.
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/5577050
変更の背景
Go言語のmath/big
パッケージは、任意精度の整数および浮動小数点数演算を提供します。このパッケージには、与えられた数値が何ビットで表現できるかを計算するbitLen
という関数が存在します。
AMD64アーキテクチャにおいて、Go言語のint
型は通常64ビット幅です。しかし、bitLen
関数が返す「ビット数」という値は、最大でも64(64ビット整数のビット長)であり、多くの場合32ビットで十分に表現可能です。
元の実装では、bitLen
関数の結果を格納する際に、64ビット操作であるINCQ
(64ビットインクリメント)とMOVQ
(64ビット移動)を使用していました。これは、Goのint
型が64ビットであるという事実には合致しますが、実際に格納される値の範囲を考えると過剰な操作でした。
このコミットの背景には、以下の目的があります。
- 効率性の向上:
bitLen
の戻り値が32ビットで収まることを利用し、より軽量で高速な32ビット操作(INCL
とMOVL
)に切り替えることで、アセンブリコードの実行効率を改善します。 - 正確性の維持: 戻り値の型が
int
(64ビット)であっても、その値が32ビットに収まる限り、32ビット操作で値を設定しても上位ビットはゼロクリアされるため、正確性は保たれます。
この変更は、特定のアーキテクチャ(AMD64)における低レベルな最適化であり、math/big
パッケージ全体のパフォーマンス向上に寄与します。
前提知識の解説
このコミットの変更内容を理解するためには、以下の知識が役立ちます。
1. Go言語のmath/big
パッケージ
math/big
パッケージは、Go言語で任意精度の数値(整数、有理数、浮動小数点数)を扱うための機能を提供します。標準のint
やfloat64
型では表現できない非常に大きな数や、高い精度が求められる計算に使用されます。
bitLen
関数は、big.Int
型などの数値が何ビットで表現されるか(最上位ビットの位置)を計算するために内部的に使用されます。例えば、bitLen(7)
は3(バイナリで111
、3ビット必要)を返します。
2. x86-64 (AMD64) アセンブリ言語
Go言語は、パフォーマンスが重要な部分でアセンブリ言語を使用することがあります。arith_amd64.s
は、AMD64プロセッサ向けのアセンブリコードファイルです。
- レジスタ:
AX
: 汎用レジスタ。通常、関数の戻り値や演算結果を格納するために使用されます。AMD64ではRAX
が64ビット、EAX
が32ビット、AX
が16ビット、AL
/AH
が8ビットの下位部分を指します。Goのアセンブリでは、AX
と書くと文脈によって64ビットレジスタ全体を指すことが多いですが、命令によってはその下位部分に作用します。
- 命令:
BSRQ
(Bit Scan Reverse Quadword): 64ビットオペランド(Q
はQuadword、64ビットを意味)の最上位セットビット(1になっているビット)のインデックスを検索し、結果をデスティネーションオペランドに格納します。例えば、BSRQ 0b00100000, AX
はAX
に5を格納します(0から数えて5番目のビットがセットされているため)。入力が0の場合は、フラグがセットされ、結果は未定義になります。JZ
(Jump if Zero): 直前の命令の結果がゼロフラグ(ZF)をセットした場合に、指定されたラベルにジャンプします。BSRQ
命令は、入力が0の場合にゼロフラグをセットします。INCQ
(Increment Quadword): 64ビットオペランドの値を1増やします。INCL
(Increment Longword): 32ビットオペランドの値を1増やします。MOVQ
(Move Quadword): 64ビットデータをソースからデスティネーションへ移動します。MOVL
(Move Longword): 32ビットデータをソースからデスティネーションへ移動します。RET
: サブルーチンから戻ります。
- Goアセンブリのディレクティブ:
TEXT ·bitLen(SB),7,$0
:bitLen
という関数を定義します。SB
はStatic Baseで、グローバルシンボルを指します。7
はスタックフレームのフラグ、$0
はローカル変数のサイズを示します。x+0(FP)
: 関数引数x
へのオフセット指定。FP
はフレームポインタで、スタックフレーム内の引数やローカル変数にアクセスするために使用されます。n+8(FP)
: 戻り値n
へのオフセット指定。Goの関数呼び出し規約では、戻り値もスタックフレームに配置されることがあります。
3. Go言語のint
型のサイズ
Go言語のint
型は、実行されるアーキテクチャに依存します。32ビットシステムでは32ビット、64ビットシステムでは64ビットの符号付き整数です。AMD64は64ビットシステムであるため、int
はint64
として扱われます。
技術的詳細
このコミットの核心は、bitLen
関数のアセンブリ実装におけるデータ移動とインクリメントの命令を、64ビット操作から32ビット操作へと変更した点にあります。
元のコードは以下のようになっていました。
TEXT ·bitLen(SB),7,$0
BSRQ x+0(FP), AX
JZ Z1
INCQ AX // AXを64ビットでインクリメント
MOVQ AX, n+8(FP) // AXの64ビット値を戻り値nに格納
RET
Z1: MOVQ $0, n+8(FP) // 0を64ビットで戻り値nに格納
RET
変更後のコードは以下の通りです。
TEXT ·bitLen(SB),7,$0
BSRQ x+0(FP), AX
JZ Z1
INCL AX // AXの下位32ビットをインクリメント
MOVL AX, n+8(FP) // AXの下位32ビット値を戻り値nに格納
RET
Z1: MOVL $0, n+8(FP) // 0を32ビットで戻り値nに格納
RET
変更の論理:
BSRQ x+0(FP), AX
: この命令は、入力値x
の最上位セットビットのインデックスをAX
レジスタ(64ビット)に格納します。bitLen
が返す値は、最大でも64(64ビット整数のビット長)です。この値は、32ビット整数で十分に表現できます。INCQ AX
からINCL AX
:INCQ AX
はAX
レジスタ全体(64ビット)をインクリメントします。INCL AX
はAX
レジスタの下位32ビット(EAX
部分)のみをインクリメントします。上位32ビットは変更されません。bitLen
の戻り値が32ビットで収まるため、INCL
を使用しても結果の正確性は保たれます。これにより、プロセッサはより小さなデータパスで操作を実行でき、効率が向上する可能性があります。
MOVQ AX, n+8(FP)
からMOVL AX, n+8(FP)
:MOVQ AX, n+8(FP)
はAX
レジスタ全体(64ビット)の値を、戻り値n
が格納されるメモリ位置n+8(FP)
に64ビットで移動します。MOVL AX, n+8(FP)
はAX
レジスタの下位32ビット(EAX
部分)の値を、戻り値n
が格納されるメモリ位置に32ビットで移動します。- Goの
int
型は64ビットですが、MOVL
で32ビット値を格納した場合、残りの上位32ビットはゼロクリアされます。bitLen
の戻り値は常に非負であり、32ビットで収まるため、このゼロクリアは問題ありません。むしろ、不要な64ビット操作を避けることで、メモリ帯域幅の節約やキャッシュ効率の向上に繋がる可能性があります。
Z1:
ラベル内の変更:- 入力値がゼロの場合に
bitLen
が0を返すパスでも、同様にMOVQ $0, n+8(FP)
からMOVL $0, n+8(FP)
に変更されています。これは、0という値も32ビットで表現可能であるため、一貫した最適化が適用されています。
- 入力値がゼロの場合に
この変更は、Goのコンパイラやランタイムが生成するアセンブリコードの品質を向上させるための、細かながらも重要な最適化の一例です。
コアとなるコードの変更箇所
src/pkg/math/big/arith_amd64.s
ファイルにおいて、以下の行が変更されました。
--- a/src/pkg/math/big/arith_amd64.s
+++ b/src/pkg/math/big/arith_amd64.s
@@ -266,9 +266,9 @@ E7: SUBL $1, BX // i--
TEXT ·bitLen(SB),7,$0
BSRQ x+0(FP), AX
JZ Z1
- INCQ AX
- MOVQ AX, n+8(FP)
+ INCL AX
+ MOVL AX, n+8(FP)
RET
-Z1: MOVQ $0, n+8(FP)
+Z1: MOVL $0, n+8(FP)
RET
コアとなるコードの解説
-
- INCQ AX
から+ INCL AX
:INCQ AX
は、AX
レジスタ(64ビット)の値を1だけインクリメントする命令です。INCL AX
は、AX
レジスタの下位32ビット(EAX
)の値を1だけインクリメントする命令です。bitLen
関数が返すビット長は最大でも64であり、32ビットで表現可能なため、より効率的な32ビットインクリメント命令に置き換えられました。
-
- MOVQ AX, n+8(FP)
から+ MOVL AX, n+8(FP)
:MOVQ AX, n+8(FP)
は、AX
レジスタ(64ビット)の値を、戻り値n
が格納されるスタック上のメモリ位置(n+8(FP)
)に64ビットで移動する命令です。MOVL AX, n+8(FP)
は、AX
レジスタの下位32ビット(EAX
)の値を、同じメモリ位置に32ビットで移動する命令です。- Goの
int
型は64ビットですが、bitLen
の戻り値が32ビットで収まるため、32ビット移動命令を使用しても問題ありません。これにより、不要な64ビット操作を削減し、パフォーマンスを向上させます。
-
-Z1: MOVQ $0, n+8(FP)
から+Z1: MOVL $0, n+8(FP)
:Z1
ラベルは、bitLen
の入力値がゼロの場合にジャンプする場所です。この場合、ビット長は0となります。- ここでも同様に、0という値を戻り値に格納する際に、64ビット移動命令(
MOVQ
)から32ビット移動命令(MOVL
)に置き換えられました。これは、0も32ビットで表現可能であるため、一貫した最適化が適用されています。
これらの変更は、bitLen
関数のアセンブリ実装において、実際に扱われる値の範囲に合わせて命令のビット幅を最適化することで、実行効率を高めることを目的としています。
関連リンク
- Go Code Review (CL):
- https://golang.org/cl/5577050 このリンクは、このコミットがGoのコードレビューシステム(Gerrit)でどのように議論され、承認されたかを示すものです。詳細な議論や背景情報が含まれている場合があります。
参考にした情報源リンク
- Go言語公式ドキュメント:
math/big
パッケージ: https://pkg.go.dev/math/big- Goのアセンブリ言語に関するドキュメント: https://go.dev/doc/asm
- x86-64 (AMD64) 命令セットリファレンス:
- Intel 64 and IA-32 Architectures Software Developer's Manuals (特にVolume 2A: Instruction Set Reference, A-M): https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
BSR
,INC
,MOV
などの命令の詳細を確認できます。
- Go言語の
int
型のサイズに関する情報:- Go言語の仕様: https://go.dev/ref/spec#Numeric_types
- Goの
int
型がアーキテクチャ依存であることを確認できます。