実行について説明しますか?コンパイルして実行しますか?ジャストインタイムコンパイル?コンパイル前とコンパイル後を簡単に区別できます

コンパイルの理解

「えーと、昨日バグを修正しました。コンパイルして問題がないか確認してください。」その後、ソフトウェア部長がお茶を一口飲んだ。「監督、あなたはとても素晴らしいです!...」。部下が目を崇拝していたので、監督はそもそもコードを書いていたことを思い出さずにはいられなかったが、「コンパイル」の仕方すら知らなかった小さな白人だった。

コンパイルを起動します。私たちの最初の理解は次のとおりです。プログラムは最初に記述されたコードを読み取り、基本的な文法エラーがあるかどうかを判断します。コンパイルが渡されると、実行モードに入ります。そして、学習の深化と理解の深化により、コンパイルの理解はもはやこれに限定されることはありません。

image.png

Javaテクノロジのコンテキストで「コンパイラ」について話すことは、実際にはコンテキストのない非常にあいまいな表現です。彼はフロントエンドコンパイラ(javacを介して.javaファイルを.classファイルに変換するプロセス)を参照する場合があります。私たちの通常の開発は「ソフトウェアのコンパイル」を指します。実際、これはフロントエンドコンパイラを指します。この問題は完全にJVMに任されており、通常、開発についてはほとんど気にしないため、バックエンドコンパイラは無視されることがよくあります。

いわゆるコンパイルの意味は、実際にはプログラムの世界の翻訳です。フロントエンドコンパイルとは、既知の.javaファイルをJVMによって認識される.classファイルに変換する必要があることを意味します。しかし、コンピュータはあなたの.classファイルを認識しません。したがって、JVMは、認識した.classファイルを関連するバイナリマシンコードに変換する必要があります。これは、いわゆるバックエンドコンパイルです。

最後に、コンパイルのより具体的な概念があります。

バックエンドのコンパイルと最適化

バイトコードがプログラムの中間式形式と見なされる場合、コンパイラがいつどの状態でクラスファイルをコンピュータ用の別のバイナリマシンコードに変換するかに関係なく、バックエンドコンパイルプロセスと見なすことができます。

バックエンドのコンパイルには、主にジャストインタイムとアヘッドオブタイムが含まれます。以下では、これら2つの形式をそれぞれ紹介します。

解釈と実行

解釈と実行は、コンパイルと実行の前に配置する必要があります。事故がないため、ほとんどの場合、処理手順を説明して実行します。

インタプリタを介して、コードが実行されると、保存せずに1つずつマシンコードに変換されます。インタプリタメソッドは非常に非効率的です。実行する前に、バイトコードをマシンコードに変換する必要があります。(説明と実行を同時に)。ただし、プログラム内のプリコンパイル済みプログラムを除いて、コードは最初の解釈と実行から始まります。

コンパイルして実行する

特定の条件下では、JVM自体がインクの解釈と実行が無限であると感じます。とにかく、この時点でJVMワーカーは、このコードはとにかくマシンコードに変換されることが多いので、事前にこれらのコードをマシンコードにコンパイルし、コードの実行時にマシンコードを直接読み取るだけだと考えました。

これにより、コンパイルと実行が行われました。特定のシナリオまたは特定の構成を通じて、仮想マシンはコードの一部をマシンコードにコンパイルできます。マシンコードにコンパイルした後、これまで実行するたびに、マシンコードで直接実行することで運用効率を向上させています。

ジャストインタイムコンパイル(JIT)

プログラムに事前コンパイルがないと仮定すると、プログラムは最初から解釈および実行されるだけです。

しかし、それを想像してみましょう。私たちが英語の先生だとしましょう。私たちは生徒に英語の単語の発音の仕方を教えます。1回、2回…この生徒は100回目と1000回目はまだ読み方がわかりません。少しイライラしていませんか?はい、仮想マシンも煩わしいです(正確には、仮想マシンの開発者はこの問題を発見しました)。したがって、ホットコードの概念が提案されます。

プログラムが常に高周波プログラムブロックを繰り返し実行する場合、この時点でホットコードと判断されます。したがって、繰り返し実行率が非常に高いコードは、毎回翻訳するのではなく、一度翻訳して、翻訳されたコンテンツを毎回読むだけです。

