Graal 編譯器是動態即時 (JIT) 編譯技術的根本性飛躍。 JIT 編譯在Java 虛擬機器(JVM) 架構中的作用和功能被譽為Java 令人印象深刻的性能背後的一個重要因素,由於其複雜且相當不透明的性質,常常讓許多從業者感到困惑。
當您執行 javac 命令或使用 IDE 時,您的 Java 程式將從 Java 原始程式碼轉換為 JVM 字節碼。這個
該過程創建 Java 程式的二進位表示 - 一種比原始原始程式碼更簡單、更緊湊的格式。
但是,電腦或伺服器中的經典處理器無法直接執行 JVM 位元組碼。這需要 JVM 來解釋字節碼。
圖 1 – 即時 (JIT) 編譯器的工作原理
與在實際處理器上執行的本機程式碼相比,解釋器的效能通常較差,這促使 JVM 在執行時間呼叫另一個編譯器 - JIT 編譯器。 JIT 編譯器將字節碼轉換為處理器可以直接運作的機器碼。這個複雜的編譯器執行一系列高級最佳化來產生高品質的機器碼。
此字節碼充當中間層,使 Java 應用程式能夠在具有不同處理器架構的各種作業系統上運行。 JVM本身就是一個軟體程序,它逐條指令地解釋這個字節碼。
JVM 的 OpenJDK 實作包含兩個傳統的 JIT 編譯器 - 客戶端編譯器 (C1) 和伺服器編譯器(C2 或 Opto)。客戶端編譯器針對更快的操作和較少優化的程式碼輸出進行了最佳化,使其成為桌面應用程式的理想選擇,因為長時間的 JIT 編譯暫停可能會中斷使用者體驗。相反,伺服器編譯器旨在花費更多時間來產生高度最佳化的程式碼,使其適合長時間運行的伺服器應用程式。
兩個編譯器可以透過「分層編譯」串聯使用。最初,程式碼透過 C1 編譯,如果執行頻率證明額外的編譯時間合理,則隨後透過 C2 編譯。
C2 採用 C++ 開發,儘管具有高效能特性,但也有固有的缺點。 C++是一種不安全的語言;因此,C2 模組中的錯誤可能會導致整個虛擬機器崩潰。繼承的C++程式碼的複雜性和僵化也導致其維護性和可擴展性成為重大挑戰。
Graal 獨有的,這個 JIT 編譯器是用 Java 開發的。編譯器的主要要求是接受 JVM 字節碼並輸出機器碼 - 一種不需要 C 或 C++ 等系統級語言的高階操作。
用 Java 寫的 Graal 有幾個優點:
提高安全性:Java 的垃圾收集和託管記憶體方法消除了 JIT 編譯器本身與記憶體相關的崩潰風險。
更容易維護和擴展:Java 程式碼庫更容易開發人員貢獻和擴展 JIT 編譯器的功能。
可移植性:Java 的平台獨立性意味著 Graal JIT 編譯器可以在任何具有 Java 虛擬機的平台上工作。
JVM 編譯器介面 (JVMCI) 是 JVM 中的一項創新功能和新介面 (JEP 243:https://openjdk.org/jeps/243)。
與 Java 註解處理 API 非常相似,JVMCI 也允許整合自訂 Java JIT 編譯器。
JVMCI 介麵包含一個從 byte 到 byte[] 的純函數:
這並沒有捕捉到現實生活場景的全部複雜性。
在實際應用中,我們經常需要額外的信息,例如局部變數的數量、堆疊大小以及從解釋器中分析收集的數據,以更好地了解程式碼的執行情況。因此,該介面需要更複雜的輸入。它不只接受字節碼,也接受 CompilationRequest:
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.
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:
Edges:
+--------->+--------->+ | 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).
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
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中文網其他相關文章!