ジャストインタイムコンパイルのトレースは、仮想マシンが実行時にプログラムの実行を最適化するために使用する手法です。これは、頻繁に実行される操作の線形シーケンスを記録し、それらをネイティブマシンコードにコンパイルして実行することによって行われます。これに対し、従来のジャストインタイムコンパイル(JIT)は、各メソッドを順番にコンパイルし、メソッド間の最適化は行いません。
ジャストインタイムコンパイル(JIT)は、実行時にプログラムの一部をマシンコードにコンパイルすることで、プログラムの実行速度を向上させる手法です。JITコンパイラを分類する一つの方法は、コンパイル範囲によるものです。メソッドベースのJITコンパイラは一度に1つのメソッドをマシンコードに変換しますが、トレースJITは頻繁に実行されるループをコンパイル単位として使用します。トレースJITは、多くのプログラムがホットループと呼ばれる特定のループでほとんどの時間を費やし、後続のループの反復処理がしばしば同様のパスをたどるという挙動に基づいています。トレースJITを搭載した仮想マシンは、多くの場合、混合モード実行環境であり、トレースJITに加えて、インタプリタまたはメソッドコンパイラのいずれかを備えています。
トレース機能を備えたJITコンパイラは、実行時に様々なフェーズを経ます。まず、ループのプロファイリング情報が収集されます。ホットループが特定されると、特別なトレースフェーズに入り、そのループで実行されたすべての操作が記録されます。この一連の操作はトレースと呼ばれます。次に、トレースが最適化され、マシンコードにコンパイルされます。このループが再度実行される際には、プログラム本体の代わりにコンパイルされたトレースが呼び出されます。
これらの手順については、以下で詳しく説明します。
プロファイリングの目的は、ホットループを特定することです。これは通常、各ループの反復回数をカウントすることによって行われます。ループの反復回数が一定の閾値を超えると、そのループはホットループとみなされ、トレースフェーズに入ります。
トレースフェーズでは、ループの実行は通常通り進行しますが、それに加えて、実行されたすべての操作がトレースに記録されます。記録された操作は通常、トレースツリーに格納され、多くの場合、中間表現(IR)で表されます。トレースは関数呼び出しを追跡するため、関数呼び出しはトレースにインライン化されます。トレースはループが終了して先頭に戻るまで続きます。
トレースはループの具体的な実行パスをたどって記録されるため、そのトレースの後の試行では、元のパスから逸脱する可能性があります。そのような逸脱が発生する可能性のある箇所を特定するために、トレースには特別なガード命令が挿入されます。そのような箇所の一例として、if文が挙げられます。ガードは、元の条件がまだ真であるかどうかを素早くチェックするものです。ガードが失敗した場合、トレースの実行は中止されます。
トレースは実行中に行われるため、トレースには実行時情報(例えば、型情報)を含めることができます。この情報は、後で最適化フェーズで使用して、コードの効率を向上させることができます。
トレースは、実行パスが1つしか表さないため、制御フローが存在せず、処理も不要なので、最適化が容易です。典型的な最適化には、共通部分式の削除、デッドコードの削除、レジスタ割り当て、不変コードの移動、定数折り畳み、エスケープ解析などがあります。[ 1 ]
最適化後、トレースは機械語に変換されます。最適化と同様に、トレースは線形であるため、この変換も容易です。
トレースが機械語にコンパイルされると、ループの次の反復処理で実行できるようになります。トレースの実行は、ガードが失敗するまで続きます。
JIT の概念自体は 1960 年代に遡りますが、トレース JIT がより頻繁に使用されるようになったのはごく最近のことです。今日のトレース JIT の概念に似たアイデアが最初に言及されたのは 1970 年です。[ 2 ]コンパイル済みコードは、解釈中に実行されたアクションを保存するだけで、実行時にインタプリタから派生させることができることが観察されました。
トレースの最初の実装は Dynamo であり、「プロセッサ上で実行されるネイティブ命令ストリームのパフォーマンスを透過的に向上させることができるソフトウェア動的最適化システム」です。[ 3 ]これを行うには、ネイティブ命令ストリームを解釈して「ホット」な命令シーケンスを見つけます。このシーケンスに対して最適化されたバージョンが生成され、キャッシュされ、実行されます。
Dynamoは後にDynamoRIOへと拡張された。DynamoRIOベースのプロジェクトの一つは、トレースと部分評価を組み合わせたインタプリタ構築のためのフレームワークであった。これは「言語実装からインタプリタのオーバーヘッドを動的に除去する」ために使用された。[ 4 ]
2006年に、高水準言語向けの初のトレース機能付きJITコンパイラであるHotpathVMが開発されました。[ 5 ]このVMは、頻繁に実行されるバイトコード命令を動的に識別し、トレースして静的単一代入形式(SSA)構造を使用してマシンコードにコンパイルすることができました。HotpathVMの開発動機は、リソースが限られたモバイルデバイス向けの効率的なJVMを実現することでした。
トレースJITのもう1つの例は、 MozillaのFirefox(2009)向けJavaScript実装の1つであるTraceMonkeyです。 [ 6 ] TraceMonkeyは、実行時に動的言語JavaScriptで頻繁に実行されるループトレースをコンパイルし、各パスで発生する実際の動的型に合わせて生成されたコードを特殊化します。
トレースJITを利用する別のプロジェクトはPyPyです。PyPyの翻訳ツールチェーンで記述された言語実装に対してトレースJITを使用できるようにすることで、そのインタプリタを使用して実行されるプログラムのパフォーマンスを向上させます。これは、インタプリタによって実行されるプログラムではなく、インタプリタ自体をトレースすることによって可能になります。[ 7 ]
JIT のトレースは、 Microsoftが共通中間言語(CIL)用の SPUR プロジェクトで検討した。SPUR は CIL 用の汎用トレーサーであり、JavaScript 実装のトレースにも使用できる。[ 8 ]
次のPythonプログラムは、連続する整数の二乗の合計が100000を超えるまで計算を続けます。
def square ( x ): return x * xi = 0 y = 0 while True : y += square ( i ) if y > 100000 : break i = i + 1このプログラムのトレースは次のようになります。
loopstart ( i1 , y1 ) i2 = int_mul ( i1 , i1 ) # i*i y2 = int_add ( y1 , i2 ) # y += i*i b1 = int_gt ( y2 , 100000 ) guard_false ( b1 ) i3 = int_add ( i1 , 1 ) # i = i+1 jump ( i3 , y2 )関数呼び出しがsquareトレースにインライン化され、if 文が に変換される様子に注目してくださいguard_false。