史上最速のandroid

Post on 12-Apr-2017

1.169 Views

Category:

Technology

2 Downloads

Preview:

Click to see full reader

TRANSCRIPT

史上最速の AndroidDroidKaigi2016

僻地のプログラマ kmt-t

2

自己紹介 ハンドルネーム

@kmt_t 職業

業務系プログラマ 元組み込み系

専門分野 画像処理、ファイルシステム、仮想マシン 自然言語処理、ディープラーニングは最近下火

3

定例連絡 ワールドワイドでオンリーワンです ART も内容的に半分ぐらい同じです

「 Android の仮想マシン Dalvik 編」発売中!

ART 本執筆進捗 ART の変更が頻繁なため手が出せず 最近気力充実してるのでそろそろ本気出す ちなみに本執筆終わると精神崩壊レベルで燃え尽きます

「 Android の仮想マシン ART 編」は必ず出します!

今日話すこと 前回の DroidKaigi は難しかったようなので反省 今回はアプリケーションに近いところで話します 仮想マシン依存の最適化の話です

今日話すこと 前回の DroidKaigi は難しかったようなので反省 今回はアプリケーションに近いところで話します 仮想マシン依存の最適化の話です注意!ART のコードが頻繁に変わるので将来的に正しい保証はありません

今日のネタ一覧1. 誰も知らないループのオーバヘッド2. 遅い命令と速い命令3. JNI の秘密

本日の計測環境

• https://www.genymotion.com/• x86/Android エミュレータ• Android 6.0

GenyMotion

• Intel core i5 6400T (Skylake)• メモリ 32GB

PC 環境

解析ツール• DEX ファイル解析• バイトコードレベルの解析• 今回は使いません

dexdump

• OAT ファイル解析• コンパイルされたバイトコード、ネイティブコードの解析• 今回はこっちを使います

oatdump

ネタその 1誰も知らないループのオーバヘッドこんな簡単なことに誰も気が付かない

何の変哲もないループint N = 100000000;int ret = 0;for (int i = 0; i < N; ++i) { ret += i;}

ベンチマーク

何となくアンロールしてみる アンロールとは複数回のループをベタで展開することint ret = 0;for (int i = 0; i < N; i += 8) { ret += i; ret += i; ret += i; ret += i; ret += i; ret += i; ret += i; ret += i;}

ベンチマーク アンロール前 (518ms) アンロール後 (331ms)

あれ、結構効果ある 単純なアンロールが結構効きますね

バイトコードを見てみる 1: int net.remoteplace.myapplication.Test.test1() DEX CODE: 0x0000: 1400 00e1 f505 | const v0, #+100000000 0x0003: 1202 | const/4 v2, #+0 0x0004: 1201 | const/4 v1, #+0 0x0005: 3501 0600 | if-ge v1, v0, +6 0x0007: b012 | add-int/2addr v2, v1 0x0008: d801 0101 | add-int/lit8 v1, v1, #+1 0x000a: 28fb | goto -5 0x000b: 0f02 | return v2

ネイティブコードを見てみる ( 長いので割愛 ) ( ループの度に GC チェックを実行 )

こいつを生成してるところ ループの度に謎の処理が入る

art/compiler/dex/quick/mir_to_lir.cc

case Instruction::IF_EQ:case Instruction::IF_NE:case Instruction::IF_LT:case Instruction::IF_GE:case Instruction::IF_GT:case Instruction::IF_LE: { if (mir_graph_->IsBackEdge(bb, bb->taken) || mir_graph_->IsBackEdge(bb, bb->fall_through)) { GenSuspendTest(opt_flags); } LIR* taken = &label_list[bb->taken]; GenCompareAndBranch(opcode, rl_src[0], rl_src[1], taken); break; }

こいつの正体 後ろ向き ( 繰り返し ) のジャンプで生成される 正体は GC チェックの処理 GC チェックは GC ポイントで実行される GC ポイントでは GC マップというガイド情報が用意される GC マップはコンパイル時に生成されるため、効率が良い

この例の教訓 ART のループは GC チェックが実行される きわめて小さいループではアンロールが効く GC が呼ばれるのでループでオブジェクトの生成を避ける

ネタその 2遅い命令と速い命令バイトコードを読めると三文の得

遅い命令と速い命令 バイトコードには似た処理を行う遅い命令と速い命令がある バイトコード命令ごとの処理時間を見積もるには? バイトコード命令ごとの処理時間を合算すればよいインタープリタのソースを読むと実行速度のイメージが沸きます!

ART のアーキテクチャ復習 仮想マシンレジスタに値を入れて計算 ローカル変数は仮想マシンレジスタに保存される

