実行時コンパイラ
実行時コンパイラ(じっこうじコンパイラ、Just-In-Time Compiler、JITコンパイラ、その都度のコンパイラ)とは、ソフトウェアの実行時にコードのコンパイルを行い実行速度の向上を図るコンパイラのこと。通常のコンパイラはソースコード(あるいは中間コード)から対象CPUの機械語への変換を実行前に事前に行い、これをJITと対比して事前コンパイラ (Ahead-Of-Timeコンパイラ、AOTコンパイラ)と呼ぶ。
目次
1 概要
2 適応的コンパイル (Adaptive Compilation)
3 JITコンパイル方式の利点
4 応用
5 JavaのJITコンパイラ
6 JavaScriptのJITコンパイラ
7 関連項目
8 参照
9 外部リンク
概要
JITコンパイルという用語は、ソフトウェアを構成するモジュールやクラス、関数などの、ある単位のコードがまさに実行されるその時に、コンパイルされることから「Just In Time」の名前が付けられた。一方、動的コンパイルという用語は、実行時に機械語を生成するというより広い意味で使われることがあり、その意味ではJITコンパイルは動的コンパイルの一種と考えることができる。
事前コンパイル方式と比べ、JIT方式ではコンパイル時間の分がプログラム実行時間に関するオーバーヘッドとなる。また、事前コンパイルで可能な、高度で時間のかかる最適化を行うことは許されない。これは、実行速度を向上させるためにコンパイルするのだから、あまり時間がかかっては意味がないためである。
事前のコンパイルと比べてこのような不利な点を持ちながらも、JIT方式が有用なのは、実行時にコンパイルを行うことでオペレーティングシステムやCPUに依存しないソースコードや中間コードの状態でソフトウェアを配布することができるからである。
JITを装備した処理系は、表面上はインタプリタとして動作するが、内部でコンパイルを行い、メモリ上に生成した機械語のコードが実行されるため、インタプリタと比べると実行速度を向上することができる。この意味で、JITはCPUやOSに依存しない実行形式を配布できる、というインタプリタの利点を保ったまま、実行速度が遅い、という欠点を克服しようとするものといえる。
適応的コンパイル (Adaptive Compilation)
上のようなJITコンパイラの短所を補うためのJITコンパイルの一方式として適応的コンパイルという方式がある。これは、起動当初はインタプリタとして実行し、よく呼び出されるメソッドや繰り返し実行されるコードの検出(プロファイリング)を行い、そのようなコードのみをコンパイルする、というものである。このとき、コードが使われた時にすぐにコンパイルするのではなく、何回か呼ばれた後に遅らせてコンパイルを行うが、このことを遅延コンパイル (Lazy Compilation)と呼ぶ。一般にプログラムの実行においてその実行時間の大半はプログラム中のごく一部において費やされる、という経験則がある(実際の比率については状況に依存するが、典型的にはコードの実行時間の80%は20%のコードにおいて費やされるといわれ、80-20の法則と呼ばれる)。それと似て、適応的コンパイルにおいては実行時間の大半が費やされるような、ボトルネックとなるコードのみをコンパイルすることで、起動時のオーバーヘッドや利用メモリ増大を抑えたうえで、効率よく実行速度を向上することができる。
この適応的コンパイルによる適応的最適化 (Adaptive Optimization) は、静的コンパイルでは得られない情報をも元にして最適化が行えるため、静的コンパイルより、むしろパフォーマンスが上がる場合もある。
注意すべきは、企業のバッチ処理である。数百件以内といった少量レコードを処理するバッチジョブが、一日に何百本、何千本も走る企業が多い。そういう場では、JITコンパイルを用いた場合、適応的コンパイルをしてさえも、ジョブの走り初めに、使われるユーザクラスさらにはコアクラスと呼ばれるような共通的なクラスのコンパイル処理がロード処理を伴いのべ何百回と行われる。処理件数が少ないので、使うクラス群の多くはコンパイル効果が出る前に、あるいはコンパイルさえされないうちにジョブ実行が終わってしまう。その結果、正味のユーザロジックよりこうした「オーバヘッド」の方が大きな比率を占める。CPUなどの資源が、いつも同様のプログラム群のコンパイルに消費されてしまい、待ちが生じて多重効果の妨げにもなる。処理件数によってJIT/AOTの有利不利が変わるが使い分ける仕組みは非常に作りにくい。また、(Javaについていえば)AOTコンパイラを通常適用しにくい。[独自研究?]これらの理由で、性能面ではマイナスな場面でも一般的な[独自研究?]JITコンパイラを使用していることがある。
長時間バッチジョブおよびオンラインでは、JITコンパイラ、特に適応的コンパイルが概してフィットしている。[独自研究?]
適応的コンパイルの最適化のために、何回実行されたらコンパイルするか、といったパラメタが用意されているコンパイラがある[要出典]。パラメタチューニングは万能ではないとしても重要である。[独自研究?]
JITコンパイル方式の利点
JITコンパイル方式と事前コンパイルの生成コードの質を比べると、前述のようにコンパイル時間に対する制約のためJIT方式の方が不利であるが、有利な点もある。それは、実行環境を知った上でそれに応じた生成コードの選択や最適化を行うことができるということである。
インテルのx86CPUを例にとって見ると、IA-32アーキテクチャの範囲内でもそれぞれの世代でさまざまに命令が拡張されてきているが、アプリケーションコードの後方互換性を保持する場合、実行バイナリ中では80386と互換の命令しか使うことができない。
つまり、MMX PentiumのMMX命令を含んだコードは80386やPentiumでは実行できない。しかし、JIT方式では、CPUがMMXをサポートしているならMMX命令を使ったコードを生成し、そうでなければ多少効率の悪いPentiumの命令の範囲内での実行を行う、ということができる。
また、実行環境におけるキャッシュやメモリのサイズ、速度特性なども実行時にならないと最終的にはわからない。JITコンパイル方式では実際に走行しているCPUやメモリの情報を知ることができるため、それに応じたコードを生成することができ、事前コンパイルよりも優れたコードを生成できる可能性がある。
さらに、オブジェクト指向言語の実行においては仮想メソッドの呼び出しは仮想関数表を経由した間接呼び出しになるが、動的コンパイルにおいては、そのメソッドをオーバーライド定義したサブクラスが存在しない限り、
間接呼び出しを静的束縛として呼び出したり、あるいはインライン展開することができる(そのメソッドをオーバーライドするサブクラスが動的にロードされる可能性があるが、その場合はこのコンパイルされたメソッドは最適化戻し (deoptimize) される必要がある)。
応用
JIT処理はJava技術の普及に伴い広範囲に使われるようになったが、JavaのHotSpot技術はSunにおけるSelf言語での動的コンパイル技術研究に基づいており、それに先立つ商用Smalltalk処理系でもJITコンパイル技術は確立されていた。
JIT技術はトランスメタ社によるCrusoeでx86コードからCrusoeのVLIW命令への変換に用いられている。適応的コンパイル技術はDEC社によるFX!32技術でも用いられていた。
Microsoft社による.NETプラットフォームも当初からJITによる実行を前提に設計されている。
JavaのJITコンパイラ
Symantec社によるsymjitおよびBorland社によるJITコンパイラは初期の主要なJITコンパイラであった。
Sun Microsystems自身によるHotSpotコンパイラは本格的に適応的コンパイル方式を採用したJIT処理系である。Hotspot以降はJITコンパイラ部分のインタフェースが規定されており、JITコンパイルエンジン部分を差し替えることができるようになった。
IBMによるIBM JDK、BEAによるJRockitはいずれも適応的コンパイルを行う独自のJIT処理系を持っている。後者は特にx86に特化して実行効率を高めている。
学術的なものとしては、首藤によるShuJITや、富士通研究所と東京工業大学によるリフレクション機能を扱うOpenJITなどがある。
JavaScriptのJITコンパイラ
近年の主要なウェブブラウザはJavaScriptのエンジンにJITコンパイラを搭載し、高速に処理できるようになっている。Internet Explorer 9、Mozilla Firefox 3.5、Google Chrome 1、Safari 4、Opera 10.50、Opera Mobile 10.1以降のウェブブラウザに搭載されている。NetFront Browser 4.1 には搭載されていない。変数の型は統計的には安定しているという事実を使い、実行時の変数に代入された値の統計データから、変数に型を割り振ることにより、JITコンパイラが実現し、高速にJavaScriptを処理できるようになった[1]。Google ChromeのV8など、インタプリタを使わずに最初からJITコンパイルし、変数などの型は実行時に随時割り振っていくタイプと、Firefox 3.5のように、事前に一度インタプリタで実行して、その情報を元に型を割り振りながらJITコンパイルするタイプなどがある。どちらのタイプであっても、型が安定しているケースは、高速に実行できるようになっている。JavaScriptにおいて、型が安定しているというのは、オブジェクトについては、プロパティが追加されていないという意味も含む。プロパティアクセスの時に、まず、型が期待通りの型であるかチェックし、もし、そうならば、プロパティからそれが指し示す番地へはC言語の構造体のように定数を足し算するだけで求まる。
関連項目
- 動的コンパイル
- トレーシング実行時コンパイル
参照
^ an overview of TraceMonkey ✩ hacks.mozilla.org
外部リンク
- John Aycock, A brief history of just-in-time