概観
いつ、どのような状態で、コンパイラがクラスファイルをローカルインフラストラクチャに関連するバイナリマシンコードに変換するかは、コンパイルプロセス全体のバックエンドと見なすことができます。
したがって、2つのタイプがあります。事前コンパイラとジャストインタイムコンパイラです。
ジャストインタイムコンパイラ
Javaは最初にインタープリターを介して解釈および実行されます。メソッドまたはコードブロックが非常に頻繁に実行されていることが仮想マシンで検出されると、ホットコードと見なされます。実行中は、これらのコードがローカルマシンコードにコンパイルされ、それぞれが使用されます。コードの最適化
インタプリタとコンパイラ
ホットスポット仮想マシンには、インタープリターとコンパイラーが同時に含まれているため、プログラムをすばやく起動して実行する必要がある場合は、インタープリターが最初に役割を果たすため、コンパイル時間を節約し、すぐに実行できます。時間が経つにつれ、コンパイラーは徐々に有効になり、より多くのコードをローカルマシンコードにコンパイルし、インタープリターの中間的な損失を減らし、より高い実行効率を実現します
インタープリターはメモリーを節約し、コンパイラーは効率を改善します
インタープリターは、根本的な最適化中にコンパイラーのバックアップエスケープドアとして使用することもできます。つまり、コンパイラーは、確率に従って実行速度を上げるためにほとんどの最適化方法を選択する可能性がありますが、毎回正しくはありません。状態を説明するために実行を継続
組み込みの2つのジャストインタイムコンパイラC1(クライアント側コンパイラ)とC2(サーバー側コンパイラ)
階層モードの前に、インタープリターはコンパイラーの1つと直接連携し、仮想マシンは自身のバージョンとホストマシンのハードウェアパフォーマンスに応じて動作モードを自動的に選択します
一般的に混合モードを使用
階層化コンパイル
ジャストインタイムコンパイラはローカルコードをコンパイルしてプログラムの実行時間を消費し、高度な最適化を行ったコードはコンパイルする必要があるため、時間がかかります。プログラムの起動応答速度と実行効率のバランスをとるために、階層型コンパイルが使用されます
- レベル0、純粋な解釈の実行、およびインタープリターはパフォーマンス監視を開きません
- 最初の層は、C1を使用して、パフォーマンス監視を有効にすることなくバイトコードをローカルコードにコンパイルします。
- 2番目の層は、C1を使用してバイトコードをローカルコードにコンパイルし、メソッドとバックエンド統計およびその他の制限されたパフォーマンス監視のみを開くことです。
- レイヤー3では、C1を使用してバイトコードをローカルコードにコンパイルし、すべてのパフォーマンス監視をオンにします。
- 最初のレイヤーは、C2を使用してバイトコードをローカルコードにコンパイルし、すべてのパフォーマンスモニタリングをオンにすることです。パフォーマンスモニタリング情報に基づいて、信頼できない根本的な最適化もいくつか実行されます。
階層コンパイルを実装すると、インタープリターC1、C2が同時に動作し、ホットコードが複数回コンパイルされ、C1を使用してコンパイル速度が向上し、C2を使用してコンパイル品質が向上します。解釈と実行中に追加のコレクションを負担する必要はありません。パフォーマンス監視情報のタスク、およびC2が複雑度の高い最適化アルゴリズムを使用する場合、C1は最初に単純な最適化を使用して、コンパイル時間を増やすことができます。
コンパイルオブジェクトとトリガー条件
ホットコード:複数回呼び出されるメソッド、複数回実行されるループ本体
どちらの場合も、コンパイルされたオブジェクトはメソッド本体全体であり、個別のループ本体ではありません
後者の場合、実行エントリは少し異なります。実行エントリポイントのバイトコードシーケンス番号は、コンパイル中に渡され、コンパイルはメソッドの実行中に行われるため、スタック置換と呼ばれます。つまり、メソッドのスタックフレームはまだスタック上にあり、メソッドは交換されました
ホットスポット検出
- 仮想マシンは、サンプリングホットスポットの検出に基づいて、各スレッドの呼び出しスタックの最上部を定期的にチェックします。スタックの最上部にメソッドが頻繁に出現する場合、それはホットコードです。欠点は、メソッドの人気を正確に確認することが難しいことです
- カウンターベースのホットスポット検出に基づいて、仮想マシンは各メソッドのカウンターを作成し、メソッドの実行数をカウントします。比較的正確で厳密
HotSpot仮想マシンの実装
カウンターベースのホットスポット検出を使用して、メソッド呼び出しカウンターが準備され、エッジバックカウンター(ループ境界ジャンプバックを参照)カウンターのしきい値がオーバーフローすると、すぐにコンパイルがトリガーされます。
デフォルトでは、実行エンジンはコンパイルリクエストの完了を同期的に待機することはありません。代わりに、送信されたリクエストがジャストインタイムコンパイラーによってコンパイルされるまで、インタープリターに入り、解釈された方法でバイトコードを実行します。コンパイルが完了すると、メソッド呼び出しエントリはシステムによって変更されます。新しい値です。このメソッドが次に呼び出されたときに、コンパイルされたバージョンが使用されます
メソッド呼び出しカウンター
メソッド呼び出しカウンターは、メソッド呼び出しの絶対数ではなく、相対的な実行頻度、つまり一定期間内のメソッド呼び出しの数です。
一定期間以上、メソッド呼び出しの数がコンパイルのためにジャストインタイムコンパイラーに送信されるのに十分でない場合、メソッドの呼び出しカウンターは半分になります。このプロセスは、メソッド呼び出しカウンターの減衰と呼ばれます。この期間は半減期と呼ばれます
後方カウンター
メソッド内のループ本体コードの実行回数をカウントします。バイトコードでは、制御フローが後方にジャンプする命令はバックエッジと呼ばれます。目的は、スタック上で置換コンパイルをトリガーすることです。
しきい値を超えると、スタックに送信されますコンパイル要求を置き換え、ループバックカウンターの値をわずかに減らして、インタープリターでループの実行を継続し、コンパイラーがコンパイル結果を出力するのを待ちます。
ループバックカウンターはホットスポットの減衰をカウントせず、カウントはメソッドのループ実行の絶対数です
コンパイルプロセス
デフォルトの条件下では、それがメソッド呼び出しによって生成された標準のコンパイル要求であるか、スタック上の置換コンパイル要求であるかに関係なく、コンパイラーがコンパイルを完了する前に仮想マシンは解釈された方法でコードを実行し続け、コンパイルアクションはバックグラウンドコンパイルスレッドで実行されます進行中
クライアントコンパイラ
比較的シンプルで高速な3段階のコンパイラーであり、主な焦点はローカル最適化であり、時間のかかる多くのグローバル最適化手法を放棄します
- プラットフォームに依存しないフロントエンドは、バイトコードを高レベルの中間コード表現(HIR)に構築します
- プラットフォーム関連のバックエンドがHIRから低レベルの中間コード表現(LIR)を生成します
- プラットフォーム関連のバックエンドで線形スキャンアルゴリズムを使用してマシンコードを生成する
サーバー側コンパイラ
具体的には、サーバー側の一般的なアプリケーションシナリオ。これは高度な最適化の複雑さを許容できる高度なコンパイラです。従来の最適化操作のほとんどといくつかの不安定な予測的ラジカル最適化(分岐頻度予測など)を実行します
実際の戦闘:コンパイルされた結果も表示および分析
Java仮想マシンのコンパイルプロセスがユーザーとプログラムに対して完全に透過的であっても、
空のループは実際には最終的なネイティブコードでは実行されません。
先コンパイラ
事前コンパイルのメリットとデメリット
2つのブランチ
- プログラムの実行前にプログラムコードをマシンコードにコンパイルする静的変換作業。その値は、ジャストインタイムコンパイラの最大の弱点です。ジャストインタイムコンパイルは、プログラムの実行時間とコンピューティングリソースを消費します。したがって、静的コンパイルでは時間のかかる最適化が実行され、高品質のコードが生成されます。副作用は、インストールが遅いことです
- 元のジャストインタイムコンパイラーが実行時に実行する必要があるコンパイル作業を準備して保存し、次回コードを実行するときに直接ロードします。基本的に、Javaプログラムの起動時間を改善するためにインスタントコンパイラのキャッシュを高速化することと、最高のパフォーマンスを実現するためにウォームアップに一定の時間がかかるという問題があります。ジャストインタイムのコンパイルキャッシュと呼ぶことができます。欠点は、このプリコンパイル方法がターゲットマシンに関連しているだけでなく、HotSpot仮想マシンの動作パラメータにバインドされている必要があることです。
ジャストインタイムコンパイラの自然な利点
パフォーマンス分析ガイダンスの最適化
インタプリタまたはクライアント側コンパイラの実行中、パフォーマンス監視情報は継続的に収集されます。このような情報は通常、静的解析中には利用できません。または明確な固有のソリューションはありません。ただし、動的な操作中にそれらの設定を明確に確認でき、リソースを割り当てるためにホットコードを集中および最適化できます。
積極的な予測最適化
高確率の仮定に基づいて大胆に最適化でき、ミスが発生した場合は、低コストのコンパイラーまたはインタープリターにフォールバックして実行できます。
リンク時に最適化
Java言語は本質的に動的にリンクされています。クラスファイルは実行時に仮想マシンのメモリに読み込まれ、最適化されたコードがジャストインタイムコンパイラで生成されます
コンパイラー最適化手法
コンパイラの目的は、プログラムコードをネイティブマシンコードに変換するプロセスですが、出力コード最適化の品質は、コンパイラが優れているかどうかを判断するための鍵です。
インスタントコンパイラはコードを最適化し、コードの途中またはマシンコード上で変更します。 、Javaソース
コードの最適化されたコードの代わりに、効果は同じですが、多くのコードステートメントが省略されている場合、バイトコードとマシンコード命令の間のギャップが大きくなります。
メソッドのインライン化
これはコンパイラーの最も重要な最適化方法です。通常、最適化シーケンスの先頭に配置されます。
インライン化する方法はなく、他のほとんどの最適化は効果的に実行できません。これは、多くのメソッドの操作が意味のある
最適化動作である可能性があるためです。ターゲットメソッドのコードをそのまま呼び出しを開始したメソッドに「コピー」することですが、Java実装はかなり複雑です
目的
- メソッド呼び出しのコスト(メソッドバージョンの検索、スタックフレームの確立など)を削除します。
- 他の最適化のための優れた基盤を構築する
Java実装メソッドのインライン問題
一般に、プライベートメソッド、インスタンスコンストラクター、親メソッド、静的メソッド、最終メソッドのみがコンパイル時に解決されます。他のすべてのJavaメソッド呼び出しは、実行時にメソッドレシーバーの多態的な選択を実行する必要があり、メソッドレシーバーのバージョンが複数存在する場合があります。したがって、デフォルトのインスタンスメソッドは仮想メソッドです。
したがって、メソッドは実際の型に応じて動的に割り当てられる必要があり、実際の型はコード行が実際に実行された後に決定される必要があります。コンパイル時に完全に正確な結論を得るのは困難
です。Javaオブジェクトのデフォルトメソッドは仮想メソッドです。Javaはプログラマーに間接的に多くのプログラムロジックを実装する仮想メソッド
Java実装メソッドインラインメソッド
タイプ継承関係分析を使用して、現在ロードされているクラスのインターフェースおよび親クラス情報を判別します。このようにして、コンパイラーはインライン化するときに、状況に応じて異なる処理を行います。
- 非仮想メソッドの場合、直接インライン
- それが仮想メソッドであり、バージョンが1つしかない場合は、これだけであると想定されます。ただし、これは根本的な予測最適化であり、エスケープドアを予約する必要があります。つまり、仮定が正しくない場合の回避策です。継承関係の変更を引き起こす新しいクラスを後でロードする場合、コンパイルされたコードを破棄するか、解釈された状態に戻って実行を続けるか、再コンパイルする必要があります。
- 複数のバージョンがある場合は、インラインキャッシュが使用されます。インラインキャッシュは、ターゲットメソッドの通常のエントリの前に構築されるキャッシュです。メソッド呼び出しが発生する前は、インラインキャッシュステータスは空です。最初の呼び出しが行われると、キャッシュはメソッドレシーバーのバージョン情報を記録し、メソッドが呼び出されるたびに、受信するたびにレシーバーのバージョンを比較します。呼び出しメソッドの受信者は同じで、彼はモノモーフィックインラインキャッシュです。
したがって、ほとんどの場合、Java仮想マシンによって実行されるメソッドのインライン化は根本的な最適化であり、小さな確率のイベントがある場合は、エスケープドアを使用して解釈状態に戻り、再度実行されます。
エスケープ分析
最も最先端の最適化手法の1つは、他の最適化の基礎を提供する分析手法です。ただし、エスケープ計算のコストは非常に高く、効果が不安定になる可能性があります。
原理
オブジェクトの動的スコープを分析します。オブジェクトがメソッドで定義されると、他のメソッドに呼び出しパラメータとして渡されるなど、外部メソッドによって参照される場合があります。これはメソッドエスケープと呼ばれます。スレッドエスケープと呼ばれる、他のスレッドでアクセスできるインスタンス変数に値を割り当てるなど、外部スレッドからもアクセスできます。
オブジェクトがメソッドまたはスレッドの外にエスケープしないことを証明できる場合、またはエスケープの度合いが比較的低い場合は、次の手法を使用できます。
スタック上の割り当て
Javaは、ほとんどの場合、ヒープ上にオブジェクトを作成するためのメモリ領域を割り当てます。ヒープ内のオブジェクトは共有され、すべてのスレッドに表示されます。このオブジェクトへの参照を保持している限り、ヒープに格納されているオブジェクトデータにアクセスできます。
オブジェクトがスレッドにエスケープしないことが確実な場合は、オブジェクトにスタック上のメモリを割り当てさせることができます。メソッドエスケープをサポートできますが、スレッドエスケープはサポートしません
スカラー置換
データの一部を小さなデータに分解できない場合、それはスカラーです。(Int、long、参照など)
javaオブジェクトが分割され、使用されたメンバー変数がアクセスのために元の型に復元される場合、このプロセスはスカラー置換です。
エスケープ分析でオブジェクトがメソッドの外部で使用されず、オブジェクトが分割できることが証明される場合、オブジェクトは実際に実行されても作成されないことがありますが、そのいくつかのメンバー変数が作成されます。これはスタックへの割り当ての特別なケースであり、実装が簡単で、エスケープの要件が高くなります
同期除去
スレッドの同期は、比較的時間がかかる操作です。変数がスレッドをエスケープしない場合、読み取りと書き込みの競合は発生せず、変数に実装された同期手段を排除できます。
一般的な部分式の除去
古典的な言語に依存しない最適化手法の1つ。
式Eが以前に計算されており、E内のすべての変数値が以前から現在まで変更されていない場合は、Eを以前に計算された結果に置き換えることができます。
アレイ境界チェックの排除
古典的な言語関連の最適化手法の1つである
Java動的セキュリティ、配列要素システムへのアクセスは、上限と下限を自動的にチェックします。これは開発者にとって使いやすいですが、配列要素が読み書きされるたびに、暗黙的な条件判断操作が行われます。一種のパフォーマンスの負担。
配列アクセスがループで発生し、ループ変数が配列アクセスに使用されている場合、コンパイラーがループ変数の値の範囲がデータフロー分析を通じて配列の長さ内にあると判断できる場合、ループの上限と下限のチェックを排除できます。落とす
実際の戦闘:Graalコンパイラの深い理解
範囲チェックは開発者にとって使いやすいものですが、配列要素が読み取られたり書き込まれたりするたびに、暗黙的な条件判断操作が行われ、パフォーマンスの負担になります。
配列アクセスがループで発生し、ループ変数が配列アクセスに使用されている場合、コンパイラーがループ変数の値の範囲がデータフロー分析を通じて配列の長さ内にあると判断できる場合、ループの上限と下限のチェックを排除できます。落とす
実際の戦闘:Graalコンパイラの深い理解
Graal Compiler:ジャストインタイムコンパイラーと初期のコンパイラーの両方の最新の成果。高品質なコンパイル効率、高出力品質、早期コンパイル、ジャストインタイムコンパイルのサポートが期待されます。