2009年08月31日

AndroidのDalvikVMのインタープリタをFPU命令で少し高速化する(その1)

AndroidのDalvik VMではJavaのバイトコードから変換されたDXコードというものをインタープリタで実行しています。DXコードの中には浮動小数点演算を行うための命令もあるのですが、現状のDalvik VMではFPU命令を使わずにすべてソフトウェアによる浮動小数点演算のライブラリを呼び出しています。

Android SDKのシミュレータでは実はkernelとqemuはVFPが有効になっているのでFPU命令を使うことができます。そこでインタープリタのコードの浮動小数点の四則演算の部分をFPU命令を使うように書き換えて少し高速化してみました。



2009年11月のEclairのソースリリースでだいぶ状況が変わっています。こちらも参照してください。

DalvikVMのインタープリタのコード

インタープリタはDXコードをひとつ取り出しては、それに対応する処理を実行するということを繰り返すループになっています。

Dalvik VMではインタープリタを何種類か持っています。

  • 移植のためのC言語で書かれたもの
  • デバッグ機能つきのもの
  • 高速化のためにアセンブラ言語で記述されたもの

通常実行する時には3つめのものが使用されます。

インタープリタの仕組みを勉強するにはC言語で書かれたものを読むとよいでしょう。

アセンブラ版インタープリタのコードの断片

以下のコードの断片はfloatの乗算を行う部分です。

/* ------------------------------ */
    .balign 64
.L_OP_MUL_FLOAT: /* 0xa8 */
/* File: armv5te/OP_MUL_FLOAT.S */
/* File: armv5te/binop.S */
    /*
     * Generic 32-bit binary operation.  Provide an "instr" line that
     * specifies an instruction that performs "result = r0 op r1".
     * This could be an ARM instruction or a function call.  (If the result
     * comes back in a register other than r0, you can override "result".)
     *
     * If "chkzero" is set to 1, we perform a divide-by-zero check on
     * vCC (r1).  Useful for integer division and modulus.  Note that we
     * *don't* check for (INT_MIN / -1) here, because the ARM math lib
     * handles it correctly.
     *
     * For: add-int, sub-int, mul-int, div-int, rem-int, and-int, or-int,
     *      xor-int, shl-int, shr-int, ushr-int, add-float, sub-float,
     *      mul-float, div-float, rem-float
     */
    /* binop vAA, vBB, vCC */
    FETCH(r0, 1)                        @ r0<- CCBB
    mov     r9, rINST, lsr #8           @ r9<- AA
    mov     r3, r0, lsr #8              @ r3<- CC
    and     r2, r0, #255                @ r2<- BB
    GET_VREG(r1, r3)                    @ r1<- vCC
    GET_VREG(r0, r2)                    @ r0<- vBB
    .if 0
    cmp     r1, #0                      @ is second operand zero?
    beq     common_errDivideByZero
    .endif

    FETCH_ADVANCE_INST(2)               @ advance rPC, load rINST
                               @ optional op; may set condition codes
    bl      __aeabi_fmul                              @ r0<- op, r0-r3 changed
    GET_INST_OPCODE(ip)                 @ extract opcode from rINST
    SET_VREG(r0, r9)               @ vAA<- r0
    GOTO_OPCODE(ip)                     @ jump to next instruction
    /* 11-14 instructions */

詳しい説明は省略しますが、注目すべきところは実際のfloatの乗算を行うところで __aeabi_fmul というランタイムライブラリを呼び出しているところです。この部分をFPU命令に置き換えれば高速化できるはずです。

gccを使ってFPU命令の使い方を調べる

ARMのFPU命令のアセンブラの書き方をマニュアルで調べるのは面倒です。そこで以下のようなCのソースをgccでFPUを使う場合と使わない場合の二通りのコンパイルオプションでコンパイルしてみて、生成されたアセンブラソースを比べてみます。

$ cat floatop.c
float fadd(float x, float y) { return x + y; }
float fsub(float x, float y) { return x - y; }
float fmul(float x, float y) { return x * y; }
float fdiv(float x, float y) { return x / y; }

まずはfpuを使わない場合

$ arm-eabi-gcc -S -O -march=armv5te -msoft-float floatop.c
$ less floatop.s
fmul:
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	stmfd	sp!, {r4, lr}
	bl	__aeabi_fmul
	ldmfd	sp!, {r4, pc}

次にfpuを使う場合

$ arm-eabi-gcc -S -O -march=armv5te -mhard-float -mfloat-abi=softfp -mfpu=vfp floatop.c
$ less floatop.s
fmul:
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
	fmsr	s14, r0
	fmsr	s15, r1
	fmuls	s13, s14, s15
	fmrs	r0, s13
	bx	lr

この2つを比べれば

	bl	__aeabi_fmul

の部分を

	fmsr	s14, r0
	fmsr	s15, r1
	fmuls	s13, s14, s15
	fmrs	r0, s13

