日別アーカイブ: 2013年11月1日

Android NDK r9b と ARMv7A の hard-float

Android 4.4 (KitKat) とともに NDK r9b がリリースされています。
RenderScript 対応などいくつか新しい機能がありますが、
その中に ARMv7A の hard-float 対応が含まれています。
ちょうど NDK を使った関数電卓アプリを作っているところだったので試してみました。

Android 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 を比べる