ホットスポットコードをローカルプラットフォームに関連するマシンコードにコンパイルし、メモリに保存します。コンパイルと実行はオフラインで一度だけコンパイルする必要がありますが、解釈と実行は毎回コンパイルする必要があるため、コンパイルと実行の操作効率は高くなります。

HopSpot仮想マシンは、解釈の実行とジャストインタイムコンパイルの利点組み合わせハイブリッドモードを採用していますこれは、あろう最初のバイトコードの解釈および実行を実行し、その後コンパイルホットスポットコードされる繰り返しで実行するための手段として、この方法インスタントコンパイル。

ジャストインタイムコンパイルは、プログラムが第28法に準拠している、つまり、コードの20%がコンピューターリソースの80%を占めるという仮定に基づいています。

使用頻度の低いコードのほとんどは、マシンコードにコンパイルするのに時間を費やす必要はありませんが、解釈された実行モードで実行されます。一方、ごく一部しか占有しないホットコードの場合は、コンパイルできます。機械語に、理想的な動作速度に達しました。

 

理論的には、ジャストインタイムコンパイル後のJavaプログラムの実行効率は、C ++プログラムの実行効率を超える可能性があります。これは、静的コンパイル(すべて最初にマシンコードに変換される)と比較して、ジャストインタイムコンパイルには実行時にプログラムに関する情報があり(プログラムは実行時に再度最適化できる)、これに基づいて対応する決定を行うことができるためです。情報の最適化、より良いピークパフォーマンス。
OracleのHotSpotVMには、C ++で実装された2つのJITコンパイラC1とC2が付属しています。

C1およびC2コンパイラの概要

ジャストインタイムコンパイルと事前コンパイルの前の説明の後。今、大まかな考えがあるはずです。コンパイラの目的はプログラムコードをローカルマシンコードに変換することですが、マシンコードを正常に変換できるかどうかは問題ではありません。出力コードの最適化品質は、コンパイラが優れているかどうかを判断するための鍵です。

C1(クライアントコンパイラ)

クライアント側のコンパイラは.classファイルを保守的に変換しますが、非常に高速です。

そして、なぜそれがこの機能を伴うのですか?これは、C1コンパイラが根本的な最適化手法を適用しないためです。これらの最適化には、時間のかかるコード分析が伴うことが多いためです。

 

C2(サーバーコンパイラ)

サーバー側のコードコンパイラは、複雑度の高い最適化アルゴリズムを使用します。ラジカル最適化手法が採用されることが多いため、実行速度は遅くなりますが、最適化されたコード実行効率は高くなります。

C2は私たちの人工知能として想像することができます。運用期間中のプログラムの行動習慣に応じて、予測性の最適化が行われます。この最適化により、より効率的なコードをコンパイルできます。

Java 7より前は、ユーザーは自分のアプリケーションシナリオに応じて適切なJITコンパイラを選択する必要があります。たとえば、C1は高い起動パフォーマンスを好むGUIクライアントプログラムに使用され、C2は高いピークパフォーマンスを好むサーバー側プログラムに使用されます。

 

インタプリタとコンパイラの混合使用の重要性

インタープリターとコンパイラーには、それぞれ独自の利点があります。

1.プログラムを迅速に開始する必要がある場合、インタプリタが最初に役割を果たすことができるため、コンパイルと実行の時間を節約でき、すぐに実行できます。

(どちらもマシンコードに変換されますが、コンパイラがコードを最適化して保存する必要があるため、必然的に時間がかかります)

2.プログラムが一定期間実行された後、コンパイラは徐々に有効になります。ますます多くのホットコードを置く

マシンコードに変換されます。これにより、多くの通訳者の時間消費を削減し、効率を向上させることができます。(時間の味を変えるスペースも少しあります)

3.プログラム実行環境のメモリリソース制限が大きい場合、インタプリタ実行を使用してメモリを節約できます。それ以外の場合は、

翻訳者は効率を改善します。

