2009年08月26日
AndroidのSDKのgoldfishのCPUをcortex-A8に置き換えてNEONのSIMD命令を試す(その2)
前回はgoldfishのカーネルをcortex-A8向けにリビルドしてemulatorで起動するところまでを紹介しました。
ここで一気にユーザーランドもcortex-A8用にリビルドしてみたいところですが、単純にコンパイルオプションを変えただけではうまくいきませんでした。アセンブラで書いてある所などは書き換えが必要になりそうです。
そこで、今回はまだユーザーランドはarmv5te用のままで使用し、NDKを使って一部の共有ライブラリだけをcortex-a8用にコンパイルし、そこでNEONのSIMD命令を試してみることにします。
2009年11月のEclairのソースリリースでだいぶ状況が変わっています。こちらも参照してください。
NEONのSIMD命令のテストプログラム
以下のような積和演算をSIMD命令で実際に実行できるかを試してみます。
void vmla(float* a, float* x, float* y, float* z) { a[0] = x[0] * y[0] + z[0]; a[1] = x[1] * y[1] + z[1]; a[2] = x[2] * y[2] + z[2]; a[3] = x[3] * y[3] + z[3]; }
これをNEONのintrinsicsを使って書くと以下のようになります。
gccに付属のarm_neon.hにNEON用の型や関数が定義されています。
#include <arm_neon.h> void neon_vmla(float* a, float* x, float* y, float* z) { float32x4_t va, vx, vy, vz; vx = *(float32x4_t*)x; vy = *(float32x4_t*)y; vz = *(float32x4_t*)z; va = vmlaq_f32(vz, vx, vy); *(float32x4_t*)a = va; }
これらの関数を組み込んだ共有ライブラリをこれから作るのですが、作成手順は後にして、先にこれらの関数がどうコンパイルされているのか、逆アセンブルした結果をお見せします。
$ arm-eabi-objdump -d apps/hello-jni/project/libs/armeabi/libhello-jni.so |less
000003e8 <vmla>: 3e8: edd26a00 flds s13, [r2] 3ec: ed917a00 flds s14, [r1] 3f0: edd37a00 flds s15, [r3] 3f4: ee467a87 fmacs s15, s13, s14 3f8: edc07a00 fsts s15, [r0] 3fc: edd26a01 flds s13, [r2, #4] 400: ed917a01 flds s14, [r1, #4] 404: edd37a01 flds s15, [r3, #4] 408: ee467a87 fmacs s15, s13, s14 40c: edc07a01 fsts s15, [r0, #4] 410: edd26a02 flds s13, [r2, #8] 414: ed917a02 flds s14, [r1, #8] 418: edd37a02 flds s15, [r3, #8] 41c: ee467a87 fmacs s15, s13, s14 420: edc07a02 fsts s15, [r0, #8] 424: edd26a03 flds s13, [r2, #12] 428: ed917a03 flds s14, [r1, #12] 42c: edd37a03 flds s15, [r3, #12] 430: ee467a87 fmacs s15, s13, s14 434: edc07a03 fsts s15, [r0, #12] 438: e12fff1e bx lr
これがNEONのSIMD命令を使うと以下のようになります。ベクタの積和演算が一命令になっています。
0000043c <neon_vmla>: 43c: ecd30b04 vldmia r3, {d16-d17} 440: ecd12b04 vldmia r1, {d18-d19} 444: ecd24b04 vldmia r2, {d20-d21} 448: f2420df4 vmla.f32 q8, q9, q10 44c: ecc00b04 vstmia r0, {d16-d17} 450: e12fff1e bx lr
NDKによる共有ライブラリの作成
NDKのサンプルプログラムのhello_jniに今作った関数を呼び出すような改造を加えます。
先に無変更のhello_jniが動作することを確認してから、以下のパッチをあててください。
コンパイルオプションの変更
NDKのmakefileの中でツールチェインとコンパイルオプションを指定している場所を以下のように変更します。
(もちろん、こんな変更をしているのでこれで作ったアプリケーションは現在市販されているandroid携帯では動きません。本来のNDKの用途から逸脱した使い方です。)
ツールチェインにはカーネルとビルドしたのと同じandroidのソースツリーにあるarm-eabi-4.4.0のものを指定します。
--- build/toolchains/arm-eabi-4.2.1/setup.mk.org 2009-08-21 17:17:05.000000000 +0900 +++ build/toolchains/arm-eabi-4.2.1/setup.mk 2009-08-13 20:12:25.000000000 +0900 @@ -24,12 +24,26 @@ # TOOLCHAIN_NAME := arm-eabi-4.2.1 -TOOLCHAIN_PREFIX := $(HOST_PREBUILT)/$(TOOLCHAIN_NAME)/bin/arm-eabi- +#TOOLCHAIN_PREFIX := $(HOST_PREBUILT)/$(TOOLCHAIN_NAME)/bin/arm-eabi- +TOOLCHAIN_PREFIX := /path/to/your/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi- TARGET_CFLAGS.common := \ -I$(SYSROOT)/usr/include \ - -march=armv5te -mtune=xscale \ - -msoft-float -fpic \ + -march=armv7-a -mtune=cortex-a8 \ + -mhard-float -mfpu=neon -mfloat-abi=softfp -ffast-math -fpic \ -mthumb-interwork \ -ffunction-sections \ -funwind-tables \
Cソースの変更
--- sources/samples/hello-jni/hello-jni.c.org 2009-08-21 17:28:32.000000000 +0900 +++ sources/samples/hello-jni/hello-jni.c 2009-08-13 20:24:24.000000000 +0900 @@ -29,3 +29,62 @@ { return (*env)->NewStringUTF(env, "Hello from JNI !"); } + + +void vmla(float* a, float* x, float* y, float* z) +{ + a[0] = x[0] * y[0] + z[0]; + a[1] = x[1] * y[1] + z[1]; + a[2] = x[2] * y[2] + z[2]; + a[3] = x[3] * y[3] + z[3]; +} + +#include <arm_neon.h> + +void neon_vmla(float* a, float* x, float* y, float* z) +{ + float32x4_t va, vx, vy, vz; + + vx = *(float32x4_t*)x; + vy = *(float32x4_t*)y; + vz = *(float32x4_t*)z; + va = vmlaq_f32(vz, vx, vy); + *(float32x4_t*)a = va; +} + +void Java_com_example_hellojni_HelloJni_simd_1test0 + (JNIEnv *env, jobject thiz, jfloatArray ja, jfloatArray jx, jfloatArray jy, jfloatArray jz) +{ + float *a; + float *x; + float *y; + float *z; + jboolean isCopy; + + if ((*env)->GetArrayLength(env, ja) != 4) return; + + + a = (*env)->GetFloatArrayElements(env, ja, &isCopy); + x = (*env)->GetFloatArrayElements(env, jx, &isCopy); + y = (*env)->GetFloatArrayElements(env, jy, &isCopy); + z = (*env)->GetFloatArrayElements(env, jz, &isCopy); + + //vmla(a, x, y, z); + neon_vmla(a, x, y, z); + + (*env)->ReleaseFloatArrayElements(env, ja, a, 0); + (*env)->ReleaseFloatArrayElements(env, jx, x, 0); + (*env)->ReleaseFloatArrayElements(env, jy, y, 0); + (*env)->ReleaseFloatArrayElements(env, jz, z, 0); +}
エラーチェックは手抜きです。とりあえず配列aのサイズが4であることだけチェックしています。
Javaのfloatの配列をCで扱うには、このようにJNIのGetFloatArrayElements を使用します。この関数を呼んだら、必ず対応するReleaseFloatArrayElements を呼んでください。このあたりは丁寧に作らないと後でdalvikVMがガベージコレクションした時にクラッシュするなどの原因のわかりにくいバグを混入することになるので気をつけてください。
javaソースの変更
--- apps/hello-jni/project/src/com/example/hellojni/HelloJni.java.org 2009-08-21 17:30:47.000000000 +0900 +++ apps/hello-jni/project/src/com/example/hellojni/HelloJni.java 2009-08-13 19:37:37.000000000 +0900 @@ -33,7 +33,8 @@ * function. */ TextView tv = new TextView(this); - tv.setText( stringFromJNI() ); + tv.setText( stringFromJNI() + " === "+ simd_test()); setContentView(tv); } @@ -60,6 +61,32 @@ * /data/data/com.example.HelloJni/lib/libhello-jni.so at * installation time by the package manager. */ + + private native void simd_test0(float a, float x, float y, float z); + + private void simd_test1(float a, float x, float y, float z) { + a[0] = x[0] * y[0] + z[0]; + a[1] = x[1] * y[1] + z[1]; + a[2] = x[2] * y[2] + z[2]; + a[3] = x[3] * y[3] + z[3]; + } + + String simd_test() { + float[] x = {1.0f, 2.0f, 3.0f, 4.0f}; + float[] y = {5.0f, 6.0f, 7.0f, 8.0f}; + float[] z = {9.0f, 10.0f, 11.0f, 12.0f}; + float[] a = {0, 0, 0, 0}; + + simd_test0(a, x, y, z); + return Float.toString(a[0]) + " " + Float.toString(a[1]) + " " + + Float.toString(a[2]) + " " + Float.toString(a[3]); + } + static { System.loadLibrary("hello-jni"); }
ビルド
先にlibhello-jni.soをビルドしてから、javaのビルドをしてapkファイルを作成します。
実行結果
実行結果はとても地味です。エミュレータの画面に"Hello from JNI! === 14.0 22.0 32.0 44.0" と表示されればOK。
地味すぎてゴメンナサイ。でもこれで少なくともひとつのNEONのSIMD命令がgoldfish上で動いていることは確認できました。
今回はarm_neon.hに定義されているintrinsicsを使ってNEONのSIMD命令を(かなり無理やり)使いましたが、これだとNEONに依存したプログラムになってしまいます。次回以降でgccのauto vectorizeの機能を使ってSIMD命令をコンパイラに生成させる方法についてもとりあげたいと思います。
(2009.9.10 追記)