に置き換えればよいことがわかります。簡単ですね。

本来ならばこのアセンブラ版インタープリタは別のソースからスクリプトで生成されたものなので、そのソースの方を修正するのが筋ですが、今回は無理やり力技で修正してみました。

そして、HelloWorldのサンプルプログラムを改造して即席のfloat演算の速度測定のプログラムを作って動かしてみました。

私のPCのエミュレータでは126msec前後のものが、この修正で75msec前後に短縮されました。エミュレータで測定してもあんまり意味がなさそうですが、とりあえず正しく動作していそうだということと高速化の効果があったということがわかりました。

AndroidをFPU付きの実機に移植した人はぜひ試してみてください。

(GDフォンは残念ながらFPUがついていないそうです。)

次回予告

今回は32bitのfloatの四則演算だけFPU命令を使うように変更しました。次回は64bitのdoubleの四則演算の修正を紹介します。これは予想してたよりもずっと大変でした...

(2009.9.2 追記)

続きを書きました。

インタープリタのソース変更点

diff --git a/vm/mterp/out/InterpAsm-armv5te.S b/vm/mterp/out/InterpAsm-armv5te.S
index 9987ff5..fa2ad9a 100644
--- a/vm/mterp/out/InterpAsm-armv5te.S
+++ b/vm/mterp/out/InterpAsm-armv5te.S
@@ -4952,7 +4952,12 @@ d2i_doconv:
 
     FETCH_ADVANCE_INST(2)               @ advance rPC, load rINST
                                @ optional op; may set condition codes
-    bl      __aeabi_fadd                              @ r0<- op, r0-r3 changed
+@    bl      __aeabi_fadd                              @ r0<- op, r0-r3 changed
+    fmsr	s14, r0
+    fmsr	s15, r1
+    fadds	s14, s14, s15
+    fmrs	r0, s14
+
     GET_INST_OPCODE(ip)                 @ extract opcode from rINST
     SET_VREG(r0, r9)               @ vAA<- r0
     GOTO_OPCODE(ip)                     @ jump to next instruction
@@ -4994,7 +4999,12 @@ d2i_doconv:
 
     FETCH_ADVANCE_INST(2)               @ advance rPC, load rINST
                                @ optional op; may set condition codes
-    bl      __aeabi_fsub                              @ r0<- op, r0-r3 changed
+@    bl      __aeabi_fsub                              @ r0<- op, r0-r3 changed
+    fmsr	s14, r0
+    fmsr	s15, r1
+    fsubs	s14, s14, s15
+    fmrs	r0, s14
+
     GET_INST_OPCODE(ip)                 @ extract opcode from rINST
     SET_VREG(r0, r9)               @ vAA<- r0
     GOTO_OPCODE(ip)                     @ jump to next instruction
@@ -5036,7 +5046,12 @@ d2i_doconv:
 
     FETCH_ADVANCE_INST(2)               @ advance rPC, load rINST
                                @ optional op; may set condition codes
-    bl      __aeabi_fmul                              @ r0<- op, r0-r3 changed
+@    bl      __aeabi_fmul                              @ r0<- op, r0-r3 changed
+    fmsr	s14, r0
+    fmsr	s15, r1
+    fmuls	s14, s14, s15
+    fmrs	r0, s14
+
     GET_INST_OPCODE(ip)                 @ extract opcode from rINST
     SET_VREG(r0, r9)               @ vAA<- r0
     GOTO_OPCODE(ip)                     @ jump to next instruction
@@ -5078,7 +5093,12 @@ d2i_doconv:
 
     FETCH_ADVANCE_INST(2)               @ advance rPC, load rINST
                                @ optional op; may set condition codes
-    bl      __aeabi_fdiv                              @ r0<- op, r0-r3 changed
+@    bl      __aeabi_fdiv                              @ r0<- op, r0-r3 changed
+    fmsr	s14, r0
+    fmsr	s15, r1
+    fdivs	s14, s14, s15
+    fmrs	r0, s14
+
     GET_INST_OPCODE(ip)                 @ extract opcode from rINST
     SET_VREG(r0, r9)               @ vAA<- r0
     GOTO_OPCODE(ip)                     @ jump to next instruction
@@ -6233,7 +6253,12 @@ d2i_doconv:
     FETCH_ADVANCE_INST(1)               @ advance rPC, load rINST
 
                                @ optional op; may set condition codes
-    bl      __aeabi_fadd                              @ r0<- op, r0-r3 changed
+@    bl      __aeabi_fadd                              @ r0<- op, r0-r3 changed
+    fmsr	s14, r0
+    fmsr	s15, r1
+    fadds	s14, s14, s15
+    fmrs	r0, s14
+
     GET_INST_OPCODE(ip)                 @ extract opcode from rINST
     SET_VREG(r0, r9)               @ vAA<- r0
     GOTO_OPCODE(ip)                     @ jump to next instruction
