ホームページ  >  記事  >  Java  >  Graal の探索: Java 用の次世代 JIT コンパイル

Graal の探索: Java 用の次世代 JIT コンパイル

WBOY
WBOYオリジナル
2024-08-31 16:36:32819ブラウズ

Graal コンパイラーは、動的なジャストインタイム (JIT) コンパイル テクノロジにおける大幅な進歩です。 Java の優れたパフォーマンスの背後にある重要な要素として宣伝されている Java 仮想マシン (JVM) アーキテクチャ内の JIT コンパイルの役割と機能は、その複雑でかなり不透明な性質のため、多くの実務者を困惑させることがよくあります。

JITコンパイラとは何ですか?

javac コマンドを実行するか、IDE を使用すると、Java プログラムは Java ソース コードから JVM バイトコードに変換されます。これ
このプロセスは、Java プログラムのバイナリ表現を作成します。この形式は、元のソース コードよりもはるかにシンプルでコンパクトです。

ただし、コンピューターまたはサーバーにある従来のプロセッサは、JVM バイトコードを直接実行できません。これには、JVM がバイトコードを解釈する必要があります。

Exploring Graal: Next-Generation JIT Compilation for Java

図 1 – ジャストインタイム (JIT) コンパイラーの仕組み

インタプリタは、実際のプロセッサ上で実行されるネイティブ コードと比較してパフォーマンスが劣ることがよくあるため、JVM は実行時に別のコンパイラ (JIT コンパイラ) を呼び出すようになります。 JIT コンパイラーは、バイトコードをプロセッサーが直接実行できるマシンコードに変換します。この洗練されたコンパイラーは、さまざまな高度な最適化を実行して、高品質のマシンコードを生成します。

このバイトコードは中間層として機能し、異なるプロセッサ アーキテクチャを備えたさまざまなオペレーティング システム上で Java アプリケーションを実行できるようにします。 JVM 自体は、このバイトコードを命令ごとに解釈するソフトウェア プログラムです。

Graal JIT コンパイラー – Java で書かれています

JVM の OpenJDK 実装には、クライアント コンパイラー (C1) とサーバー コンパイラー (C2 または Opto) という 2 つの従来の JIT コンパイラーが含まれています。クライアント コンパイラーは、より高速な操作とあまり最適化されていないコード出力を実現するために最適化されており、長時間にわたる JIT コンパイルの一時停止によってユーザー エクスペリエンスが中断される可能性があるデスクトップ アプリケーションに最適です。逆に、サーバー コンパイラーは、高度に最適化されたコードの生成により多くの時間を費やすように設計されており、長時間実行されるサーバー アプリケーションに適しています。

2 つのコンパイラは、「階層化コンパイル」を通じて連携して使用できます。最初にコードは C1 を通じてコン​​パイルされ、実行頻度が追加のコンパイル時間に見合う場合は C2 が続きます。

C++ で開発された C2 は、その高性能特性にもかかわらず、固有の欠点があります。 C++ は安全ではない言語です。したがって、C2 モジュールのエラーにより VM 全体がクラッシュする可能性があります。継承された C++ コードの複雑さと厳格さにより、そのメンテナンスと拡張性も大きな課題となっています。

Graal に特有の、この JIT コンパイラーは Java で開発されています。コンパイラーの主な要件は、JVM バイトコードを受け入れてマシン コードを出力することです。これは、C や C++ などのシステム レベルの言語を必要としない高レベルの操作です。

Java で書かれた Graal にはいくつかの利点があります:

  • 安全性の向上: Java のガベージ コレクションと管理メモリのアプローチにより、JIT コンパイラ自体によるメモリ関連のクラッシュのリスクが排除されます。

  • メンテナンスと拡張が容易になりました: 開発者が Java コードベースを利用すると、JIT コンパイラの機能に貢献したり拡張したりすることがより容易になります。

  • 移植性: Java のプラットフォーム非依存性は、Graal JIT コンパイラーが Java 仮想マシンを備えた任意のプラットフォームで動作する可能性があることを意味します。

JVM コンパイラ インターフェイス (JVMCI)

