この章ではOracle JRockit JVMによるコード生成の概要について説明します。JRockit JVMによってコードがJITコンパイルされ、コードの最適化で高いパフォーマンスが実現される仕組みを説明します。
この章の内容は次のとおりです。
ユーザーの視点では、JRockit JVMは特定のプラットフォームに対して高度に最適化されたマシン・コードにJavaコードを変換するブラック・ボックスとなります(図2-1を参照)。
図2-2は、JavaコードがJRockit JVMを通過する際の状況を示しています。JavaコードがJRockit JVMに入力されると、そのコードは操作ステージ、データ構造ステージおよび変換ステージを通過してから、マイクロチップでバイナリ・コードとして現れます。
JRockit JVMのコード・ジェネレータは、Javaアプリケーションの実行中ずっとバックグラウンドで動作し、コードが最適な状態で実行されるように自動的に調整します。コード・ジェネレータの動作は、図2-3に示すように3段階に分かれています。
ステップ1: JITコンパイル
マシンコード生成プロセスの第1段階では、Just-In-Time (JIT)コンパイルが行われます。JRockitにはインタプリタが含まれていないため、バイト・コードからネイティブのマシン・コードへのJITコンパイルは、メソッドの実行前に行われる必要があります。JITコンパイルは、Javaメソッドが初めて呼び出されたときに実行されます。
JITコンパイラは高速に、適度に効率的なコードを生成します。これは、Javaアプリケーションの迅速な起動および実行を可能にするために必要です。したがって、頻繁に呼び出されるメソッド(ホット・スポット)にはさらに最適化が必要なことがプロファイリングによって明らかになります。JRockitアプローチ(JITコンパイル、インタープリタなし)では起動時間が比較的に長くなりますが、JITコンパイルで適度に効率的なコードのみが生成される場合でも、生成されたコードはインタープリタによるコードよりもはるかに高速なままです。
ステップ2: スレッド監視
マシンコード生成プロセスの第2段階では、JRockit JVMは最先端で低コストのサンプリング・ベースの方法を使用して、どの機能が最適化の対象となるかを特定します。サンプラ・スレッドは定期的にウェイクアップして、複数のアプリケーション・スレッドのステータスをチェックします。このスレッドは、各スレッドの実行内容を確認して実行履歴を記録します。この情報はすべてのメソッドで追跡されます。あるメソッドが頻繁に使用されている(ホットである)ことが情報に示されている場合、そのメソッドは最適化の対象として指定されます。一般に、そのような最適化の機会はアプリケーションの実行の早い段階で発生し、実行が進むにつれて、その割合は小さくなります。
ステップ3: コード最適化
コード最適化は、頻繁に実行されるコードが、より効率的な実行のために再コンパイルされるプロセスです。
JRockit JVMでメソッドが最初に実行されるとき、メソッドはマシン・コードにコンパイルされます。このコンパイルは高速ですが、生成されるコードは最大限に効率化されているわけではありません。このコードは、一度実行されると破棄されるメソッドには許容範囲ですが、メソッドが繰り返し使用される場合、対象となるメソッドのコードがより効率的な方法で再生成されるとシステムのパフォーマンスが向上します。
JRockit JVMはこれらの頻繁に実行される(ホットな)メソッドを最適化して、可能なかぎり効率的なコードを生成します。この最適化はバックグラウンドで実行され、実行中のアプリケーションに干渉することはありません。
次のコード例はJRockit JVMによるJavaコードの最適化を示しています。
例2-1は最適化の前のコードを示しています。
例2-1 最適化の前のコード
class A { B b; public void newMethod() { y = b.get(); ...do stuff... z = b.get(); sum = y + z; } } class B { int value; final int get() { return value; } }
例2-2は最適化されたコードを示しています。
例2-2 最適化の後のコード
class A { B b; public void newMethod() { y = b.value; ...do stuff... sum = y + y; } } class B { int value; final int get() { return value; } }
最初のコードには、b.get()
メソッドへの2つのコールが含まれています。最適化の後、2つのメソッド・コールは1つのvariable-copy操作に最適化されます。つまり、最適化されたコードでは、クラスBのフィールド値を取得するためのメソッド・コールを実行する必要がありません。
最適化プロセスの各手順
表2-1は、例2-1のコードを最適化するためにJRockit JVMで実行されるタスクを示しています。
最適化プロセスは通常、Javaソース・コード・レベルでは発生しないので注意してください。ここで使用されるJavaソース・コードは、最適化プロセスを簡単に理解できるビューを提供しています。
表2-1 最適化プロセスの各手順
最適化の手順 | コードの変換 | コメント |
---|---|---|
元のコード |
public void newMethod() { y = b.get(); ...do stuff... z = b.get(); sum = y + z; } |
|
finalメソッドをインライン化 |
public void newMethod() { y = b.value; ...do stuff... z = b.value; sum = y + z; } |
// b.get() is replaced by b.value // latencies are reduced by accessing // b.value directly instead of using // a function call. |
余計なロードを削除 |
public void newMethod() {
y = b.value;
...do stuff...
z = y;
sum = y + z;
}
|
// z = b.value is replaced with // z = y so that latencies are // reduced by accessing local value // instead of b.value. |
伝播をコピー |
public void newMethod() { y = b.value; ...do stuff... y = y; sum = y + y; } |
// z = y is replaced by y = y because // there is no use for extra variable // z; the values of z and y are equal. |
不要なコードを削除 |
public void newMethod() { y = b.value; ...do stuff... sum = y + y; } |
// y = y is unnecessary and can be // eliminated. |