Android 4.4 (KitKat) とともに NDK r9b がリリースされています。
RenderScript 対応などいくつか新しい機能がありますが、
その中に ARMv7A の hard-float 対応が含まれています。
ちょうど NDK を使った関数電卓アプリを作っているところだったので試してみました。
Android/iOS など ARM デバイスではこれまで float ABI として softfp が用いられていました。
関数呼び出しなどの引数は、浮動小数点数であっても必ず整数レジスタ r を経由しており、
FPU の有無にかかわらず共通化出来るようになっています。
その代わり VFP が搭載されているデバイスでの実行効率とコード効率がわずかに犠牲になっています。
これまでの iOS/Android スマートフォンで VFP が搭載されていないのは
MSM7225 など一部の ARM11 (ARMv6, Android では ARMv5TE) に限られていました。
ARMv7A では存在しておらず softfp を用いる必要はありませんでした。
Android NDK r9b で hard-float に対応したので VFP/NEON レジスタを直接用いた
関数呼び出しが可能となっています。
●コンパイル方法
NDK で hard-float を指定する手順は下記の通り。
Android.mk に追加します。
LOCAL_CFLAGS += -mhard-float LOCAL_LDFLAGS += -Wl,--no-warn-mismatch
ただし対応しているコンパイラは gcc のみで clang ではまだ使用できません。
このオプションを指定できるのは ARMv7A ( TARGET_ARCH_ABI = armeabi-v7a ) の場合だけです。
●外部ライブラリの宣言
システムや外部ライブラリは softfp でコンパイルされているので、
コンパイラに ABI の違いを正しく認識させる必要があります。
NDK r9b 付属のヘッダでは、各関数に下記の宣言が追加されています。
これは softfp 関数の呼び出しであることを意味しています。
__attribute__((pcs("aapcs")))
例えば NDK 付属の math.h ヘッダを見ると下記のように宣言されています。
// math.h より抜粋 double acos(double) __NDK_FPABI_MATH__; double asin(double) __NDK_FPABI_MATH__;
__NDK_FPABI_MATH__ や __NDK_FPABI__ は sys/cdefs.h で定義されています。
// sys/cdefs.h より一部抜粋
#define __NDK_FPABI__ __attribute__((pcs("aapcs")))
#define __NDK_FPABI_MATH__ __NDK_FPABI__
●コンパイル結果と libm の hard_float
実際に hard-float でコンパイルした結果は下記の通り。
ローカル関数呼び出しは直接 d0 レジスタが用いられていることがわかります。
// hard-float // t_value f_bittof( t_value val ) vcvt.u32.f64 s0, d0 vcvt.f64.f32 d0, s0 bx lr
↓これまでの softfp でコンパイルすると下記の通り。
64bit 倍精度浮動小数点数は r0/r1 と 2つの 32bit 整数レジスタを使って
受け渡しが行われています。
// softfp
// t_value f_bittof( t_value val )
vmov d6, r0, r1
vcvt.u32.f64 s15, d6
vcvt.f64.f32 d6, s15
vmov r0, r1, d6
bx lr
hard-float でコンパイルした場合も、外部のライブラリ呼び出しでは
下記のように r0/r1 レジスタへのへのコピーが発生します。
// hard-float (-lm)
vmov r0, r1, d0
bl 0
vmov d0, r0, r1
ただし libm は hard-float でコンパイルした static ライブラリが付属しているので、
直接 VFP/NEON レジスタによる呼び出しも出来ます。
LOCAL_CFLAGS += -mhard-float -D_NDK_MATH_NO_SOFTFP=1 LOCAL_LDFLAGS += -Wl,--no-warn-mismatch -lm_hard
-lm (libm) の代わりに -lm_hard (libm_hard) を指定します。
この場合ヘッダの attribute 宣言を外す必要があるので -D_NDK_MATH_NO_SOFTFP=1 も
必要です。
↓ libm 呼び出しでもレジスタ転送が無くなりました。
// hard-float (-lm_hard)
b 0
アプリケーションコードは小さくなりますが libm_hard は static なので
プログラムコード全体は増えます。
● libm 以外のライブラリ呼び出し
浮動小数点数を用いるライブラリは libm 以外にもあります。
例えば OpenGL ES 2.0 関数にも attribute 宣言が追加されています。
// GLES2/gl2.h より
GL_APICALL void GL_APIENTRY glClearColor (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
// GLES2/gl2platform.h
#define GL_APICALL KHRONOS_APICALL
// KHR/khrplatform.h より抜粋
# define KHRONOS_APICALL __attribute__((visibility("default"))) __NDK_FPABI__
実際に glClearColor() を呼び出してみると、hard-float でも r0-r3 へのコピーが
行われていることがわかります。
// hard-float (debug build)
vstr s0, [fp, #-8]
vstr s1, [fp, #-12]
vstr s2, [fp, #-16]
vstr s3, [fp, #-20] ; 0xffffffec
ldr r0, [fp, #-8]
ldr r1, [fp, #-12]
ldr r2, [fp, #-16]
ldr r3, [fp, #-20]
bl 0
libm 以外では特に hard-float 版が用意されているわけではないので
softfp 同様の転送が行われます。
実際のアプリケーションでどの程度の差が出るかわかりませんが、
NDK でさらに最適化出来る余地ができました。
もっとも、iOS の方はすでに ARMv8 に移行しつつあります。
Android も 64bit 化が行われればこのような違いを意識する必要は無くなるでしょう。
関連エントリ
・Nexus 7 の Ubuntu で ARM の abi softfp と hard-float を比べる