技術部門: x86用Just In Time コンパイラ shuJIT
JITコンパイラによるJava仮想マシンの分散オブジェクト対応 JITDO
首藤 一幸
早稲田大学 理工学研究科 情報科学専攻
shudoh@muraoka.info.waseda.ac.jp


1.応募の技術的主張の概要

SunのJava仮想マシン(JVM)と Intel x86 プロセッサ用の Just In Timeコンパイラ(JIT)、shuJIT を開発した。Linux, FreeBSD で動作する。ネイティブコードの生成に、処理が重い レジスタ割り付けを行わずに複数のレジスタを活用できる手法 を採っている。また、JVM のそれではなくプロセッサのレジス タを使用しての命令数削減や、ネイティブコードの自己書き換 えを利用した最適化を行っている。

JITコンパイラはバイトコードをいかようにもコンパイルする ことができる。つまり、JVM によるバイトコードの解釈を変え ることが可能である。独自の JVM を実装したり既存 JVM を改 造する必要はない。バイトコードの解釈変更には様々な応用が 考えられる。例えば、配列の生成、アクセス命令の解釈を変更 することで、配列を通常のヒープ以外のメモリ空間、例えば分 散共有メモリ上に確保することが可能である[1]

私は現在、JVM 自身を分散オブジェクト対応させようと、JIT コンパイラおよびオブジェクトの遠隔操作機構 JITDO を設計、 実装している。JVM 自身は遠隔オブジェクトを扱えないので既 存の Java用分散オブジェクトシステムはそれ自体が Javaのプ ログラムとして実現されている。そのため、分散オブジェクト をローカルオブジェクトとは同じようには扱えない。可能なの はメソッド呼び出しのみであるなど、文法、意味的にさまざま な制約がある。 JITコンパイラを使ってバイトコードの解釈を変更することで、 これらの制約がない分散オブジェクトシステムを構成可能であ る。

本稿では、まず JITコンパイラ shuJIT のコード生成方式、最 適化手法、性能を述べる。続いて、現在 shuJIT をベースに実 装している分散オブジェクトシステム JITDO の概要、構成を 述べる。


2. 応募の背景

2.1 x86 用 JIT コンパイラ

JIT コンパイラは数多くリリースされている。いくつかの JIT コンパイラはすでに C,C++ に近い速度を実現しているし、さ らなる高速化手法も研究、実装されつつある。 ところが、これら OS、処理系ベンダの JIT コンパイラは、自 社プラットフォームの競争力強化のために投入されるため、フ リーの OS、例えば Linux、FreeBSD 用のものは提供されない。 また、ソースは公開されない。

Linux, FreeBSD でも利用可能な JITコンパイラとして TransVirtual社の JVM Kaffe があるが、 Java処理系のリファレンスとしての JDK が必要な局面はどうしても多く、 JDK 用 JIT コンパイラの需要は高い。 JDK 用 JIT コンパイラとして、Linux には TYA[2] が存在するが、 FreeBSD には存在しなかった。しかも FreeBSD ではインタプ リタとしてアセンブラではなく C で書かれたものが使われて いる。 shuJIT は JDK/FreeBSD で利用可能な唯一の JIT コンパイラである。

動的コンパイルを含めた実行時コンパイルの手法、構成の学習、 研究のためには、JIT コンパイラのソースコードが役立つ。し かし、ベンダ製 JIT コンパイラはソースコードが公開されて いない完全なブラックボックスである。 OpenJIT プロジェクト[1]は、実行時コンパ イラを拡張、制御するプログラミングインタフェースの提供を 目指している。これが成功すれば、コンパイラのソースコード がなくとも研究基盤として利用できる。 shuJIT は GNU Public License ver.2 に基づいてソースコー ドが公開されていて、教育、研究目的に自由に利用できる。

2.2 Java 用分散オブジェクトシステム

