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 の概要、構成を 述べる。
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 に基づいてソースコー ドが公開されていて、教育、研究目的に自由に利用できる。
別の JVM 上のオブジェクト、つまり遠隔オブジェクトをロー カルオブジェクトと同じか近い方法で操作可能にするシステム を、ここでは分散オブジェクトシステムと呼ぶ。 仕様[3]より、JVM 自 身は遠隔オブジェクトを扱う機能を持たない。既存の Java用 分散オブジェクトシステム [4] [5] [6] はそれ自体 Javaのプログラムとして実現されている。 遠隔オブジェクトの操作が Java 自身で実現されているために、 ローカルオブジェクトにはない制約がある。例えば、生成に `new クラス名' では生成できず、メソッド呼び出しだけが可 能でフィールド、配列アクセスはできない。また、遠隔参照す る変数の型は、参照の指す実際の遠隔オブジェクトと異なる。
JITコンパイラを利用して、JVMによるバイトコードの解釈を変 更することで、上記の制約を取り除け、より分散透明な分散オ ブジェクトシステムを実現できる。
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 ベンチマーク
レジスタマシンを対象としたコンパイラは通常、最適化の容易 さ、移植性とレジスタの有効活用を両立するために、プログラ ムを一旦レジスタ数無制限の中間言語に変換し、最適化後にレ ジスタ割り付けを行う。 ところが shuJIT はレジスタ割り付けを行わない。コンパイル 処理をなるべく軽くするためである。 JVM の単純なベンチマーク [7]では 実行時コンパイルにかかる時間が計測されない。 しかし現実には実行時コンパイルの間 JVM は利用者を待たせ ている。JITコンパイラでは生成されるコードの質だけでなく コンパイル時間の短さも重要である。
shuJIT では、レジスタ割り付けを行わずに、かつ複数のレジ スタを活用できるコード生成手法を採っている。TYA[2]と同様に、生成するネ イティブコードをあらかじめ各バイトコード命令ごとに用意し てある。基本的にこの pre-assembled ネイティブコードをつ なぎ合わせてコード生成を行っていく。
これだけではスタックトップ付近のキャッシュにはひとつのレ ジスタしか使えない。そこで、複数レジスタを活用するために、 レジスタがスタック上のどの要素を保持しているかに対応する スタック状態を定義した。 shuJIT では 2つのレジスタをキャッシュに利用し 5 状態定義 している。pre-assembled コードも 5状態を前提としたものを それぞれ別に用意している。コンパイラは、バイトコード命令、 スタック状態に応じた pre-assembled コードをつなぎ合わせていく。
図3: stack states
ジャンプ命令では、ジャンプ直前のスタック状態とジャンプ先 で想定されている状態が異なり得るので注意が要る。通常の JVMジャンプ命令ではジャンプ直前に状態移行コードを挿入す る。JVM の tableswitch, lookupswitch 命令ではトランポリ ン方式で状態を移行させる。すなわち、状態移行と指定番地へ のジャンプだけを行うコード片をあらかじめ用意しておき、各 swtich命令ではそのコード片へジャンプさせる。
スタックトップをレジスタに載せる他にも、いくつか工夫をしている。
JVM が用意しているスタックの代わりにプロセッサの機構を使っ ている。JVM のスタックを利用すると、push 動作は JVM スタッ クへのコピーと JVM スタックポインタとなる。プロセッサの スタックを利用するとプロセッサの push 命令ひとつで済む。
しかしそもそも、コード生成時にレジスタマシン的な中間言語 を経由させればネイティブコードまでがスタックマシンの動作 を行う必要はない。pre-assembled コードを使用するコード生 成方式に特化した最適化である。
生成されたネイティブコードが自己書き換えを行う。一度きり、 初めて実行されたときのみ必要な処理を二度目以降の実行では 省くために利用している。例えば JVM new 命令では、実行中 のコードがインスタンスを生成するクラスへアクセスする権限 を持っているかチェックする必要がある。このチェックは一度 で充分である。
shuJIT はフリーソフトウェアとしてすでに公開されている。 ライセンスは GNU Public License ver.2 なので、誰でもソー スコードを入手し、学習、研究に利用することができる。
JDK/FreeBSD で利用可能な JIT コンパイラは shuJIT のみである。
JITDO は現在 (98年 10月 27日) shuJIT をベースに実装中で あり、まだ動作していない。完成すれば、世界一分散透明な分 散オブジェクトシステムとなる。
JVM 自身は遠隔オブジェクトを直接扱う機能を持たない。既存 の分散オブジェクトシステムはそれを補う Java のプログラム である。Java プログラムであるゆえに、ローカルオブジェク トと同じようには遠隔オブジェクトを扱えない。インスタンス の生成はローカルオブジェクトとは異なる方法で行わざるを得 ず、フィールド、配列のアクセスはできず、遠隔参照の変数の 型が実際の遠隔オブジェクトとは異なる。
JIT コンパイラはバイトコードをいかようにもコンパイルでき る。つまり JVM によるバイトコードの解釈を変更できる。オ ブジェクトに対する操作、つまりインスタンスの生成、フィー ルド、配列アクセス、メソッド呼び出しを行う JVM 命令の解 釈を変えることで、Java プログラムから遠隔オブジェクトを ローカルオブジェクトと同様に扱えるようにできる。
ここでは、それ自身遠隔オブジェクトを扱うことができる JVM を分散オブジェクト対応 JVM と呼ぶ。
Just In Time Distributed Object -- JITDO は、遠隔オブジェ クトの操作を実現する Java プログラムと、それを利用する JIT コンパイラから成る。
既存システムでは、遠隔オブジェクトの代理となるローカルオ ブジェクトが遠隔オブジェクトと signature (セレクタ) が同 じメソッド群を持ち、メッセージ呼び出しを遠隔オブジェクト に中継する。JITDO ではオブジェクトを操作する JVM 命令を JIT コンパイラがローカルな代理オブジェクトに中継し、代理 オブジェクトは遠隔のリクエストマネージャに中継する。 中継されるのは JVM 命令であってメソッド呼び出しではない ので、遠隔オブジェクトのクラスに応じて代理オブジェクトの クラスを生成する必要はない。
図4: 遠隔オブジェクトの操作
最低限必要なプログラミングインタフェースはこれだけである。VMAddress addr = new VMAddress("foo.bar.com", 10000); JitdoController.setInstantiationVM(addr);
まず、より分散透明である。インスタンスの生成をローカルオ ブジェクトと同じ文法で行えるし、フィールドアクセスが可能 で、配列の遠隔参照を持て、遠隔参照と実際のオブジェクトの 型の不一致がない。
反面、ローカルオブジェクトの操作が遅くなることが予想され る。既存システムでは、virtual 呼び出しを利用して、メソッ ド呼び出しが遠隔参照に対してであった場合は遠隔へのリクエ ストを行っている。JITDO ではオブジェクトへの操作ごとに、 対象がローカルか遠隔かの判断をするので、その分、ローカル な操作であっても遅くなることが予想される。
% 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
Intel x86 プロセッサ用の JIT コンパイラ shuJIT を実装した。 Sun の JVM (JDK, JRE) と Linux または FreeBSD で動作している。 ソースコード込みで公開されていて、学習、研究に利用できる。 その性能、コード生成手法、いくつかの最適化手法について述べた。
JIT コンパイラがバイトコードの解釈を変更することで、既存 システムより分散透明な分散オブジェクトシステムを構成でき る。このアイディアに基づいて、現在 JITDO を実装している。 その設計、プログラミングインタフェースを述べ、既存システ ムとの差異を考察した。