[インデックス 12934] ファイルの概要
このコミットは、Go言語の標準ライブラリ math パッケージにおける Abs 関数のARMアーキテクチャ向けアセンブリ実装に関するものです。具体的には、src/pkg/math/abs_arm.s ファイルが変更されています。
コミット
commit e7e7b1c55ce7d73812caff018d02545f8f5740aa
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Mon Apr 23 23:47:36 2012 +0800
math: ARM assembly implementation for Abs
Obtained on 700MHz OMAP4460:
benchmark old ns/op new ns/op delta
BenchmarkAbs 61 23 -61.63%
R=dave, remyoudompheng, mtj, rsc
CC=golang-dev
https://golang.org/cl/6094047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e7e7b1c55ce7d73812caff018d02545f8f5740aa
元コミット内容
math: ARM assembly implementation for Abs
Obtained on 700MHz OMAP4460:
benchmark old ns/op new ns/op delta
BenchmarkAbs 61 23 -61.63%
R=dave, remyoudompheng, mtj, rsc
CC=golang-dev
https://golang.org/cl/6094047
変更の背景
このコミットの主な背景は、Go言語の math.Abs 関数(浮動小数点数の絶対値を計算する関数)のARMアーキテクチャにおけるパフォーマンス改善です。コミットメッセージに記載されているベンチマーク結果が示すように、700MHzのOMAP4460プロセッサ上で BenchmarkAbs の実行時間が61ns/opから23ns/opへと大幅に短縮されており、これは約61.63%の性能向上に相当します。
Go言語はクロスプラットフォーム対応を重視しており、様々なアーキテクチャで効率的に動作することが求められます。特にARMプロセッサはモバイルデバイスや組み込みシステムで広く利用されているため、これらの環境でのパフォーマンス最適化は重要です。math.Abs のような基本的な数値演算関数は頻繁に呼び出される可能性があるため、その性能改善はアプリケーション全体の高速化に寄与します。
この性能向上は、Goのランタイムが提供する汎用的な実装ではなく、ARM固有のアセンブリ言語で最適化された実装を提供することで達成されました。アセンブリ言語を使用することで、プロセッサのレジスタや命令セットを直接操作し、コンパイラが生成するコードよりも効率的な処理を実現できる場合があります。
前提知識の解説
1. 浮動小数点数の表現 (IEEE 754)
math.Abs 関数は float64 型の絶対値を計算します。float64 は、IEEE 754標準で定義される倍精度浮動小数点数です。この標準では、浮動小数点数は以下の3つの要素で表現されます。
- 符号部 (Sign Bit): 1ビット。数値が正か負かを示します。0は正、1は負です。
- 指数部 (Exponent): 11ビット。数値のスケール(桁)を決定します。
- 仮数部 (Mantissa/Significand): 52ビット。数値の精度(有効数字)を決定します。
float64 は合計64ビットで構成され、最上位ビット(ビット63)が符号ビットです。絶対値を計算するということは、数値の「大きさ」は変えずに「符号」だけを正にする操作です。IEEE 754の特性として、符号ビットを0に設定するだけで、その浮動小数点数の絶対値が得られます。これは、正の数と負の数の表現が符号ビット以外は同じパターンを持つためです(例: +5.0 と -5.0 は符号ビットのみが異なる)。
2. ARMアセンブリ言語
ARMアセンブリ言語は、ARMアーキテクチャのプロセッサが直接実行できる機械語命令を人間が読める形式で記述したものです。Go言語のランタイムは、パフォーマンスが重要な部分や、特定のハードウェア機能にアクセスする必要がある場合に、C言語やGo言語のコードと並行してアセンブリ言語を使用することがあります。
- レジスタ: ARMプロセッサには、データを一時的に保持するための汎用レジスタ(R0-R12など)や特殊レジスタ(SP: スタックポインタ、LR: リンクレジスタ、PC: プログラムカウンタなど)があります。
- 命令:
TEXT: 関数の開始を宣言します。SB: 疑似レジスタで、静的ベース(Static Base)を指します。グローバルシンボルや関数のアドレス計算に使われます。FP: 疑似レジスタで、フレームポインタ(Frame Pointer)を指します。関数の引数やローカル変数のアドレス計算に使われます。MOVW: 32ビットワードを移動する命令です。AND: ビット単位の論理積(AND)演算を行う命令です。RET: 関数から戻る命令です。
3. Go言語のアセンブリ規約
Go言語は独自のアセンブリ規約を持っています。Goのアセンブリコードでは、関数名に (SB) を付けてグローバルシンボルであることを示したり、引数や戻り値に (FP) を付けてフレームポインタからのオフセットでアクセスしたりします。
lo+0(FP):float64の下位32ビット(loは引数の名前、0はオフセット)。hi+4(FP):float64の上位32ビット(hiは引数の名前、4はオフセット)。resultlo+8(FP): 戻り値の下位32ビット。resulthi+12(FP): 戻り値の上位32ビット。
float64 は64ビット(8バイト)のデータですが、32ビットアーキテクチャのARMでは、これを2つの32ビットワード(上位と下位)として扱います。
技術的詳細
このコミットは、math.Abs 関数がGoの汎用的な実装(通常はGo言語で記述されるか、C言語のランタイム関数を呼び出す)ではなく、ARMアーキテクチャに特化したアセンブリコードで実装されるように変更します。
浮動小数点数の絶対値を計算する最も効率的な方法は、そのバイナリ表現の符号ビットをクリアすることです。float64 の場合、これは64ビットの表現のうち、最上位ビット(ビット63)を0に設定することを意味します。
ARMアセンブリでは、64ビットの float64 値を直接操作するのではなく、それを2つの32ビットワードとして扱います。
- 入力値の読み込み:
Abs関数に渡されたfloat64の引数は、スタックフレーム上のlo+0(FP)とhi+4(FP)に格納されています。これらをそれぞれR0とR1レジスタに読み込みます。hiは上位32ビット、loは下位32ビットです。float64の符号ビットは上位32ビットの最上位ビット(R1のビット31)に位置します。 - 符号ビットのクリア:
AND $((1<<31)-1), R1命令は、R1レジスタ(上位32ビット)に対してビット単位のAND演算を行います。((1<<31)-1)は、32ビット値で最上位ビット(ビット31)が0で、それ以外のビットがすべて1であるマスク0x7FFFFFFFを生成します。このAND演算により、R1のビット31(元のfloat64の符号ビット)が強制的に0に設定され、他のビットは変更されません。 - 結果の書き込み: 変更されたR1(上位32ビット、符号ビットがクリアされた状態)と、変更されていないR0(下位32ビット)を、それぞれ戻り値の格納場所である
resulthi+12(FP)とresultlo+8(FP)に書き込みます。 - 関数からの戻り:
RET命令により、関数呼び出し元に戻ります。
このアセンブリ実装は、浮動小数点演算ユニット(FPU)の命令を使用する代わりに、整数レジスタとビット演算命令のみを使用しています。これは、FPUのパイプラインやレジスタのロード/ストアのオーバーヘッドを回避し、非常に高速な絶対値計算を実現できるため、特に古いARMプロセッサやFPUの性能が限られている環境で有効です。
コアとなるコードの変更箇所
diff --git a/src/pkg/math/abs_arm.s b/src/pkg/math/abs_arm.s
index 23f6a2a2de..d7a406bec1 100644
--- a/src/pkg/math/abs_arm.s
+++ b/src/pkg/math/abs_arm.s
@@ -3,4 +3,9 @@
// license that can be found in the LICENSE file.\n \n TEXT ·Abs(SB),7,$0
-\tB ·abs(SB)
+\tMOVW\tlo+0(FP), R0
+\tMOVW\thi+4(FP), R1
+\tAND \t$((1<<31)-1), R1
+\tMOVW\tR0, resultlo+8(FP)
+\tMOVW\tR1, resulthi+12(FP)
+\tRET
コアとなるコードの解説
変更前のコードは B ·abs(SB) となっており、これはおそらくGoランタイム内の汎用的な abs 関数(C言語で実装されている可能性が高い)にジャンプするだけでした。つまり、ARM固有の最適化は行われていませんでした。
変更後のコードは、ARMアセンブリで直接 Abs 関数を実装しています。
-
TEXT ·Abs(SB),7,$0:TEXT: アセンブリ関数の開始を宣言します。·Abs(SB):mathパッケージのAbs関数を定義します。·はGoのアセンブリにおけるパッケージ区切り文字です。SBは静的ベースレジスタで、グローバルシンボルであることを示します。,7: Goのアセンブリ規約における引数のサイズ(バイト単位)。float64は8バイトですが、ここではおそらくレジスタ渡しやスタックフレームのオフセットに関する内部的な値を示しています。$0: ローカル変数のスタックフレームサイズ(0バイト)。この関数はローカル変数を使用しません。
-
MOVW lo+0(FP), R0:MOVW: 32ビットワードを移動する命令。lo+0(FP): 関数Absの最初の引数(float64型)の下位32ビットを指します。FPはフレームポインタで、引数はスタックフレーム上に配置されます。loは引数の名前、0はオフセットです。R0: ARMプロセッサの汎用レジスタR0に、float64の下位32ビットをロードします。
-
MOVW hi+4(FP), R1:hi+4(FP): 関数Absの最初の引数(float64型)の上位32ビットを指します。4はloからのオフセットです(loが4バイトを占めるため)。R1: ARMプロセッサの汎用レジスタR1に、float64の上位32ビットをロードします。float64の符号ビットは、このR1レジスタの最上位ビット(ビット31)に位置します。
-
AND $((1<<31)-1), R1:AND: ビット単位の論理積(AND)演算を行います。$((1<<31)-1): 即値オペランド。これは0x7FFFFFFFという32ビットのマスク値を表します。このマスクは、最上位ビット(ビット31)が0で、残りの31ビットがすべて1です。R1: R1レジスタの内容とマスク値とのAND演算を行います。これにより、R1のビット31(元のfloat64の符号ビット)が強制的に0に設定されます。他のビットはマスクの対応するビットが1であるため、変更されません。
-
MOVW R0, resultlo+8(FP):resultlo+8(FP): 戻り値(float64型)の下位32ビットを格納するスタックフレーム上の位置を指します。8は引数領域の終わりからのオフセットです。R0: 変更されていないR0レジスタ(元のfloat64の下位32ビット)を、戻り値の下位32ビットとして書き込みます。
-
MOVW R1, resulthi+12(FP):resulthi+12(FP): 戻り値(float64型)の上位32ビットを格納するスタックフレーム上の位置を指します。12はresultloからのオフセットです。R1: 符号ビットがクリアされたR1レジスタ(元のfloat64の上位32ビット)を、戻り値の上位32ビットとして書き込みます。
-
RET:RET: 関数から戻ります。これにより、呼び出し元に制御が戻され、計算された絶対値が返されます。
この一連の命令は、float64 のバイナリ表現の符号ビットを効率的にクリアすることで、絶対値を計算しています。
関連リンク
- Go CL (Code Review) へのリンク: https://golang.org/cl/6094047
参考にした情報源リンク
- GitHub上のコミットページ: https://github.com/golang/go/commit/e7e7b1c55ce7d73812caff018d02545f8f5740aa
- Go
math.AbsARM assemblyに関するWeb検索結果 (IEEE 754浮動小数点数表現と符号ビットのクリアに関する一般的な情報): https://www.google.com/search?q=Go+math.Abs+ARM+assembly - IEEE 754 浮動小数点数標準に関する一般的な情報源 (例: Wikipediaなど)
- ARMアセンブリ言語の基本に関する情報源 (例: ARM開発者向けドキュメントなど)