Upload
kmt-t
View
9.982
Download
8
Embed Size (px)
DESCRIPTION
改定しました http://www.slideshare.net/kmt-t/dalvik-10316622
Citation preview
Dalvik仮想マシンの
アーキテクチャ
発表者 : 僻地の暗黒プログラマ kmt-t
自己紹介
Web上での活動
ハンドルネーム : kmt-t
はてな ID : kmt-t2
Twitter ID : kmt_t
属性
僻地といいながら去年の10月から大阪在住です
組み込みプログラマらしい?
画像処理、ファイルシステムなどのミドルウェア中心
使用する言語はC++(not C)/C#/たまにPython
関数型言語はOcaml/F#派。あまり触れてませんが
今後の話の前振り
Dalvik仮想マシン三部作
Dalvik仮想マシンのアーキテクチャ
Dalvikバイトコードのリファレンスの読み方
DEXファイルフォーマット
今回は第一部のみを解説
機会があれば第二部、第三部もやりたい
三部作のゴール
ソースコードを誰でも読めるようにしたい
本日の前振り
仮想マシンの基本的なアーキテクチャ スタックマシンとは?レジスタマシンとは?
スレッドインタープリタとは?
Java仮想マシンの詳細についてのリファレンス 「Java仮想マシン仕様 第二版」
ISBN : 489471356X
ガベージコレクションは以下の本が詳しい 「ガベージコレクションのアルゴリズムと実装」
ISBN : 4798025623
Dalvik仮想マシンの「今の」実装は大幅に変わっている
アーキテクチャ比較
Java仮想マシン vs Dalvik仮想マシン
Java仮想マシン
スタックマシン
Dalvik仮想マシン
レジスタマシン
両者の違いとトレードオフの関係は?
当然両者一長一短
今回の解説ではこの点をクリアにしたい
Java仮想マシン
スタックマシン
計算に使用する値をスタックに積んでいく
スタックから値をポップして、その値で演算
演算結果をスタックに積む
値1
値2
値3
メモリ
値1
値2
値3
演算
Dalvik仮想マシン
レジスタマシン (1)
レジスタとよばれる領域を使って計算する
R0
R1
R2
R3
R4
R5
R6
R7
メモリ
演算
Dalvik仮想マシン
レジスタマシン (2)
Dalvik仮想マシンは特殊なレジスタマシン
レジスタはメソッドごとに用意される
レジスタはスタック領域に積まれる形で用意される
レジスタ数が可変 (0~65535個)
C言語のローカル変数と扱いは近い
Dalvik仮想マシン
レジスタマシン (3)
メソッドAからメソッドBを呼んだ場合のスタック
成長方向
(VM-specific internal goop)
メソッドB R0 (ローカル変数)
メソッドB R1 (ローカル変数)
メソッドB R2 (引数1)
メソッドA R0 (ローカル変数)
メソッドA R1 (ローカル変数)
メソッドA R2(引数1)
…
メソッドA R3(引数2)
メソッドBレジスタ
メソッドAレジスタ
一般的に考えられる
バイトコードのフォーマット
バイトコード命令には最低必要な情報 命令の種類をあらわす「オペコード」
命令の対象となるデータを指す「オペランド」
これは仮想マシンだけでなく本物のCPUも同様
命令のバイナリサイズを命令長と呼ぶ 命令長は短いほうが実行ファイルサイズ的に有利
命令長は可変長、固定長どちらもありうる
Java/Dalvik仮想マシンは可変長
本物のCPUのARM/MIPSなどは固定長
バイトコード命令の
バイナリデータの例
バイトコード命令の例 (架空の例)
DEST = SRC1 + SRC2 演算命令
オペコード8bit
(ADD)
オペランド8bit
(DEST
レジスタ番号)
オペランド8bit
(SRC1
レジスタ番号)
オペランド8bit
(SRC2
レジスタ番号)
命令長32bit
Java仮想マシンの
バイトコードのフォーマット
オペコードは8bit
オペランドは省略できることが多い
スタックマシンなので命令の対象データがスタックトップに決まっている命令が多いため
そのため、Java仮想マシンのバイトコードはオペコードのみの8bit命令が多い
Dalvik仮想マシンの
バイトコードのフォーマット
オペコードは8bit
レジスタを指すオペランドは最大16bit レジスタは最大65535個あるため
Java仮想マシンのような省略は不可
3オペランドの命令の場合、普通に考えると、オペコード8bit、オペランド16bit*3=48bitで命令長が56bitになる
命令長は必ず16bitでアライメント 命令長的にはJava仮想マシンに比べて不利
Dalvik仮想マシンの
バイトコードの工夫
レジスタが多いため、オペランドが巨大化
この問題を解決するため、「すべての命令ですべてのレジスタを参照」しない
多くの命令はレジスタ番号0~15または0~256
までのレジスタしか参照できない
オペランドサイズを4bitまたは8bitに圧縮
これでもJava仮想マシンより命令長は長い
命令数が多いことによる問題点
レジスタマシンにしたのは何故か?
スタックマシンは命令長が短い代わりにスタック操作のための命令の挿入が必要
つまり命令数はJava仮想マシンの方が多い
命令数が多くなるのを嫌ったのでは?
インタープリタのパフォーマンス上非常に不利
インタープリタは命令そのものの処理より、命令毎に発生するオーバーヘッドのほうが非常に大きい
JITコンパイラを使えばこのオーバーヘッドは回避できる。JITコンパイラで高速化できる理由は主にこの点が大きい
一般的なインタープリタ実装
擬似コードで書くと以下のような感じ
普通の感覚では特に問題ないように思える
実は非常にオーバヘッドが大きなコード
どの辺りがオーバヘッドが大きいのか?
# INSTR = 現在実行中のバイトコード命令 # TBL = オペコードに対応した処理の関数テーブル LOOP : FUNC = TBL[INSTR->OPCODE] CALL FUNC(INSTR) # 関数呼び出し NEXT INSTR # 次の命令に移動する GOTO LOOP # 繰り返し
一般的なインタープリタの問題点
ジャンプ命令が必要な箇所が多い
命令ごとにループを回す箇所のジャンプ
命令実行ルーチンに飛ぶためのジャンプ
命令実行ルーチンから戻るためのジャンプ
ジャンプは最悪10CPUサイクル以上かかる重い処理
メモリアクセス
テーブル参照のメモリアクセス
命令処理に対し数十倍のオーバーヘッド
命令処理内容が単なる加算だとすると、それ自体は1CPUサイクル(ARMの場合)程度で完了
Dalvik仮想マシンのインタープリタ
スレッドインタープリタ方式 # 最初の命令処理ルーチンのアドレスをBASE_ADDRとする # 各命令処理ルーチンの間隔は64バイトでアライメント .ALIGN 64 OP_A : … # ここで命令実行 NEXT INSTR # 次の命令に移動する GOTO BASE_ADDR + 64 * INSTR->OPCODE .ALIGN 64 OP_B : … # ここで命令実行 NEXT INSTR # 次の命令に移動する GOTO BASE_ADDR + 64 * INSTR->OPCODE
… # 以下繰り返し
Dalvik仮想マシン
インタープリタの改善点
ジャンプ命令が必要な箇所が少ない
命令処理の最後に一回ジャンプするだけ
メモリアクセス
テーブル参照が存在しない
命令処理に対し数倍程度のオーバーヘッド
唯一のジャンプがCPUが分岐予測できない
最新のARMプロセッサだとパイプラインストールで十数CPUサイクルのロス
まとめ
Dalvik仮想マシンではインタープリタを改善すべく工夫されている それでも命令毎のオーバーヘッドは大きい
依然としてバイトコードの命令数は少ないほうが良い
以上をまとめると バイナリサイズはJava仮想マシンの方がだいぶん小さい
実行速度はDalvik仮想マシンの方が高速にできる「かもしれない」
今後の予定
Dalvikバイトコードのリファレンスの読み方
今回解説した内容をベースにDalvikバイトコードのリファレンスの読み方を次回解説したい
ここまでいけば自力でインタープリタのソースは読めるようになる
みんなでDalvik仮想マシンを語れるように
以上で発表は終わりです
ご静聴ありがとうございました!