MOVE と MOVE_LONG 仮想マシンレジスタの値をコピーするバイトコード命令 MOVE は 32ビット MOVE_LONG は 64ビット ART の仮想マシンレジスタは幅は 32ビット 64ビットリードは仮想マシンレジスタを 2 回リードする x86 でも 2 回リードする double型も同様 そのため浮動小数点の計算は float のほうが速い

IF_EQ と IF_EQZ とそのバリエーション IF_EQ はふたつの仮想マシンレジスタの値が一致すると分岐 IF_EQZ は仮想マシンレジスタの値と 0 が一致すると分岐 IF_EQ は x86 の cmp 命令に変換 IF_EQZ は x86 の test 命令に変換 IF_EQZ の方が使用する CPU レジスタが少ないので速くなる

いつかベンチマーク取って公開します 今回は全バイトコード命令のベンチマークを取る予定でした 資料作成の時間の関係で作れませんでした いつか実施して結果を公開します…

ネタその 3JNI の秘密

OSS に情報漏洩はない

JNI の秘密 Dalvik では組み込みメソッドがあり、一部のフレームワークのメソッドは仮想マシン内にネイティブで実装されていた 組み込みメソッドは JNI と違い呼び出しが速い ART では組み込み関数は廃止され、 JNI に置き換わった JNI が速くなったのもあるが、実は隠し機能がある 隠し機能を使うことで JNI 呼び出しが高速化される

高速 JNI関数一覧 (1/2)クラス名 メソッド名

java/lang/Class

classForName getDeclaredMethodInternalgetDeclaredConstructorInternal getDeclaredMethodsUncheckedgetDeclaredConstructorsInternal getNameNativegetDeclaredFieldInternal getProxyInterfacesgetDeclaredFields getPublicDeclaredFieldsgetDeclaredFieldsUnchecked newInstance

java/lang/Object internalClone notifyAllnotify wait

java/lang/reflect/Array createMultiArray createObjectArrayjava/lang/reflect/Constructor newInstance  

java/lang/reflect/Field

get setgetBoolean setBooleangetByte setBytegetChar setChargetDouble setDoublegetFloat setFloatgetInt setIntgetLong setLonggetShort setShort

java/lang/reflect/Method invoke getExceptionTypesNative

高速 JNI関数一覧 (2/2)クラス名 メソッド名

java/lang/StringFactory newStringFromBytes newStringFromStringnewStringFromChars  

java/lang/System

arraycopy arraycopyLongUncheckedarraycopyCharUnchecked arraycopyFloatUncheckedarraycopyByteUnchecked arraycopyDoubleUncheckedarraycopyShortUnchecked arraycopyBooleanUncheckedarraycopyIntUnchecked identityHashCode

java/lang/Throwable nativeFillInStackTrace nativeGetStackTrace

libcore/util/CharsetUtilsasciiBytesToChars toIsoLatin1BytesisoLatin1BytesToChars toUtf8BytestoAsciiBytes  

sun/misc/Unsafe

compareAndSwapInt putIntcompareAndSwapLong putOrderedIntcompareAndSwapObject getLonggetIntVolatile putLongputIntVolatile putOrderedLonggetLongVolatile getObjectputLongVolatile putObjectgetObjectVolatile putOrderedObjectputObjectVolatile getArrayBaseOffsetForComponentTy

pegetInt getArrayIndexScaleForComponentTy

pe

高速 JNI関数の使い方 実は普通のアプリケーションでも使用できる 型ディスクリプタの頭に「 ! 」付けるだけ

JNINativeMethod m = {“hoge", “!()Z", hoge};  env->RegisterNatives(clazz, &m, 1); 

高速 JNI の注意事項 高速 JNI は仮想マシンのステート切り替えのオーバーヘッドがない 高速 JNI 内部では Java オブジェクトにアクセスできない 高速 JNI で長い時間がかかる処理を行うと GC に悪影響がある この機能は使っていいか俺は知りません 当然 Dalvik では動きません

まとめ

最速を目指すための作法 for 文の中でオブジェクトを生成しない 数値計算の場合はループをアンロールする 遅い命令は避ける 高速 JNI を使う

最速を目指すための作法 for 文の中でオブジェクトを生成しない 数値計算の場合はループをアンロールする ローカル変数のオブジェクトにアクセスしない フィールド変数はローカル変数にコピーする 遅い命令は避ける 高速 JNI を使う

注意!最速を目指す必要などない!

Android Guru を目指して バイトコードと逆アセンブラは読みましょう インタープリタのコードは読みましょう ベンチマークは重要です

ご清聴ありとうございました

質問はありますか!?

top related