Download - Dalvik仮想マシンのアーキテクチャ 改訂版
Dalvik仮想マシンの アーキテクチャ
僻地からの出稼ぎプログラマ
kmt-t
自己紹介
・ハンドルネーム : kmt-t ・はてなダイアリ ID : kmt-t2 ・Twitter ID : kmt_t
Web上での活動
属性
・鳥取県から大阪に出稼ぎ中です ・組み込みプログラマらしい ・ミドルウェアが得意です →画像処理(2D/3D)、ファイルシステム、仮想マシンが専門です ・使用言語はC++(not C)/C#/Python →C++11とかC#の最新の仕様がキャッチアップできていません…
属性
発表の構成
1. Dalvik仮想マシンのソースコードが誰でも読めるようにする 2. Dalvik仮想マシンに対するみんなのリテラシを上げる 3. より深い部分の発表をするための下地をつくる
発表の目的
Dalvik仮想マシンの発表を以下の3回にわけて行います 1. Dalvik仮想マシンのアーキテクチャ ←今回はここの発表 2. Dalvikバイトコードのリファレンスの読み方 3. DEXファイルフォーマット
Dalvik仮想マシン3部作
本日の発表の概要
Java仮想マシン仕様 (ISBN:489471356X)
参考文献
Java仮想マシンはスタックマシン、 Dalvik仮想マシンはレジスタマシン ・スタックマシンとは何か? ・レジスタマシンとは何か? ・両者の比較とトレードオフをはっきりさせる
Java仮想マシンとの比較
Dalvik仮想マシンで採用されているスレッドインタープリタとは何か?
インタープリタの設計
Java仮想マシン スタックマシン
スタックマシンの基本動作
メモリ
値3
値2
値1
Push Pop メモリ
値3
メモリ
値2
値3
メモリ
値1
値2
値3
メモリ
値1
値2
値3
メモリ
値1
値2
値3
メモリ
値1
値2
値3
メモリ 演算
値3
値2
値1 値1
値2
値3
メモリ
オペランドスタック オペランドスタック
スタックマシンは演算を行うための「オペランドスタック」を持つ 1. 演算に使う値をオペランドスタックに積む 2. スタックから値をポップしてその値で演算をする 3. 演算結果をスタックに積む
スタックマシンの基本動作
値2
値3
値1
値2
値3
Dalvik仮想マシン レジスタマシン
レジスタマシンは演算を行うための「レジスタ」と呼ばれる領域を持つ 1. 演算に使う値をメモリからレジスタにコピーする 2. レジスタ上の値を演算し、レジスタに結果を保存する 3. 演算結果をメモリへコピーする
レジスタマシンの基本動作
メモリ
レジスタ1
レジスタ2
レジスタ3
レジスタ4
レジスタ
レジスタ5
レジスタ6
演算
Dalvik仮想マシン レジスタマシンの補足
・レジスタはメソッド呼び出し時にスタック領域に確保される →確保されるレジスタは0~65535個までの可変数 ・メソッド終了時にスタック領域に確保されたレジスタは開放される ・レジスタにはレジスタを識別する番号、「レジスタ番号」が割り振られる ・Dalvik仮想マシンのレジスタとは一般的にいわれるレジスタとは違い、 演算に使うメモリ領域をレジスタと呼んでいる
Dalvik仮想マシンは特殊なレジスタマシン
メソッドA R1
スタック
メソッドA R0
(VM-specific internal goop)
メソッドB R1
メソッドB R0
成長方向
メソッドAから メソッドB呼出
CPUの機械語や 仮想マシンバイトコードの基礎用語
機械語やバイトコードの命令には最低以下の情報が必要 ・命令の種類を示す「オペコード」 ・命令の演算対象となる値を示す「オペランド」 →オペランドにはレジスタ番号や即値が指定される
最小の構成要素
機械語やバイトコードの命令のバイナリサイズを「命令長」と呼ぶ →スタックマシンとレジスタマシンとの比較の大きなポイント ちなみに、命令長はCPU、仮想マシンごとに可変長のものと固定長の ものと、どちらも存在する ・固定長 – MIPS/ARMなど ・可変長 – x86/Java仮想マシン/Dalvik仮想マシンなど
命令長
仮想マシンバイトコード 命令のイメージ (架空の例)
レジスタAとレジスタBを加算してレジスタCに格納する命令の例
たとえば
オペコード ADD (8bit)
オペランド レジスタ番号C
(8bit)
オペランド レジスタ番号A
(8bit)
オペランド レジスタ番号B
(8bit)
命令長=32bit
Java仮想マシン スタックマシンの特徴
・可変長命令フォーマット ・オペランドは8bit ・演算対象はオペランドスタックのトップと決まっている命令が多い →この場合オペランドが省略可能 ・オペランドスタックを操作するための命令が必要なケースがある →そのため命令数がレジスタマシンにくらべて多くなるケースがある
特徴
・バイトコードのサイズは小さい (オペランドが省略できるため) ・同じ処理をした場合の命令数はレジスタマシンにくらべて多い
まとめると
Dalvik仮想マシン レジスタマシンの特徴
・可変長命令フォーマット ・オペランドは8bit ・レジスタがオペランドとなる場合、オペランドサイズは最大16bitになる →レジスタが最大65535個あるため ・命令は16bitでアライメントされている ・オペランドスタックを操作するための命令が不要である
特徴
・バイトコードのサイズは大きい (オペランドが大きいため) ・同じ処理をした場合の命令数はスタックマシンにくらべて少ない
まとめると
Dalvikバイトコードの命令長が 長い問題に対する対策
レジスタをオペランドとした場合、オペランドが巨大化
長い命令長
すべての命令の種類ですべてのレジスタを参照することをあきらめる →参照するレジスタ番号を制限することによりオペランドを小さくする 命令により以下のレンジで参照できるレジスタ番号を制限 ・レジスタ番号0~15 (オペランド4bit) ・レジスタ番号0~255 (オペランド8bit) ・レジスタ番号0~65535 (オペランド16bit)
その対策
なぜDalvik仮想マシンは レジスタマシンなのか?
・オペランドスタックを操作するための命令が必要なケースがある →そのため命令数がレジスタマシンにくらべて多くなるケースがある
スタックマシンの欠点
・命令数が多いことによる「インタープリタでの」パフォーマンス低下 →インタープリタの設計と実装を理解すると理由がわかる ・JITコンパイラでは起こりにくい問題 →初期のDalvik仮想マシンはインタープリタのみであったのと関連? →インタープリタのパフォーマンスを優先した可能性がある
パフォーマンスの問題
一般的なインタープリタの実装
素朴に書くと擬似コードのようになる。実はオーバーヘッドが大きい。 →どの辺りがオーバーヘッドが大きいのか?
擬似コードによる例
# INSTR = 現在実行中のバイトコード命令 # TBL = オペコードに対応した処理の関数テーブル LOOP : FUNC = TBL[INSTR->OPCODE] CALL FUNC(INSTR) # 関数呼び出し NEXT INSTR # 次の命令に移動する GOTO LOOP # 繰り返し
一般的なインタープリタの 問題点
・ジャンプが多い (ジャンプは最悪10CPUサイクル以上かかる処理) 1. 命令ごとにループを回す箇所のジャンプ 2.命令実行ルーチンに飛ぶためのジャンプ →このジャンプはCPUによる最適化処理(分岐予測)が効かない 3. 命令実行ルーチンから戻るためのジャンプ ・テーブル参照がある
重い処理が多い
バイトコード命令が単なる加算である場合は、それそのものの処理は 数CPUサイクルで完了するが、バイトコード命令ごとに発生する インタープリタのオーバーヘッドは数十サイクル以上ある
巨大なオーバーヘッド
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による最適化処理(分岐予測)が効かない ・テーブル参照がない →次の命令のジャンプ先を演算で算出できる
オーバーヘッドが低減
重い処理が少ない
ちなみにJITコンパイラを使うと
・命令実行のためにジャンプする必要がない ・テーブル参照はない
重い処理がない
・命令の「フェッチ(読み込み処理)」が不要 ・命令の「デコード(命令の意味解釈処理)」が不要
更に
・速いのは当たり前 ・実行するバイトコードの命令数の多い少ないはあまり関係ない
まとめると
まとめ
・実行するバイトコード命令ごとに発生するオーバーヘッドは大きい ・Dalvik仮想マシンではインタープリタを改良、改善している ・しかし依然として実行するバイトコード命令の数は少ないほうが良い
要約
・バイナリサイズはJava仮想マシンの方がだいぶん小さい ・実行速度はDalvik仮想マシンの方が高速にできる「かもしれない」 →インタープリタのパフォーマンスでは有利 →JITコンパイラを採用するとどちらが有利か微妙
まとめ
おわり
ご清聴ありがとうございました