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。

hello-neon

地味すぎてゴメンナサイ。でもこれで少なくともひとつのNEONのSIMD命令がgoldfish上で動いていることは確認できました。

今回はarm_neon.hに定義されているintrinsicsを使ってNEONのSIMD命令を(かなり無理やり)使いましたが、これだとNEONに依存したプログラムになってしまいます。次回以降でgccのauto vectorizeの機能を使ってSIMD命令をコンパイラに生成させる方法についてもとりあげたいと思います。

(2009.9.10 追記)

gccでNEONのSIMD命令を生成させる方法



トラックバックURL

コメントする

名前
URL
 
  絵文字
 
 
記事検索
最新コメント
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

QRコード
QRコード