web-dev-qa-db-ja.com

違いを理解する:従来のインタープリター、JITコンパイラー、JITインタープリター、AOTコンパイラー

従来のインタープリターであるJITコンパイラー、JITインタープリター、AOTコンパイラーの違いを理解しようとしています。

インタープリターは、何らかのコンピューター言語で命令を実行する単なるマシン(仮想または物理)です。その意味で、JVMはインタープリターであり、物理CPUはインタープリターです。

事前コンパイルは、コードを実行(解釈)する前にコードを特定の言語にコンパイルすることを意味します。

ただし、JITコンパイラーとJITインタープリターの正確な定義についてはわかりません。

私が読んだ定義によると、JITコンパイルは、コードを解釈する前に単純にコードjustをコンパイルしています。

基本的に、JITコンパイルはAOTコンパイルであり、実行(解釈)の直前に行われますか?

そして、JITインタープリターは、JITコンパイラーとインタープリターの両方を含み、解釈する直前にコードをコンパイル(JIT)するプログラムですか?

違いを明確にしてください。

135
Aviv Cohn

概観

言語のインタプリタ[〜#〜] x [〜#〜]はプログラム(またはマシン、または単に何らかのメカニズム一般)プログラムを実行するp言語で記述された[〜#〜] x [〜#〜]効果を実行し、結果を評価する[〜#〜] x [〜#〜]の仕様で規定されています。 CPUは通常、それぞれの命令セットのインタープリターですが、最新の高性能ワークステーションCPUは実際にはそれよりも複雑です。実際には、独自のプライベートインストラクションセットがあり、外部から見えるパブリックインストラクションセットを翻訳(コンパイル)または解釈します。

A compiler from X to Y is a program (or a machine, or just some kind of mechanism in general) that translates any program p from some language X into a semantically equivalent program p′ in some language Y in such a way that the semantics of the program are preserved, i.e. that interpreting p′ with an interpreter for Y will yield the same results and have the same effects as interpreting p with an interpreter for X. (Note that X and Y may be the same language.)

Ahead-of-Time(AOT)およびJust-in-Time(JIT)参照whenコンパイルが実行されます。これらの用語で参照される「時間」は「ランタイム」です。つまり、JITコンパイラはプログラム実行中、AOTコンパイラは実行前にプログラムをコンパイルします。これには、言語[〜#〜] x [〜#〜]から言語[〜#〜] y [〜#〜]へのJITコンパイラーが何らかの形で言語のインタプリタと一緒に動作する[〜#〜] y [〜#〜]でなければ、プログラムを実行する方法はありません。 (たとえば、JavaScriptをx86マシンコードにコンパイルするJITコンパイラは、x86 CPUなしでは意味がありません。実行中にプログラムをコンパイルしますが、x86 CPUがないとプログラムは実行されません。)

この区別はインタプリタには意味がないことに注意してください。インタプリタはプログラムを実行します。実行前にプログラムを実行するAOTインタープリターや、実行中にプログラムを実行するJITインタープリターの概念は、無意味です。

だから、私たちは持っています:

  • AOTコンパイラ:実行前にコンパイル
  • JITコンパイラ:実行中にコンパイル
  • インタープリター:実行

JITコンパイラ

JITコンパイラのファミリ内では、いつ正確にコンパイルするか、頻度、そしてどのような粒度で。

たとえば、MicrosoftのCLRのJITコンパイラは、コードを1回(それがロードされたとき)だけコンパイルし、一度にアセンブリ全体をコンパイルします。他のコンパイラーは、プログラムの実行中に情報を収集し、コードを何度か再コンパイルして、新しい情報が利用可能になり、最適化できるようになります。一部のJITコンパイラは、コードを最適化解除することさえできます。さて、なぜそんなことをしたいのかと自問するかもしれません。最適化を解除すると、実際には安全ではない可能性のある非常に積極的な最適化を実行できます。それは、too積極的であることが判明した場合は、もう一度取り消すことができますが、 、最適化を解除できないJITコンパイラでは、最初から積極的な最適化を実行できなかったでしょう。

JITコンパイラーは、コードの静的ユニットを一度にコンパイルすることができます(1つのモジュール、1つのクラス、1つの関数、1つのメソッド、…;これらは通常と呼ばれます)method-at-a-timeJIT、たとえば)またはそれらはtrace動的なコードの実行を見つけて動的traces(通常はループ)をコンパイルします(これらはtracingJITと呼ばれます)。

インタプリタとコンパイラを組み合わせる

インタプリタとコンパイラは、単一の言語実行エンジンに組み合わせることができます。これが行われる典型的なシナリオは2つあります。

[〜#〜] x [〜#〜]から[〜#〜] y [〜#〜]のAOTコンパイラをのインタプリタと組み合わせる[〜#〜] y [〜#〜]。ここでは、通常[〜#〜] x [〜#〜]は、人間が読みやすいように最適化された高レベルの言語ですが、[〜#〜] y [〜#〜]は、マシンによる解釈のために最適化されたコンパクトな言語(多くの場合、ある種のバイトコード)です。たとえば、CPython Python実行エンジンには、PythonソースコードをCPythonバイトコードにコンパイルするAOTコンパイラーとCPythonバイトコードを解釈するインタープリターがあります。同様に、YARV = Ruby実行エンジンには、RubyソースコードをYARVバイトコードにコンパイルするAOTコンパイラーとYARVバイトコードを解釈するインタープリターがあります。なぜそうしたいのですか?RubyとPythonはどちらも非常に高レベルな言語であり、やや複雑な言語です。そのため、まずそれらを解析しやすく解釈しやすい言語にコンパイルしてから解釈しますその言語。

インタプリタとコンパイラを組み合わせるもう1つの方法は、mixed-mode実行エンジンです。ここでは、same言語を一緒に実装する2つの「モード」を「混合」します。つまり、[〜#〜] x [ 〜#〜]および[〜#〜] x [〜#〜]から[〜#〜] y [〜#〜]のJITコンパイラ。 (つまり、ここでの違いは、上記のケースでは、コンパイラーがプログラムをコンパイルし、結果をインタープリターに供給する複数の「ステージ」があったことです。ここでは、2つの側が機能しています。 -by-side同じ言語で。)コンパイラーによってコンパイルされたコードは、インタープリターによって実行されるコードよりも速く実行される傾向がありますが、実際にコードをコンパイルするのには時間がかかります(そして特に、コードを大幅に最適化してreally高速に実行するには、lotの時間)。したがって、JITコンパイラーがコードのコンパイルでビジー状態になっているこの時間を橋渡しするために、インタープリターはすでにコードの実行を開始でき、JITのコンパイルが終了したら、コンパイルされたコードに実行を切り替えることができます。これは、コンパイルされたコードで可能な限り最高のパフォーマンスが得られることを意味しますが、コンパイルが完了するのを待つ必要はなく、アプリケーションはすぐに実行を開始します(可能な限り高速ではありません)。

これは実際には、混合モード実行エンジンの可能な最も単純なアプリケーションです。より興味深い可能性としては、たとえば、すぐにはコンパイルを開始せず、インタープリターを少しの間実行して、統計情報、プロファイリング情報、型情報、特定の条件付きブランチが実行される可能性に関する情報、呼び出されるメソッドなどを利用できます。ほとんどの場合、この最適化されたコードを生成できるように、この動的情報をコンパイラーにフィードします。これは、前述の最適化解除を実装する方法でもあります。最適化にあまりにも積極的であることが判明した場合は、コード(の一部)を破棄して、解釈に戻すことができます。たとえば、HotSpot JVMがこれを行います。これには、JVMバイトコードのインタープリターとJVMバイトコードのコンパイラーの両方が含まれています。 (実際には、実際には2つのコンパイラが含まれています!)

これらの2つのアプローチを組み合わせることも可能であり、実際に一般的です。最初の2つのフェーズは[〜#〜] x [〜#〜][〜にコンパイルするAOTコンパイラです。 #〜] y [〜#〜]と2番目のフェーズは、両方が[〜#〜] y [〜#〜]を解釈してコンパイルする混合モードエンジンです[ 〜#〜] y [〜#〜] to [〜#〜] z [〜#〜] Rubinius Ruby実行エンジンはこのように機能します。たとえば、RubyソースコードをRubiniusバイトコードにコンパイルするAOTコンパイラと、最初にRubiniusバイトコードを解釈し、情報が収集されると、最も頻繁に呼び出されるメソッドをネイティブマシンコードにコンパイルします。

混合モードの実行エンジンの場合にインタープリターが果たす役割、つまり高速起動の提供、および潜在的に情報の収集とフォールバック機能の提供は、代わりに2番目のJITコンパイラーが果たすことに注意してください。これは、たとえばV8の動作方法です。 V8は決して解釈せず、常にコンパイルします。最初のコンパイラは非常に高速で非常にスリムなコンパイラであり、非常に速く起動します。ただし、生成されるコードはそれほど高速ではありません。このコンパイラは、生成するコードにプロファイリングコードも挿入します。もう1つのコンパイラーは速度が遅く、より多くのメモリーを使用しますが、コードははるかに高速になり、最初のコンパイラーによってコンパイルされたコードを実行して収集されたプロファイリング情報を使用できます。

204
Jörg W Mittag