JVM コンパイラー インターフェイス (JVMCI) は、JVM の革新的な機能および新しいインターフェイスです (JEP 243: https://openjdk.org/jeps/243)。
Java アノテーション処理 API と同様に、JVMCI ではカスタム Java JIT コンパイラの統合も可能です。

JVMCI インターフェースは、byte から byte[] までの純粋な関数で構成されます:

interface JVMCICompiler {

  byte[] compileMethod(byte[] bytecode);
}

これは、現実のシナリオの複雑さをすべて捉えているわけではありません。

実際のアプリケーションでは、コードのパフォーマンスをよりよく理解するために、ローカル変数の数、スタック サイズ、インタープリターでのプロファイリングから収集されたデータなどの追加情報が必要になることがよくあります。したがって、インターフェイスはより複雑な入力を受け取ります。単なるバイトコードの代わりに、CompilationRequest:
を受け入れます。

public interface JVMCICompiler {

  int INVOCATION_ENTRY_BCI = -1;

  CompilationRequestResult compileMethod(CompilationRequest request);
}

JVMCICompiler.java

CompilationRequest は、どの JavaMethod がコンパイル対象であるかなど、より包括的な情報と、コンパイラに必要なさらに多くのデータをカプセル化します。

CompilationRequest.java

This approach has the benefit of providing all necessary details to the custom JIT-compiler in a more organized and contextual manner. To create a new JIT-compiler for the JVM, one must implement the JVMCICompiler interface.

Ideal Graph

An aspect where Graal truly shines in terms of performing sophisticated code optimization is in its use of a unique data structure: the program-dependence-graph, or colloquially, an "Ideal Graph".

The program-dependence-graph is a directed graph that presents a visual representation of the dependencies between individual operations, essentially laying out the matrix of dependencies between different parts of your Java code.

Let's illustrate this concept with a simple example of adding two local variables, x and y. The program-dependence-graph for this operation in Graal's context would involve three nodes and two edges:

  • Nodes:

    • Load(x) and Load(y): These nodes represent the operations of loading the values of variables x and y from memory into registers within the processor.
    • Add: This node embodies the operation of adding the values loaded from x and y.
  • Edges:

    • Two edges would be drawn from the Load(x) and Load(y) nodes to the Add node. These directional paths convey the data flow. They signify that the values loaded from x and y are the inputs to the addition operation.
      +--------->+--------->+
      | Load(x)  | Load(y)  |
      +--------->+--------->+
                 |
                 v
              +-----+
              | Add |
              +-----+

In this illustration, the arrows represent the data flow between the nodes. The Load(x) and Load(y) nodes feed their loaded values into the Add node, which performs the addition operation. This visual representation helps Graal identify potential optimizations based on the dependencies between these operations.

This graph-based architecture provides the Graal compiler with a clear visible landscape of dependencies and scheduling in the code it compiles. The program-dependence-graph not only maps the flow of data and relationships between operations but also offers a canvas for Gaal to manipulate these relationships. Each node on the graph is a clear candidate for specific optimizations, while the edges indicate where alterations would propagate changes elsewhere in the code - both aspects influence how Graal optimizes your program's performance.

Visualizing and analyzing this graph can be achieved through a tool called the IdealGraphVisualizer, or IGV. This tool is invaluable in understanding the intricacies of Graal's code optimization capabilities. It allows you to pinpoint how specific parts of your code are being analyzed, modified, and optimized, providing valuable insights for further code enhancements.

Let's consider a simple Java program that performs a complex operation in a loop:

public class Demo {
 public static void main(String[] args) {
        for (int i = 0; i < 1_000_000; i++) {
            System.err.println(complexOperation(i, i + 2));
        }
    }

    public static int complexOperation(int a, int b) {
        return ((a + b)-a) / 2;
    }
}

When compiled with Graal, the Ideal Graph for this program would look something like this(Figure 2).

Exploring Graal: Next-Generation JIT Compilation for Java

Figure 2 – Graal Graphs

Therefore, along with its method level optimizations and overall code performance improvements, this graph-based representation constitutes the key to understanding the power of the Graal compiler in optimizing your Java applications

In Conclusion

The Graal JIT compiler represents a significant leap forward in Java performance optimization. Its unique characteristic of being written in Java itself offers a compelling alternative to traditional C-based compilers. This not only enhances safety and maintainability but also paves the way for a more dynamic and adaptable JIT compilation landscape.

The introduction of the JVM Compiler Interface (JVMCI) further amplifies this potential. By allowing the development of custom JIT compilers in Java, JVMCI opens doors for further experimentation and innovation. This could lead to the creation of specialized compilers targeting specific needs or architectures, ultimately pushing the boundaries of Java performance optimization.

In essence, Graal and JVMCI represent a paradigm shift in JIT compilation within the Java ecosystem. They lay the foundation for a future where JIT compilation can be customized, extended, and continuously improved, leading to even more performant and versatile Java applications.

以上がGraal の探索: Java 用の次世代 JIT コンパイルの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。