Dalvik仮想マシンのアーキテクチャ 改訂版

20
Dalvik仮想マシンの アーキテクチャ 僻地からの出稼ぎプログラマ kmt-t

Transcript of Dalvik仮想マシンのアーキテクチャ 改訂版

Page 1: Dalvik仮想マシンのアーキテクチャ 改訂版

Dalvik仮想マシンの アーキテクチャ

僻地からの出稼ぎプログラマ

kmt-t

Page 2: Dalvik仮想マシンのアーキテクチャ 改訂版

自己紹介

・ハンドルネーム : kmt-t ・はてなダイアリ ID : kmt-t2 ・Twitter ID : kmt_t

Web上での活動

属性

・鳥取県から大阪に出稼ぎ中です ・組み込みプログラマらしい ・ミドルウェアが得意です →画像処理(2D/3D)、ファイルシステム、仮想マシンが専門です ・使用言語はC++(not C)/C#/Python →C++11とかC#の最新の仕様がキャッチアップできていません…

属性

Page 3: Dalvik仮想マシンのアーキテクチャ 改訂版

発表の構成

1. Dalvik仮想マシンのソースコードが誰でも読めるようにする 2. Dalvik仮想マシンに対するみんなのリテラシを上げる 3. より深い部分の発表をするための下地をつくる

発表の目的

Dalvik仮想マシンの発表を以下の3回にわけて行います 1. Dalvik仮想マシンのアーキテクチャ ←今回はここの発表 2. Dalvikバイトコードのリファレンスの読み方 3. DEXファイルフォーマット

Dalvik仮想マシン3部作

Page 4: Dalvik仮想マシンのアーキテクチャ 改訂版

本日の発表の概要

Java仮想マシン仕様 (ISBN:489471356X)

参考文献

Java仮想マシンはスタックマシン、 Dalvik仮想マシンはレジスタマシン ・スタックマシンとは何か? ・レジスタマシンとは何か? ・両者の比較とトレードオフをはっきりさせる

Java仮想マシンとの比較

Dalvik仮想マシンで採用されているスレッドインタープリタとは何か?

インタープリタの設計

Page 5: 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

Page 6: Dalvik仮想マシンのアーキテクチャ 改訂版

Dalvik仮想マシン レジスタマシン

レジスタマシンは演算を行うための「レジスタ」と呼ばれる領域を持つ 1. 演算に使う値をメモリからレジスタにコピーする 2. レジスタ上の値を演算し、レジスタに結果を保存する 3. 演算結果をメモリへコピーする

レジスタマシンの基本動作

メモリ

レジスタ1

レジスタ2

レジスタ3

レジスタ4

レジスタ

レジスタ5

レジスタ6

演算

Page 7: Dalvik仮想マシンのアーキテクチャ 改訂版

Dalvik仮想マシン レジスタマシンの補足

・レジスタはメソッド呼び出し時にスタック領域に確保される →確保されるレジスタは0~65535個までの可変数 ・メソッド終了時にスタック領域に確保されたレジスタは開放される ・レジスタにはレジスタを識別する番号、「レジスタ番号」が割り振られる ・Dalvik仮想マシンのレジスタとは一般的にいわれるレジスタとは違い、 演算に使うメモリ領域をレジスタと呼んでいる

Dalvik仮想マシンは特殊なレジスタマシン

メソッドA R1

スタック

メソッドA R0

(VM-specific internal goop)

メソッドB R1

メソッドB R0

成長方向

メソッドAから メソッドB呼出

Page 8: Dalvik仮想マシンのアーキテクチャ 改訂版

CPUの機械語や 仮想マシンバイトコードの基礎用語

機械語やバイトコードの命令には最低以下の情報が必要 ・命令の種類を示す「オペコード」 ・命令の演算対象となる値を示す「オペランド」 →オペランドにはレジスタ番号や即値が指定される

最小の構成要素

機械語やバイトコードの命令のバイナリサイズを「命令長」と呼ぶ →スタックマシンとレジスタマシンとの比較の大きなポイント ちなみに、命令長はCPU、仮想マシンごとに可変長のものと固定長の ものと、どちらも存在する ・固定長 – MIPS/ARMなど ・可変長 – x86/Java仮想マシン/Dalvik仮想マシンなど

命令長

Page 9: Dalvik仮想マシンのアーキテクチャ 改訂版

仮想マシンバイトコード 命令のイメージ (架空の例)

レジスタAとレジスタBを加算してレジスタCに格納する命令の例

たとえば

オペコード ADD (8bit)

オペランド レジスタ番号C

(8bit)

オペランド レジスタ番号A

(8bit)

オペランド レジスタ番号B

(8bit)

命令長=32bit

Page 10: Dalvik仮想マシンのアーキテクチャ 改訂版

Java仮想マシン スタックマシンの特徴

・可変長命令フォーマット ・オペランドは8bit ・演算対象はオペランドスタックのトップと決まっている命令が多い →この場合オペランドが省略可能 ・オペランドスタックを操作するための命令が必要なケースがある →そのため命令数がレジスタマシンにくらべて多くなるケースがある

