診断ガイド

     前  次    目次     
コンテンツの開始位置

JIT コンパイルと最適化の概要

ここでは、Oracle JRockit JVM によるコード生成の基本について説明します。JIT コンパイルの概要と JVM によるコード最適化で高いパフォーマンスが実現される仕組みを説明します。この節の内容は以下のとおりです。

 


「ブラック ボックス」以外のもの

ユーザの立場から見ると、JRockit JVM は Java コードを高度に最適化されたマシン コードに「変換」するブラック ボックスでしかありません。JVM の一方の端で Java コードを入力すると、反対側の端から特定のプラットフォームのマシン コードが出力されます (図 2-1 を参照)。

図 2-1 ブラック ボックスとして見たときの JRockit JVM

ブラック ボックスとして見たときの JRockit JVM

ブラック ボックスのふたを外すと、コードを特定のオペレーティング システムに最適化する前に実行されるさまざまな操作が目に入ります。特定の操作、データ構造の変更および変換などが、コードが JVM を離れる前に実行されます (図 2-2 を参照)。

図 2-2 ブラック ボックスの内部

ブラック ボックスの内部

この節では、JVM を通過するときに Java アプリケーション コードに実際に加えられる操作について明らかにします。

 


JRockit JVM によりコードのコンパイル方法

JRockit JVM のコード ジェネレータは、Java アプリケーションのバックグラウンドで動作し、コードをベストの状態で動くように自動的に最適化します。コード ジェネレータの動作は、図 2-3 に示すように 3 段階に分かれています。

図 2-3 JRockit JVM における Java アプリケーションのコードの最適化
ブラック ボックスの内部

1. JRockit JVM が JIT コンパイルを実行する

コード生成の第 1 段階では、Just-In-Time (JIT) コンパイルを実行します。JIT コンパイルによって Java アプリケーションは起動して実行できるようになりますが、生成されるコードはプラットフォームに対して完全には最適化されません。JIT は実際には JVM 標準に含まれていませんが、Java に必須のコンポーネントです。理論上、JIT が使われるのは Java のメソッドが呼び出されたときで、JIT はそのメソッドのバイトコードをコンパイルしてネイティブなマシン コードに変換します。こうして実行直前にコンパイルされることから「just in time」と呼ばれます。

コンパイルされたメソッドについては、JRockit JVM はそれを解釈する代わりにコンパイル済みのコードを直接呼び出します。これにより、アプリケーションの実行が速くなります。ただし、実行の開始時には大量の新しいメソッドが実行されるので、JRockit JVM の実際の起動が他の JVM より遅くなる可能性があります。JIT を実行し、メソッドをコンパイルする処理に大きなオーバーヘッドがあるためです。したがって、JIT を使わずに実行した場合、JVM は速く起動できるのですが、実行はたいてい遅くなります。逆に、JIT を使って実行した場合、JRockit JVM の起動は遅くなり、実行は速くなります。これがある段階に達すると、アプリケーションの実行よりも JVM の起動に多くの時間がかかるような状況が生じます。

利用できる最適化機能をすべて適用して全メソッドを起動時にコンパイルすることも、起動時間にマイナスの影響を与えるため、望ましくありません。JIT コンパイルは、起動時に全メソッドを完全に最適化することはありません。

2. JRockit JVM がスレッドをモニタする

第 2 段階では JRockit JVM は、最適化の効果が最も期待できる関数を特定するために、低負荷の巧妙なサンプリング手法が用いられています。「サンプルのスレッド」が定期的に起動され、このスレッドが複数のアプリケーション スレッドの状態をチェックします。このスレッドは、各スレッドの実行内容を確認して実行履歴を残します。この情報はすべてのメソッドで追跡されます。あるメソッドが頻繁に使用されている (つまり「ホット」である) ことがわかると、そのメソッドは最適化の対象として指定されます。一般に、そのような最適化の機会はアプリケーションの実行の早い段階で発生し、実行が進むにつれて、その割合は小さくなります。

3. JRockit JVM が最適化を実行する

第 3 段階では、JVM は検出された最も使用頻度が高い (ホットな) メソッドの最適化を行います。この最適化はバックグラウンドで実行され、実行中のアプリケーションのパフォーマンスを損なうことはありません。

 


コード最適化を示す例

この例では、JRockit JVM が Java コードを最適化する方法を示します。ここで紹介する例はかなり短くて単純ですが、実際の Java コードの最適化の仕組みを把握することができます。ここでは紹介しませんが、Java アプリケーションの最適化にはさまざまな方法があります。

表 2-1 に、最適化前のコードと最適化後のコードを示します。それほど大きな違いはないように見えますが、最適化されたコードでは、クラス A を実行するたびにクラス B を実行する必要がありません。

表 2-1 クラスの最適化前と後の例
クラス A : 最適化前
クラス A : 最適化後
class A {
B b;
public void foo() {
y = b.get();
...do stuff...
z = b.get();
sum = y + z;
}
}
class B {
int value;
final int get() {
return value;
}
}
class A {
B b;
public void foo() {
y = b.value;
...do stuff...
sum = y + y;
}
}
class B {
int value;
final int get() {
return value;
}
}

クラス A を最適化するためのステップ

Oracle JRockit JVM のコード最適化処理は、最善の最適化に到達するために複数のステップで実行されます。表 2-1 の例は、最適化の前と後でメソッドがどのように変わるかを示しています。表 2-2 では、JVM による最適化のステップで行われる操作について、Java アプリケーション コードのレベルで説明します。ただし、JVM の複数の最適化には、アセンブラ コードのレベルで行われるものもあります。

表 2-2 最適化の各ステップ
最適化のステップ
コードの変換
コメント
元のコード
public void foo() {
y = b.get();
...do stuff...
z = b.get();
sum = y + z;
}
 
  1. final メソッドをインライン化
public void foo() {
y = b.value;
...do stuff...
z = b.value;
sum = y + z;
}

// 関数呼び出しの代わりに、
// b.value を直接評価することで
// 待ち時間が削減されるため、
// b.get() を b.value で置き換える。
  1. 余計なロードを削除
public  void foo() {
y = b.value;
...do stuff...
z = y;
sum = y + z;
}



// b.value の代わりにローカル値を
// 評価することで、待ち時間が
// 削減されるように、
// z = b.value を z = y で置き換える。
  1. 伝播をコピー
public  void foo() {
y = b.value;
...do stuff...
y = y;
sum = y + y;
}



// z と y の値は同じになるので、
// 別の変数 z を使っても意味がないため、
// z = y を y = y で置き換える。
  1. 不要なコードを削除
public  void foo() {
y = b.value;
...do stuff...
sum = y + y;
}



// y = y は不必要なので、削除できる。


  ページの先頭       前  次