◀返回
使用設定檔引導最佳化來最佳化原生可執行檔
預設情況下,GraalVM Native Image 為 Java 應用程式提供快速啟動和較低的記憶體消耗,並以原生可執行檔的形式執行。您可以透過應用設定檔引導最佳化 (Profile-Guided Optimizations, PGO) 來進一步最佳化此原生可執行檔,以獲得額外的效能提升和更高的吞吐量。
透過 PGO,您可以預先收集設定檔資料,然後將其饋送給 native-image
工具,該工具將使用此資訊來最佳化原生應用程式的效能。一般工作流程如下:
- 透過將
--pgo-instrument
選項傳遞給native-image
,來建置檢測的原生可執行檔。 - 執行檢測的可執行檔以產生設定檔檔案。預設情況下,會在目前的工作目錄和應用程式關閉時產生 default.iprof 檔案。
- 建置最佳化的可執行檔。將會自動選取具有預設名稱和位置的設定檔檔案。或者,您可以透過指定檔案路徑將其傳遞給
native-image
建置器:--pgo=myprofile.iprof
。
您可以在執行檢測的原生可執行檔時,透過在執行時間傳遞 -XX:ProfilesDumpFile=YourFileName
選項來指定收集設定檔的位置。您也可以透過指定不同的檔案名稱來收集多個設定檔檔案,並在建置時將它們傳遞給 native-image
。
請注意,執行所有相關的應用程式程式碼路徑,並給予應用程式足夠的時間來收集設定檔,對於擁有完整的設定檔資訊以及最佳效能至關重要。
注意:PGO 在 GraalVM 社群版中不可用。
如需此主題的更多資訊,請參閱設定檔引導最佳化參考文件。
執行示範
在示範部分,您將執行使用 Java Streams API 實作查詢的 Java 應用程式。使用者應提供兩個整數引數:迭代次數和資料陣列的長度。應用程式會使用確定性的隨機種子建立資料集,並迭代 10 次。每次迭代所花費的時間及其校驗和會列印到主控台。
以下是要最佳化的串流表達式
Arrays.stream(persons)
.filter(p -> p.getEmployment() == Employment.EMPLOYED)
.filter(p -> p.getSalary() > 100_000)
.mapToInt(Person::getAge)
.filter(age -> age > 40)
.average()
.getAsDouble();
請按照以下步驟使用 PGO 建置最佳化的原生可執行檔。
先決條件
請確定您已安裝 GraalVM JDK。最簡單的入門方式是使用 SDKMAN!。如需其他安裝選項,請瀏覽下載區段。
- 將以下程式碼儲存到名為 Streams.java 的檔案中
import java.util.Arrays; import java.util.Random; public class Streams { static final double EMPLOYMENT_RATIO = 0.5; static final int MAX_AGE = 100; static final int MAX_SALARY = 200_000; public static void main(String[] args) { int iterations; int dataLength; try { iterations = Integer.valueOf(args[0]); dataLength = Integer.valueOf(args[1]); } catch (Throwable ex) { System.out.println("Expected 2 integer arguments: number of iterations, length of data array"); return; } Random random = new Random(42); Person[] persons = new Person[dataLength]; for (int i = 0; i < dataLength; i++) { persons[i] = new Person( random.nextDouble() >= EMPLOYMENT_RATIO ? Employment.EMPLOYED : Employment.UNEMPLOYED, random.nextInt(MAX_SALARY), random.nextInt(MAX_AGE)); } long totalTime = 0; for (int i = 1; i <= 20; i++) { long startTime = System.currentTimeMillis(); long checksum = benchmark(iterations, persons); long iterationTime = System.currentTimeMillis() - startTime; totalTime += iterationTime; System.out.println("Iteration " + i + " finished in " + iterationTime + " milliseconds with checksum " + Long.toHexString(checksum)); } System.out.println("TOTAL time: " + totalTime); } static long benchmark(int iterations, Person[] persons) { long checksum = 1; for (int i = 0; i < iterations; ++i) { double result = getValue(persons); checksum = checksum * 31 + (long) result; } return checksum; } public static double getValue(Person[] persons) { return Arrays.stream(persons) .filter(p -> p.getEmployment() == Employment.EMPLOYED) .filter(p -> p.getSalary() > 100_000) .mapToInt(Person::getAge) .filter(age -> age >= 40).average() .getAsDouble(); } } enum Employment { EMPLOYED, UNEMPLOYED } class Person { private final Employment employment; private final int age; private final int salary; public Person(Employment employment, int height, int age) { this.employment = employment; this.salary = height; this.age = age; } public int getSalary() { return salary; } public int getAge() { return age; } public Employment getEmployment() { return employment; } }
- 編譯應用程式
javac Streams.java
(選用) 執行示範應用程式,提供一些引數以觀察效能。
java Streams 100000 200
- 從類別檔案建置原生可執行檔,並執行它以比較效能
native-image Streams
會在目前的工作目錄中建立一個可執行檔案 streams。現在使用相同的引數執行它以查看效能
./streams 100000 200
此版本的程式預計會比在 GraalVM 或任何常規 JDK 上執行來得慢。
- 透過將
--pgo-instrument
選項傳遞給native-image
,來建置檢測的原生可執行檔native-image --pgo-instrument Streams
- 執行它以收集程式碼執行頻率設定檔
./streams 100000 20
請注意,您可以使用小得多的資料大小來進行設定檔。從此次執行收集的設定檔預設會儲存在 default.iprof 檔案中。
- 最後,建置最佳化的原生可執行檔。設定檔檔案具有預設名稱和位置,因此將會自動選取
native-image --pgo Streams
- 執行此最佳化的原生可執行檔,計時執行時間,以查看系統資源和 CPU 使用率
time ./streams 100000 200
您應獲得與程式的 Java 版本相當或更快的效能。例如,在具有 16 GB 記憶體和 8 個核心的電腦上,10 次迭代的
TOTAL time
從約 2200 毫秒減少到約 270 毫秒。
本指南示範如何最佳化原生可執行檔,以獲得額外的效能提升和更高的吞吐量。Oracle GraalVM 為建置原生可執行檔提供額外的優勢,例如設定檔引導最佳化 (PGO)。透過 PGO,您可以針對特定工作負載「訓練」您的應用程式,並顯著提升效能。