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
コメント一覧
この記事に関しては4.2.1を使いました。4.4.0でも変わらないと思います。
http://sites.google.com/a/oidon.net/www/linux/arm-eabi-oabi
コンパイルしたコードを人間が見るときには -O オプションをつけると余計なメモリの読み書きがすっきりして見やすくなります。
ご回答ありがとうございます。
> gccはAndroidのソースツリーの prebuild にあるものを使っています。
言われてみれば、そうですよね。Androidの記事なんですから。。。
ちょうど今、浮動小数点演算の取扱いについて調べていたので、気になりました。
> コンパイルしたコードを人間が見るときには -O オプションをつけると余計なメモリの読み書きがすっきりして見やすくなります。
なるほど。アドバイスありがとうございます。