特徴

・バイトコードのサイズは小さい (オペランドが省略できるため) ・同じ処理をした場合の命令数はレジスタマシンにくらべて多い

まとめると

Page 11: Dalvik仮想マシンのアーキテクチャ 改訂版

Dalvik仮想マシン レジスタマシンの特徴

・可変長命令フォーマット ・オペランドは8bit ・レジスタがオペランドとなる場合、オペランドサイズは最大16bitになる →レジスタが最大65535個あるため ・命令は16bitでアライメントされている ・オペランドスタックを操作するための命令が不要である

特徴

・バイトコードのサイズは大きい (オペランドが大きいため) ・同じ処理をした場合の命令数はスタックマシンにくらべて少ない

まとめると

Page 12: Dalvik仮想マシンのアーキテクチャ 改訂版

Dalvikバイトコードの命令長が 長い問題に対する対策

レジスタをオペランドとした場合、オペランドが巨大化

長い命令長

すべての命令の種類ですべてのレジスタを参照することをあきらめる →参照するレジスタ番号を制限することによりオペランドを小さくする 命令により以下のレンジで参照できるレジスタ番号を制限 ・レジスタ番号0~15 (オペランド4bit) ・レジスタ番号0~255 (オペランド8bit) ・レジスタ番号0~65535 (オペランド16bit)

その対策

Page 13: Dalvik仮想マシンのアーキテクチャ 改訂版

なぜDalvik仮想マシンは レジスタマシンなのか?

・オペランドスタックを操作するための命令が必要なケースがある →そのため命令数がレジスタマシンにくらべて多くなるケースがある

スタックマシンの欠点

・命令数が多いことによる「インタープリタでの」パフォーマンス低下 →インタープリタの設計と実装を理解すると理由がわかる ・JITコンパイラでは起こりにくい問題 →初期のDalvik仮想マシンはインタープリタのみであったのと関連? →インタープリタのパフォーマンスを優先した可能性がある

パフォーマンスの問題

Page 14: Dalvik仮想マシンのアーキテクチャ 改訂版

一般的なインタープリタの実装

素朴に書くと擬似コードのようになる。実はオーバーヘッドが大きい。 →どの辺りがオーバーヘッドが大きいのか?

擬似コードによる例

# INSTR = 現在実行中のバイトコード命令 # TBL = オペコードに対応した処理の関数テーブル LOOP : FUNC = TBL[INSTR->OPCODE] CALL FUNC(INSTR) # 関数呼び出し NEXT INSTR # 次の命令に移動する GOTO LOOP # 繰り返し

Page 15: Dalvik仮想マシンのアーキテクチャ 改訂版

一般的なインタープリタの 問題点

・ジャンプが多い (ジャンプは最悪10CPUサイクル以上かかる処理) 1. 命令ごとにループを回す箇所のジャンプ 2.命令実行ルーチンに飛ぶためのジャンプ →このジャンプはCPUによる最適化処理(分岐予測)が効かない 3. 命令実行ルーチンから戻るためのジャンプ ・テーブル参照がある

重い処理が多い

バイトコード命令が単なる加算である場合は、それそのものの処理は 数CPUサイクルで完了するが、バイトコード命令ごとに発生する インタープリタのオーバーヘッドは数十サイクル以上ある

巨大なオーバーヘッド

Page 16: Dalvik仮想マシンのアーキテクチャ 改訂版

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 … # 以下繰り返し

擬似コードによる例

Page 17: Dalvik仮想マシンのアーキテクチャ 改訂版

Dalvik仮想マシン インタープリタの改善点

素朴なインタープリタとくらべるとオーバヘッドが数分の一

・ジャンプが命令実行ルーチンに飛ぶためのジャンプのみ →ただしこのジャンプはCPUによる最適化処理(分岐予測)が効かない ・テーブル参照がない →次の命令のジャンプ先を演算で算出できる

オーバーヘッドが低減

重い処理が少ない

Page 18: Dalvik仮想マシンのアーキテクチャ 改訂版

ちなみにJITコンパイラを使うと

・命令実行のためにジャンプする必要がない ・テーブル参照はない

重い処理がない

・命令の「フェッチ(読み込み処理)」が不要 ・命令の「デコード(命令の意味解釈処理)」が不要

更に

・速いのは当たり前 ・実行するバイトコードの命令数の多い少ないはあまり関係ない

まとめると

Page 19: Dalvik仮想マシンのアーキテクチャ 改訂版

まとめ

・実行するバイトコード命令ごとに発生するオーバーヘッドは大きい ・Dalvik仮想マシンではインタープリタを改良、改善している ・しかし依然として実行するバイトコード命令の数は少ないほうが良い

要約

・バイナリサイズはJava仮想マシンの方がだいぶん小さい ・実行速度はDalvik仮想マシンの方が高速にできる「かもしれない」 →インタープリタのパフォーマンスでは有利 →JITコンパイラを採用するとどちらが有利か微妙

まとめ

Page 20: Dalvik仮想マシンのアーキテクチャ 改訂版

おわり

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