別の JVM 上のオブジェクト、つまり遠隔オブジェクトをロー カルオブジェクトと同じか近い方法で操作可能にするシステム を、ここでは分散オブジェクトシステムと呼ぶ。 仕様[3]より、JVM 自 身は遠隔オブジェクトを扱う機能を持たない。既存の Java用 分散オブジェクトシステム [4] [5] [6] はそれ自体 Javaのプログラムとして実現されている。 遠隔オブジェクトの操作が Java 自身で実現されているために、 ローカルオブジェクトにはない制約がある。例えば、生成に `new クラス名' では生成できず、メソッド呼び出しだけが可 能でフィールド、配列アクセスはできない。また、遠隔参照す る変数の型は、参照の指す実際の遠隔オブジェクトと異なる。

JITコンパイラを利用して、JVMによるバイトコードの解釈を変 更することで、上記の制約を取り除け、より分散透明な分散オ ブジェクトシステムを実現できる。


3. 応募の構成・新規性・有用性

3.1 x86 用 JIT コンパイラ: shuJIT

すでにフリーソフトウェアとしてソースコードも含めて公開さ れている[9]。JDK for Linux用として性能面では既存のもの (TYA) に対するアド バンテージは顕著ではないものの、JDK for FreeBSD 用として は唯一の JIT コンパイラである。コード生成は、処理を軽く するためにレジスタ割り付けを行わないながら複数レジスタを 活用可能な手法を採っている。その他、ネイティブコードの自 己書き換えを利用した最適化などを行っている。

3.1.1 性能

Linpack Benchmark[8]、 CaffeineMark 3.0[7] で、インタプリタ、TYA、shuJITを比較した。 環境は Pentium with MMX tech./233MHz、Linux 2.1.124、 Linux用 JDK 1.1.6v5 である。

Method、String の結果の悪さから、メソッド呼び出し性能が低いことがわかる。 改善すべき点である。


図1: Linpack ベンチマーク


図2: CaffeineMark 3.0 ベンチマーク

3.1.2 コード生成手法

レジスタマシンを対象としたコンパイラは通常、最適化の容易 さ、移植性とレジスタの有効活用を両立するために、プログラ ムを一旦レジスタ数無制限の中間言語に変換し、最適化後にレ ジスタ割り付けを行う。 ところが shuJIT はレジスタ割り付けを行わない。コンパイル 処理をなるべく軽くするためである。 JVM の単純なベンチマーク [7]では 実行時コンパイルにかかる時間が計測されない。 しかし現実には実行時コンパイルの間 JVM は利用者を待たせ ている。JITコンパイラでは生成されるコードの質だけでなく コンパイル時間の短さも重要である。

shuJIT では、レジスタ割り付けを行わずに、かつ複数のレジ スタを活用できるコード生成手法を採っている。TYA[2]と同様に、生成するネ イティブコードをあらかじめ各バイトコード命令ごとに用意し てある。基本的にこの pre-assembled ネイティブコードをつ なぎ合わせてコード生成を行っていく。

これだけではスタックトップ付近のキャッシュにはひとつのレ ジスタしか使えない。そこで、複数レジスタを活用するために、 レジスタがスタック上のどの要素を保持しているかに対応する スタック状態を定義した。 shuJIT では 2つのレジスタをキャッシュに利用し 5 状態定義 している。pre-assembled コードも 5状態を前提としたものを それぞれ別に用意している。コンパイラは、バイトコード命令、 スタック状態に応じた pre-assembled コードをつなぎ合わせていく。

図3: stack states
図3: stack states

ジャンプ命令では、ジャンプ直前のスタック状態とジャンプ先 で想定されている状態が異なり得るので注意が要る。通常の JVMジャンプ命令ではジャンプ直前に状態移行コードを挿入す る。JVM の tableswitch, lookupswitch 命令ではトランポリ ン方式で状態を移行させる。すなわち、状態移行と指定番地へ のジャンプだけを行うコード片をあらかじめ用意しておき、各 swtich命令ではそのコード片へジャンプさせる。

3.1.3 その他の最適化

スタックトップをレジスタに載せる他にも、いくつか工夫をしている。

JVM が用意しているスタックの代わりにプロセッサの機構を使っ ている。JVM のスタックを利用すると、push 動作は JVM スタッ クへのコピーと JVM スタックポインタとなる。プロセッサの スタックを利用するとプロセッサの push 命令ひとつで済む。

しかしそもそも、コード生成時にレジスタマシン的な中間言語 を経由させればネイティブコードまでがスタックマシンの動作 を行う必要はない。pre-assembled コードを使用するコード生 成方式に特化した最適化である。

生成されたネイティブコードが自己書き換えを行う。一度きり、 初めて実行されたときのみ必要な処理を二度目以降の実行では 省くために利用している。例えば JVM new 命令では、実行中 のコードがインスタンスを生成するクラスへアクセスする権限 を持っているかチェックする必要がある。このチェックは一度 で充分である。

3.1.4 フリーソフトウェア

shuJIT はフリーソフトウェアとしてすでに公開されている。 ライセンスは GNU Public License ver.2 なので、誰でもソー スコードを入手し、学習、研究に利用することができる。

JDK/FreeBSD で利用可能な JIT コンパイラは shuJIT のみである。

3.2 JIT コンパイラによるJava仮想マシンの分散オブジェクト対応: JITDO

JITDO は現在 (98年 10月 27日) shuJIT をベースに実装中で あり、まだ動作していない。完成すれば、世界一分散透明な分 散オブジェクトシステムとなる。

JVM 自身は遠隔オブジェクトを直接扱う機能を持たない。既存 の分散オブジェクトシステムはそれを補う Java のプログラム である。Java プログラムであるゆえに、ローカルオブジェク トと同じようには遠隔オブジェクトを扱えない。インスタンス の生成はローカルオブジェクトとは異なる方法で行わざるを得 ず、フィールド、配列のアクセスはできず、遠隔参照の変数の 型が実際の遠隔オブジェクトとは異なる。

JIT コンパイラはバイトコードをいかようにもコンパイルでき る。つまり JVM によるバイトコードの解釈を変更できる。オ ブジェクトに対する操作、つまりインスタンスの生成、フィー ルド、配列アクセス、メソッド呼び出しを行う JVM 命令の解 釈を変えることで、Java プログラムから遠隔オブジェクトを ローカルオブジェクトと同様に扱えるようにできる。

ここでは、それ自身遠隔オブジェクトを扱うことができる JVM を分散オブジェクト対応 JVM と呼ぶ。

3.2.1 構成

Just In Time Distributed Object -- JITDO は、遠隔オブジェ クトの操作を実現する Java プログラムと、それを利用する JIT コンパイラから成る。

既存システムでは、遠隔オブジェクトの代理となるローカルオ ブジェクトが遠隔オブジェクトと signature (セレクタ) が同 じメソッド群を持ち、メッセージ呼び出しを遠隔オブジェクト に中継する。JITDO ではオブジェクトを操作する JVM 命令を JIT コンパイラがローカルな代理オブジェクトに中継し、代理 オブジェクトは遠隔のリクエストマネージャに中継する。 中継されるのは JVM 命令であってメソッド呼び出しではない ので、遠隔オブジェクトのクラスに応じて代理オブジェクトの クラスを生成する必要はない。

図4: 遠隔オブジェクトの操作
図4: 遠隔オブジェクトの操作

JIT コンパイラは、操作対象のオブジェクトがローカルに存在 するのか、代理オブジェクトなのかを判断する必要がある。ま た、代理オブジェクトを遠隔オブジェクトがローカルに存在す るように見せるために、JVM の instanceof、checkcast 命令 の挙動を変える必要がある。

3.2.2 プログラミングインタフェース

Java 言語の文法、機能に加えて唯一、オブジェクトの生成先 JVM を指定する手段を用意する必要がある。次のように指定す ると、生成先 JVM が現在制御を握っているスレッドに対応付 けられる。以後、このスレッドからのインスタンス生成は、こ こで指定した JVM 上で行われる。
VMAddress addr = new VMAddress("foo.bar.com", 10000);
JitdoController.setInstantiationVM(addr);
最低限必要なプログラミングインタフェースはこれだけである。

3.2.3 既存分散オブジェクトシステムとの比較

まず、より分散透明である。インスタンスの生成をローカルオ ブジェクトと同じ文法で行えるし、フィールドアクセスが可能 で、配列の遠隔参照を持て、遠隔参照と実際のオブジェクトの 型の不一致がない。

反面、ローカルオブジェクトの操作が遅くなることが予想され る。既存システムでは、virtual 呼び出しを利用して、メソッ ド呼び出しが遠隔参照に対してであった場合は遠隔へのリクエ ストを行っている。JITDO ではオブジェクトへの操作ごとに、 対象がローカルか遠隔かの判断をするので、その分、ローカル な操作であっても遅くなることが予想される。


4. 応募システムの実行例

Linux または FreeBSD のシェルから Linpack ベンチマークを動かした場合。
% java Linpack
Mflops/s: 1.923  Time: 0.36 secs  Norm Res: 1.33  Precision: 2.220446049250313E-16
% setenv JAVA_COMPILER shujit
% java Linpack
Mflops/s: 5.282  Time: 0.13 secs  Norm Res: 1.33  Precision: 2.220446049250313E-16

5. まとめ

Intel x86 プロセッサ用の JIT コンパイラ shuJIT を実装した。 Sun の JVM (JDK, JRE) と Linux または FreeBSD で動作している。 ソースコード込みで公開されていて、学習、研究に利用できる。 その性能、コード生成手法、いくつかの最適化手法について述べた。

JIT コンパイラがバイトコードの解釈を変更することで、既存 システムより分散透明な分散オブジェクトシステムを構成でき る。このアイディアに基づいて、現在 JITDO を実装している。 その設計、プログラミングインタフェースを述べ、既存システ ムとの差異を考察した。


参考文献

[1]
松岡, 小川, 志村, 木村, 堀田, 高木. "OpenJIT -自己反映的な Java JITコンパイラ-", 電子情報通信学会技術研究報告, CPSY98-67, pp. 49-56, August 1998.
[2]
TYA Archive,
http://tya.home.ml.org/.
[3]
Tim Lindholm, Frank Yellin. "The JavaTM Virtual Machine Specification", Addison Wesley, 1997.
[4]
Sun Microsystems. JavaTM Remote Method Invocation Specification, 1997.
[5]
ObjectSpace. Voyager,
http://www.objectspace.com/products/Voyager/.
[6]
平野. "HORB: Distributed Execution of Java Programs", Proceedings of World Wide Computing and Its Applications, March 1997.
[7]
CaffeineMark 3.0,
http://www.pendragon-software.com/pendragon/cm3/.
[8]
Linpack Benchmark -- Java Version,
http://www.netlib.org/benchmark/linpackjava/.
[9]
shuJIT: JIT compiler for Sun JVM/IA32,
http://www.shudo.net/jit/.