just-in-timeコンパイルとは? 実行しながらコンパイルする
just-in-time (JIT) コンパイルは、プログラムの実行中にコードをネイティブのマシンコードへ翻訳し、最も頻繁に実行される部分に労力を集中させます。
JITコンパイルは、解釈と事前コンパイルの間に位置します。runtimeはまずbytecodeを解釈し、どの関数が繰り返し走るかを監視し、それらのホットなパスをその場で最適化されたマシンコードへコンパイルします。インタプリタのような速い起動と、ウォームアップ後はコンパイル済みプログラムのような速い定常状態の速度が得られます。
JITが何をコンパイルするか決める方法
runtimeはプログラムの実行中にプロファイルを取り、各メソッドやループがどれだけ頻繁に走るかを数えます。頻繁に実行される「ホット」なコードはコンパイラに昇格され、めったに使われない「コールド」なコードは解釈され続けます。こうしてruntimeは見合う場所にだけ労力を費やします。
JIT vs 事前コンパイル
事前 (AOT) コンパイルは、プログラムが走る前にすべての作業を行い、即座のピーク速度を与えますがruntimeの知識はありません。JITは実行中にコンパイルするため、実際のruntimeデータを使って最適化できますが、ウォームアップ期間という代償があります。
JITの強みとコスト
- 解釈から実行が始まるため、速い起動。
- ホットなパスがコンパイルされた後の高い定常状態の性能。
- 実際のruntimeの振る舞いに基づく最適化。
- ウォームアップのコストと、コンパイラおよびコンパイル済みコードのための追加メモリ。
JITを目にする場所
JVM、.NET CLR、モダンなJavaScriptエンジン (V8、SpiderMonkey) はいずれもJITを使います。だからこそ長時間動くサーバーは最初の1分の後に速くなり、小さなスクリプトはウォームアップが完了する前に終わるため恩恵をまったく受けないことがあるのです。
簡単な例
V8を搭載したNode.jsサーバーは、最初の数百回はリクエストハンドラを解釈で走らせ、明らかにホットになったら一度JITコンパイルします。そのため後のリクエストは目に見えて速くなります。
CIにおけるJIT
短命なCIプロセスはしばしば定常状態に達しないため、JITのウォームアップはCIでのベンチマークを誤解を招くものにし、起動のオーバーヘッドとメモリを加えます。JITの多いruntimeがテスト中にメモリをスパイクさせると、jobがOOMで強制終了されることがあります。Latchkeyは余裕のためのより大きなrunnerを提供し、一時的なOOMの失敗を自動でリトライします。
重要なポイント
- JITは、実行をプロファイルした後、ホットなコードを実行時にネイティブのマシンコードへコンパイルします。
- 速い起動と高い定常状態の速度を、ウォームアップのコストを払って両立させます。
- 短いCIプロセスはしばしばウォームアップに至らず、ベンチマークを歪め、オーバーヘッドを加えます。