4.同時に、インタープリターは、コンパイラーの根本的な最適化後のバックアップ「エスケープドア」としても使用できます(状況が実行された場合、HotSpot

仮想マシンは、「エスケープドア」の役割として機能するために、根本的な最適化なしでクライアント側コンパイラも使用します。なぜあなたは脱出ドアが必要なのですか?実行の過程で、コンパイラはいくつかのシーンを認識し、経験に基づいてスピードアップのために最適化する可能性があるためです。ただし、これらのシナリオが正しいとは限りません。現時点では、根本的な最適化の仮説は成り立たないでしょう。たとえば、新しいクラスがロードされると、クラスの継承構造が変更され、「まれなトラップ」が発生します。この時点で、最適化を解除することで、コンパイルおよび実行されたコードを再解釈して実行できます。これは、HotSpot仮想マシンがインタープリターおよびコンパイラーと共存する仮想マシンである理由も示しています。

実装の詳細のインタプリタとコンパイラの層状コンパイル

すべてのJava仮想マシンが、インタプリタとコンパイラが共存する実行中のアーキテクチャを採用しているわけではありませんが。しかし、現在の主流の商用Java仮想マシン。たとえば、HotSpot、OpenJ9などには、インタープリターとコンパイラーが内部に含まれています。

ジャストインタイムコンパイラはこのコードをコンパイルするため、プログラムの実行に時間がかかります。一般に、最適化されたコードがコンパイルされるほど、時間がかかります。また、より最適化されたコードをコンパイルする場合、インタープリターはコンパイラーのパフォーマンス監視情報を収集する必要がある場合があります。インタープリターはコンパイラーを支援するためにエクスペリエンスを分離するため、その効率も影響を受けます。プログラムの起動応答速度と操作効率の最適なバランスを実現するために、HotSpotはサブシステムをコンパイルするときに階層化コンパイルの機能を追加しました。これらには以下が含まれます:

レベル0:インタープリターの解釈と実行
レベル1:C1コンパイル、プロファイリングなし(パフォーマンス監視機能)
レベル2:C1コンパイル、メソッドとループのバックエッジ実行時間のプロファイリングのみ((パフォーマンス監視機能))
レベル3:C1コンパイル、ただし、レベル2のプロファイリングには、ブランチ(ブランチジャンプバイトコード用)とレシーバータイプ(メンバーメソッド呼び出しまたはチェックキャスト、instnaceof、aastoreバイトコードなどのクラス検出用)プロファイリング
レベル4:C2コンパイルも含まれます。

ここで説明するように、プロファイリングとは、プログラムの実行中にプログラムの実行ステータスを反映できるデータの収集を指します。ここで収集されたデータは、プログラムのプロファイルと呼ばれます。

 

1.通常の状況では、メソッドは最初に解釈および実行され(レベル0)、次にC1によってコンパイルされ(レベル3)、次にプロファイルデータを使用してC2によってコンパイルされます(レベル4)
。2。コンパイルされたオブジェクトが非常に単純な場合、仮想マシンは、C1を介したコンパイルとC2を介したコンパイルに違いはないと考え、プロファイリングコードを挿入せずにC1によって直接コンパイルされます(レベル1)
3。C1がビジーの場合、インタープリターがプロファイリングをトリガーし、メソッドは次のようになります。 C2によって直接コンパイルされます
。4。C2がビジーの場合、メソッドは最初にC1によってコンパイルされ、プロファイリングが少なくなり(レベル2)、実行効率が高くなります(レベル3よりも30%高くなります)。

通常の状況では、C2コードの実行効率はC1コードの実行効率よりも30%以上高くなります。ただし、C1コードの3つの状態の場合、実行効率の高いものから低いものへの順序は、1レベル> 2レベル> 3レベルです。

レイヤー1のパフォーマンスはレイヤー2のパフォーマンスよりもわずかに高く、レイヤー2のパフォーマンスはレイヤー3のパフォーマンスよりも30%高くなっています。これは、プロファイリングが多いほど、パフォーマンスのオーバーヘッドが増えるためです。

階層コンパイルを実装すると、インタプリタ、クライアント側コンパイラ、サーバー側コンパイラが同時に動作します。ホットコードは複数回コンパイルされる場合があります。クライアント側コンパイラを使用してコンパイル速度を上げ、サーバー側を使用します。コンパイラー。より良いコンパイル品質を得るために、解釈および実行中にパフォーマンス監視情報を収集する追加のタスクを実行する必要はありません。サーバー側が高度に複雑な最適化アルゴリズムをコンパイルして使用する場合、クライアント側コンパイラーは最初に単純なものを使用できます。そのための最適化より多くのコンパイル時間を求めて戦います。

ホットコード判定

動作中、ジャストインタイムコンパイラによってコンパイルされるターゲットは「ホットコード」です。ここでの「ホットコード」には次のものが含まれます。

1.複数回呼び出されるメソッド

2.複数回実行されるループ本体

特定のコードがホットコードであるかどうか、およびリアルタイムコンパイルをトリガーする必要があるかどうかを知るために、この動作は「ホットスポットコード検出」(ホットスポットコード検出)と呼ばれます。現在、2つの主流の方法があります。

(1)サンプルベースのホットスポットコード検出:特定の(またはいくつかの)メソッドがスタックの一番上に頻繁に表示されることがわかった場合、各スレッドの呼び出しスタックの一番上を定期的にチェックします(スタックは実行されるメソッドです)、このメソッドは「ホットメソッド」です。利点は、効率的であり、メソッド呼び出しの関係を簡単に取得できることです(呼び出しスタックを展開するだけです)。欠点は、精度が十分でなく、スレッドのブロックや外部要因の影響を受けやすいことです。

(2)カウンターベースのホットスポットコード検出:メソッドごとにカウンターを作成し(コードブロックも含む)、メソッドの実行回数をカウントします。実行回数が一定のしきい値を超えると、「ホットスポットメソッド」と見なされます。 。不利な点は、それがより面倒であり、メソッドごとにカウンターを維持する必要があり、呼び出し関係を直接取得できないことです。利点は、より厳密で正確なことです。

 

メソッド呼び出しカウンター

メソッドが呼び出された回数をカウントするために使用されます。一定の回数に達すると、ホットコードと判断できます。

メソッドが呼び出されると、仮想マシンは最初に、メソッドにジャストインタイムでコンパイルされたバージョンがあるかどうかを確認し、存在する場合は、コンパイルされたネイティブコードを優先的に使用して実行します。コンパイルされたバージョンがない場合は、メソッド呼び出しカウンタ値を1つインクリメントし、メソッド呼び出しカウンタと裏面カウンタ値合計がメソッド呼び出しカウンタしきい値を超えているかどうかを判断します。しきい値を超えると、このメソッドのコードコンパイル要求がジャストインタイムコンパイラに送信されます。このプロセスを次の図に示します。

image.png

特定の制限時間を超えても、メソッド呼び出しの数がジャストインタイムコンパイラに送信するのに十分でない場合、メソッド呼び出しカウンタは半分になります。このプロセスは、メソッドのカウンタ減衰と呼ばれます。この期間は、メソッドのカウンター半減期(Counter Half Life Time)と呼ばれます。仮想マシンパラメータ-XXを使用できます。-UseCounterDecayを使用して、熱減衰をオフにします。さらに、-XX:CounterHalfLifeTimeパラメーターを使用して、半減期のサイクル時間を秒単位で設定できます。

バックツーエッジカウンター

ループ本体コードの数をカウントするために使用します。一定の回数に達すると、ホットコードと判断できます。(For、whileなど)。

インタプリタがバックサイド命令を検出すると、最初に実行可能コードフラグメントにジャストインタイムのコンパイル済みバージョンがあるかどうかを確認し、存在する場合は、コンパイル済みのネイティブコードを優先的に使用して実行します。コンパイルされたバージョンがない場合は、戻りカウンタの値を1つ増やし、メソッド呼び出しカウンタと戻りカウンタの値の合計がメソッド呼び出しカウンタのしきい値を超えているかどうかを判断します。しきい値を超えると、スタック置換コンパイル要求が送信され、バックサイドカウンターの値がわずかに減少して、インタープリターでループを実行し続け、コンパイラーがコンパイル結果を出力するのを待ちます。このプロセスを次の図に示します。

image.png

メソッドカウンタとは異なり、エッジバックカウンタには半分の減衰プロセスがありません

 

おすすめ

転載: blog.csdn.net/weixin_47184173/article/details/109706816