@@ -6273,7 +6298,12 @@ d2i_doconv:
     FETCH_ADVANCE_INST(1)               @ advance rPC, load rINST
 
                                @ optional op; may set condition codes
-    bl      __aeabi_fsub                              @ r0<- op, r0-r3 changed
+@    bl      __aeabi_fsub                              @ r0<- op, r0-r3 changed
+    fmsr	s14, r0
+    fmsr	s15, r1
+    fsubs	s14, s14, s15
+    fmrs	r0, s14
+
     GET_INST_OPCODE(ip)                 @ extract opcode from rINST
     SET_VREG(r0, r9)               @ vAA<- r0
     GOTO_OPCODE(ip)                     @ jump to next instruction
@@ -6313,7 +6343,12 @@ d2i_doconv:
     FETCH_ADVANCE_INST(1)               @ advance rPC, load rINST
 
                                @ optional op; may set condition codes
-    bl      __aeabi_fmul                              @ r0<- op, r0-r3 changed
+@    bl      __aeabi_fmul                              @ r0<- op, r0-r3 changed
+    fmsr	s14, r0
+    fmsr	s15, r1
+    fmuls	s14, s14, s15
+    fmrs	r0, s14
+
     GET_INST_OPCODE(ip)                 @ extract opcode from rINST
     SET_VREG(r0, r9)               @ vAA<- r0
     GOTO_OPCODE(ip)                     @ jump to next instruction
@@ -6353,7 +6388,12 @@ d2i_doconv:
     FETCH_ADVANCE_INST(1)               @ advance rPC, load rINST
 
                                @ optional op; may set condition codes
-    bl      __aeabi_fdiv                              @ r0<- op, r0-r3 changed
+@    bl      __aeabi_fdiv                              @ r0<- op, r0-r3 changed
+    fmsr	s14, r0
+    fmsr	s15, r1
+    fdivs	s14, s14, s15
+    fmrs	r0, s14
+
     GET_INST_OPCODE(ip)                 @ extract opcode from rINST
     SET_VREG(r0, r9)               @ vAA<- r0
     GOTO_OPCODE(ip)                     @ jump to next instruction

速度測定に使用したプログラム

package com.example.helloandroid;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class FPUtestFloat extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView tv = new TextView(this);
        tv.setText("FPUtestFloat: \n"
        		+ testTime() + " : " + testResult() + "\n"
        		+ testTime() + " : " + testResult() + "\n"
        		+ testTime() + " : " + testResult() + "\n"
        		+ testTime() + " : " + testResult() + "\n"
        		+ testTime() + " : " + testResult() + "\n"
        		+ testTime() + " : " + testResult() + "\n"
        		+ testTime() + " : " + testResult() + "\n"
        		+ testTime() + " : " + testResult() + "\n"
        		+ testTime() + " : " + testResult() + "\n"
        		+ testTime() + " : " + testResult() + "\n");
        setContentView(tv);
    }
    
    float result;    
    
    String testResult() {
    	return Float.toString(result);
    }
    

    String testTime() {
    	long start, stop;
    	
    	start = System.currentTimeMillis();
    	result = test();
    	stop = System.currentTimeMillis();
    	return Long.toString(stop - start);
    }
    
    
    static final int COUNT = 100000;
    
    float test() {
    	float x, y, z, w;
    	x = y = z = w = 1.0f;
    	for (int i = 0; i < COUNT; i++) {
    		x += 0.0001f;
    		y -= 0.0001f;
    		z *= 1.0001f;
    		w /= 0.9999f;
    	}
    	return x + y + z + w;
    }
}


トラックバックURL

コメント一覧

1. Posted by kotak   2009年09月03日 09:44
gccは、バージョンいくつを使用していますか?
2. Posted by koba   2009年09月03日 12:47
gccはAndroidのソースツリーの prebuild にあるものを使っています。
この記事に関しては4.2.1を使いました。4.4.0でも変わらないと思います。
3. Posted by koba   2009年09月03日 13:03
totakさん、このページを見せていただきました。興味深いですね。
http://sites.google.com/a/oidon.net/www/linux/arm-eabi-oabi

コンパイルしたコードを人間が見るときには -O オプションをつけると余計なメモリの読み書きがすっきりして見やすくなります。
4. Posted by kotak   2009年09月06日 17:56
koba さん

ご回答ありがとうございます。

> gccはAndroidのソースツリーの prebuild にあるものを使っています。
言われてみれば、そうですよね。Androidの記事なんですから。。。
ちょうど今、浮動小数点演算の取扱いについて調べていたので、気になりました。

> コンパイルしたコードを人間が見るときには -O オプションをつけると余計なメモリの読み書きがすっきりして見やすくなります。
なるほど。アドバイスありがとうございます。

コメントする

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

QRコード
QRコード