web-dev-qa-db-ja.com

Java JITを使用した方が、マシンコードへのコンパイルよりも高速なのはなぜですか?)

Javaを高速にするにはJITを使用する必要があると聞きました。これは解釈と比較すると完全に理にかなっていますが、なぜ高速Javaコードを生成する事前コンパイラを作成できないのでしょうか。私はgcjについて知っていますが、その出力は通常、たとえばHotspotよりも高速だとは思いません。

これを困難にする言語について何かありますか?私はそれがこれらのことだけに帰着すると思います:

  • 反射
  • クラスローディング

何が欠けていますか?これらの機能を使用しない場合、Javaコードをネイティブマシンコードに1回コンパイルして完了できますか?

48
Adam Goode

AOTコンパイラの真のキラーは次のとおりです。

Class.forName(...)

これは、ランタイムについてのみ利用可能な情報があるため、[〜#〜] all [〜#〜] JavaプログラムをカバーするAOTコンパイラを作成できないことを意味します。プログラムの特性ただし、Javaのサブセットで実行できます。これは、gcjが実行すると私が信じていることです。

別の典型的な例は、JITがgetX()などのメソッドを、安全であることが判明した場合に呼び出し側のメソッドに直接インライン化し、プログラマーが明示的に支援しなくても、適切であれば元に戻すことができることです。メソッドはfinalです。 JITは、実行中のプログラムでは特定のメソッドがオーバーライドされていないため、このインスタンスではfinalとして処理できることを確認できます。これは、次の呼び出しでは異なる場合があります。

JITコンパイラーは、マシンコードが実行される正確なマシン上で生成されるため、より高速になる可能性があります。これは、JITが最適化されたコードを発行するために利用できる最良の情報を持っていることを意味します。

バイトコードをマシンコードにプリコンパイルする場合、コンパイラはターゲットマシン用に最適化できず、ビルドマシンのみを最適化します。

37
Andrew Hare

James Gosling によって与えられた興味深い答えを本 プログラミングの首謀者 に貼り付けます。

まあ、私は事実上、Javaの世界に2つのコンパイラがあると言ったと聞きました。 Javaバイトコード用のコンパイラーがあり、基本的にすべてを再コンパイルするJITがあります。恐ろしい最適化はすべてJITにあります。

ジェームス:その通りです。最近では、本当に優れたCおよびC++コンパイラーをほぼ常に打ち負かしています。動的コンパイラーに行くと、コンパイラーが最後に実行されるときに2つの利点があります。 1つは、実行しているチップセットを正確に把握していることです。人々がCコードの一部をコンパイルしているときは、汎用のx86アーキテクチャの一種で実行するためにコンパイルしなければならないことがよくあります。あなたが手に入れるバイナリーのほとんどは、それらのいずれかのために特にうまく調整されていません。 Mozillaの最新コピーをダウンロードすると、ほとんどすべてのIntelアーキテクチャCPUで動作します。 Linuxバイナリはほぼ1つです。これはかなり汎用的で、GCCでコンパイルされていますが、これは非常に優れたCコンパイラではありません。

HotSpotを実行すると、実行中のチップセットが正確にわかります。キャッシュの仕組みを正確に把握しています。メモリ階層がどのように機能するかを正確に把握しています。すべてのパイプラインインターロックがCPUでどのように機能するかを正確に把握しています。それはこのチップが持っている命令セット拡張を知っています。使用しているマシンを正確に最適化します。次に、残りの半分は、実行中のアプリケーションを実際に認識していることです。重要なものを知る統計を得ることができます。 Cコンパイラではできないことをインライン化できます。 Javaの世界でインライン化されるものは、かなり驚くべきものです。次に、ストレージ管理が最新のガベージコレクタで機能する方法に取り組みます。最新のガベージコレクターを使用すると、ストレージの割り当てが非常に高速になります。

Masterminds of Programming

29
Edwin Dalorzo

JavaのJITコンパイラーも遅延と適応性があります。

怠惰な

怠惰なので、プログラム全体をコンパイルするのではなく、メソッドに到達したときにのみメソッドをコンパイルします(プログラムの一部を使用しない場合に非常に役立ちます)。クラスローディングは実際にはまだ出ていないクラスを無視することを可能にすることでJITをより速くするのを助けます。

適応

アダプティブであるため、最初にマシンコードの迅速でダーティなバージョンを出力し、その方法が頻繁に使用される場合にのみ、戻ってスルージョブを実行します。

23
Luke Quinane

結局のところ、より多くの情報を持っていることはより良い最適化を可能にするという事実に要約されます。この場合、JITには、コードが実行されている実際のマシンに関する詳細情報があります(Andrewが述べたように)。また、コンパイル中には利用できない多くのランタイム情報も含まれています。

11
Tal Pressman

理論的には、JITコンパイラにはAOTよりも利点があります十分な時間と利用可能な計算リソースがある場合。たとえば、大量のRAMを備えたマルチプロセッササーバーでエンタープライズアプリを何日も何ヶ月も実行している場合、JITコンパイラー can は、どのAOTコンパイラーよりも優れたコードを生成します。

現在、デスクトップアプリを使用している場合、高速起動や初期応答時間(AOTが最適)のようなものがより重要になり、さらにコンピューターには最も高度な最適化のための十分なリソースがない可能性があります。

また、リソースが不足している組み込みシステムがある場合、JITはAOTに対抗する機会がありません。

しかし、上記はすべて理論でした。実際には、そのような高度なJITコンパイラーの作成は、まともなAOTコンパイラーよりもはるかに複雑です。 実用的な証拠はどうですか?

6
Dmitry Leskov

仮想メソッドの境界を越えてインライン化し、効率的なインターフェイスディスパッチを実行するJavaの機能には、コンパイル前にランタイム分析が必要です。つまり、JITが必要です。すべてのメソッドは仮想であり、インターフェースは「どこでも」使用されるため、大きな違いが生まれます。

6
Sam Harwell

JITは、実行時にしか認識できないいくつかの条件を識別して排除できます。代表的な例は、最近のVMが使用する仮想呼び出しの排除です。たとえば、JVMがinvokevirtualまたはinvokeinterface命令を見つけたときに、呼び出されたメソッドをオーバーライドするクラスが1つだけロードされている場合、VMは実際にその仮想呼び出しを静的にできるため、インライン化できます。一方、Cプログラムの場合、関数ポインターは常に関数ポインターであり、その呼び出しは(一般的には)インライン化できません。

以下は、JVMが仮想呼び出しをインライン化できる状況です。

_interface I { 
    I INSTANCE = Boolean.getBoolean("someCondition")? new A() : new B();
    void doIt(); 
}
class A implements I { 
    void doIt(){ ... } 
}
class B implements I { 
    void doIt(){ ... } 
}
// later...
I.INSTANCE.doIt();
_

他の場所でAまたはBインスタンスを作成せず、someConditiontrueに設定されているとすると、JVMはdoIt()は常に_A.doIt_を意味するため、メソッドテーブルの検索を回避して、呼び出しをインライン化できます。 JITされていない環境での同様の構成は傾斜できません。

6
gustafc

公式のJavaコンパイラがJITコンパイラであるという事実は、これの大部分です。Java用のマシンコードコンパイラと比較して、JVMの最適化にどれだけの時間が費やされていますか?

2
Brendan Long

Dimitry Leskovはまさにここです。

上記のすべては、JITを高速化できる理論であり、すべてのシナリオを実装することはほとんど不可能です。さらに、x86_64 CPUには少数の異なる命令セットしかないため、現在のCPUのすべての命令セットを対象とすることで得ることはほとんどありません。ネイティブコードでパフォーマンスが重要なアプリケーションを構築するときは、常にx86_64とSSE4.2をターゲットにするというルールに従います。 Javaの基本的な構造は多くの制限を引き起こしています。JNIはそれがどれほど非効率的であるかを示すのに役立ちます。JITはこれを全体的に高速化することでこれをシュガーコーティングするだけです。デフォルトではすべての関数が仮想であるという事実に加えて、C++などではなく、実行時にクラス型を使用します。実行時にクラスオブジェクトをロードする必要がないため、C++はパフォーマンスに関して大きな利点があります。メモリに割り当てられるのはすべてのデータブロックであり、要求されたときにのみ初期化されます。つまり、C++には実行時にクラス型がありません。 Javaクラスはテンプレートだけではなく実際のオブジェクトです。関係がないため、GCに進みません。Java文字列も動的を使用するため低速です毎回プールテーブルで文字列検索を実行するためにランタイムを必要とする文字列プーリング。これらの多くは、Javaが最初に高速に構築されていなかったため、その基本はほとんどのネイティブ言語(主にC/C++)は、無駄のない、つまりメモリやリソースの無駄がないように特別に構築されています。Javaの最初の数バージョンは、実際には非常に遅く、変数に不要なメタデータが大量にあり、メモリに無駄が多いため、今日のように、AIT言語よりも高速なコードを生成できるJITは理論上残っています。

レイジーJITを実行するためにJITが追跡する必要のあるすべての作業について考え、関数が呼び出されるたびにカウンターをインクリメントし、呼び出された回数を確認します。 JITの実行には長い時間がかかります。私の目のトレードオフはそれだけの価値はありません。これはPCだけです

Raspberryやその他の組み込みデバイスでJavaを実行しようとしたことがありますか?絶対にひどいパフォーマンスです。RaspberryでのJavaFX?機能さえしていません... JavaそしてそのJITは非常にそれが宣伝するすべてのことや人々が盲目的にそれについて吐き出した理論に会うのは遠い。

0