Java-virtual-machine-jit-compiler

提供:Dev Guides
移動先:案内検索

Java仮想マシン-JITコンパイラー

この章では、JITコンパイラーと、コンパイル言語とインタープリター言語の違いについて学習します。

コンパイル済みvs. 通訳言語

C、C ++、FORTRANなどの言語はコンパイルされた言語です。 それらのコードは、基礎となるマシンを対象としたバイナリコードとして配信されます。 つまり、高レベルのコードは、基礎となるアーキテクチャ専用に作成された静的コンパイラーによってバイナリコードに一度にコンパイルされます。 生成されたバイナリは、他のアーキテクチャでは実行されません。

一方、PythonやPerlなどのインタープリター言語は、有効なインタープリターがあれば、どのマシンでも実行できます。 高レベルのコードを1行ずつ調べ、それをバイナリコードに変換します。

通常、解釈されたコードはコンパイルされたコードよりも遅くなります。 たとえば、ループを考えます。 インタプリタは、ループの各反復に対応するコードを変換します。 一方、コンパイルされたコードは、翻訳を1つだけにします。 さらに、インタープリターは一度に1行しか表示しないため、コンパイラーなどのステートメントの実行順序を変更するなど、重要なコードを実行できません。

私たちは、以下のそのような最適化の例を見ていきます-

メモリに保存された2つの数字を追加。 メモリへのアクセスは複数のCPUサイクルを消費する可能性があるため、優れたコンパイラはメモリからデータをフェッチし、データが利用可能な場合にのみ追加を実行する命令を発行します。 待機せず、その間に他の命令を実行します。 一方、インタープリターはコード全体を常に認識していないため、解釈中にそのような最適化は不可能です。

ただし、その場合、インタープリター言語は、その言語の有効なインタープリターを備えたマシンで実行できます。

Javaはコンパイルまたは解釈されますか?

Javaは妥協点を見つけようとしました。 JVMはjavacコンパイラと基礎となるハードウェアの間に位置するため、javac(またはその他のコンパイラ)コンパイラは、プラットフォーム固有のJVMが認識するバイトコードでJavaコードをコンパイルします。 JVMは、コードの実行時に、JIT(Just-in-time)コンパイルを使用して、バイトコードをバイナリでコンパイルします。

ホットスポット

典型的なプログラムでは、頻繁に実行されるコードはごくわずかです。多くの場合、このコードがアプリケーション全体のパフォーマンスに大きな影響を与えます。 このようなコードのセクションは、 HotSpots と呼ばれます。

コードの一部が一度しか実行されない場合、それをコンパイルするのは労力の無駄であり、代わりにバイトコードを解釈する方が速いでしょう。 しかし、セクションがホットセクションであり、複数回実行される場合、JVMは代わりにコンパイルします。 たとえば、メソッドが複数回呼び出された場合、コードのコンパイルにかかる余分なサイクルは、生成される高速なバイナリによって相殺されます。

さらに、JVMが特定のメソッドまたはループを実行するほど、より高速なバイナリが生成されるようにさまざまな情報が収集され、さまざまな最適化が行われます。

私たちは次のコードを考えてみましょう-

for(int i = 0 ; I <= 100; i++) {
   System.out.println(obj1.equals(obj2));//two objects
}

このコードが解釈されると、インタープリターは反復ごとにobj1のクラスを推測します。 これは、Javaの各クラスに.equals()メソッドがあり、Objectクラスから拡張されており、オーバーライドできるためです。 したがって、obj1が各反復の文字列であっても、演deは行われます。

一方、実際に発生することは、JVMが各反復でobj1がStringクラスであることに気付くため、Stringクラスの.equals()メソッドに対応するコードを直接生成することです。 したがって、ルックアップは不要であり、コンパイルされたコードはより高速に実行されます。

この種の動作は、JVMがコードの動作を知っている場合にのみ可能です。 したがって、コードの特定のセクションをコンパイルする前に待機します。

以下は別の例です-

int sum = 7;
for(int i = 0 ; i <= 100; i++) {
   sum += i;
}

インタープリターは、ループごとに、「sum」の値をメモリから取得し、「I」を追加して、メモリに保存します。 メモリアクセスは高価な操作であり、通常は複数のCPUサイクルがかかります。 このコードは複数回実行されるため、HotSpotです。 JITはこのコードをコンパイルし、次の最適化を行います。

「sum」のローカルコピーは、特定のスレッドに固有のレジスタに保存されます。 すべての操作はレジスタ内の値に対して実行され、ループが完了すると、値がメモリに書き戻されます。

他のスレッドが変数にアクセスしている場合はどうなりますか? 更新は他のスレッドによって変数のローカルコピーに対して行われているため、古い値が表示されます。 このような場合、スレッドの同期が必要です。 非常に基本的な同期プリミティブは、「sum」を揮発性として宣言することです。 現在、変数にアクセスする前に、スレッドはローカルレジスタをフラッシュし、メモリから値をフェッチします。 アクセスすると、値はすぐにメモリに書き込まれます。

以下は、JITコンパイラによって行われるいくつかの一般的な最適化です-

  • メソッドのインライン化
  • デッドコード除去
  • 呼び出しサイトを最適化するためのヒューリスティック
  • 一定の折りたたみ