設定檔導向最佳化

什麼是設定檔導向最佳化? #

即時 (JIT) 編譯器相較於預先 (AOT) 編譯器的一個優勢,在於它能夠分析應用程式的執行階段行為。例如,HotSpot 會追蹤 if 陳述式的每個分支被執行的次數。此資訊稱為「設定檔」,會傳遞至第二層 JIT 編譯器 (例如 Graal)。然後,第二層 JIT 編譯器會假設 if 陳述式將會繼續以相同的方式運作,並使用設定檔中的資訊來最佳化該陳述式。

AOT 編譯器通常沒有設定檔資訊,並且通常僅限於程式碼的靜態檢視。這表示,除非有啟發式方法,否則 AOT 編譯器會將每個 if 陳述式的每個分支視為在執行階段同樣可能發生;每個方法都與任何其他方法一樣有可能被叫用;並且每個迴圈重複相同的次數。這讓 AOT 編譯器處於劣勢 — 在沒有設定檔資訊的情況下,很難產生與 JIT 編譯器品質相同的機器碼。

設定檔導向最佳化 (PGO) 是一種將設定檔資訊帶給 AOT 編譯器的技術,以提高其在效能和大小方面的輸出品質。

注意:PGO 在 GraalVM 社群版中不可用。

什麼是設定檔#

設定檔是應用程式執行期間某些事件發生次數的摘要記錄。會根據哪些資訊對於編譯器做出更好的決策可能有用,來選擇事件。範例包括

  • 這個方法被呼叫了多少次?
  • 這個 if 陳述式採用 true 分支多少次?它採用 false 分支多少次?
  • 這個方法配置物件多少次?
  • String 值被傳遞到特定的 instanceof 檢查多少次?

我如何取得應用程式的設定檔? #

當在具有 JIT 編譯器的 JVM 上執行應用程式時,應用程式的設定檔是由執行階段環境處理,開發人員無需執行額外步驟。但是,建立設定檔會對正在設定檔化的應用程式效能增加執行時間和記憶體使用率的負擔。這會導致暖機問題:應用程式只有在應用程式程式碼被設定檔化並進行 JIT 編譯經過足夠時間後,才會達到可預測的峰值效能。對於長時間執行的應用程式,這種負擔通常會自行支付,稍後會產生效能提升。另一方面,對於生命週期短的應用程式和需要盡快以可預測的效能啟動的應用程式,這會適得其反。

收集 AOT 編譯應用程式的設定檔比較複雜,需要開發人員執行額外的步驟,但在最終應用程式中不會產生負擔。必須在觀察應用程式執行時收集設定檔。這通常是透過以特殊模式編譯應用程式來完成,該模式會將檢測程式碼插入到應用程式二進位檔中。檢測程式碼會遞增設定檔感興趣的事件的計數器。然後,包含檢測程式碼的二進位檔稱為檢測二進位檔,而新增這些計數器的過程稱為檢測

自然地,由於檢測程式碼的負擔,應用程式的檢測二進位檔不如預設二進位檔效能高,因此不建議在生產環境中執行它。但是,在檢測二進位檔上執行合成的代表性工作負載,可提供應用程式行為的代表性設定檔。在建置最佳化應用程式時,AOT 編譯器同時具有應用程式的靜態檢視和動態設定檔。因此,最佳化應用程式的效能優於預設的 AOT 編譯應用程式。

設定檔如何「引導」最佳化? #

在編譯期間,編譯器必須做出有關最佳化的決策。例如,在以下方法中,函式內嵌最佳化需要決定要內嵌哪些呼叫站點,以及不內嵌哪些呼叫站點。

private int run(String[] args) {
    if (args.length < 3) {
        return handleNotEnoughArguments(args);
    } else {
        return doActualWork(args);
    }
}

為了說明目的,假設內嵌最佳化對於可以產生的程式碼量有限制,因此只能內嵌其中一個呼叫。僅查看正在編譯的程式碼的靜態檢視時,doActualWork()handleNotEnoughArguments() 呼叫看起來幾乎沒有區別。在沒有任何啟發式方法的情況下,階段必須猜測哪個是更好的內嵌選擇。但是,做出不正確的選擇可能會導致程式碼效率較低。假設 run() 最常在執行階段以正確的參數數量呼叫,則內嵌 handleNotEnoughArguments 會增加編譯單元的程式碼大小,而不會產生任何效能優勢,因為大多數情況下仍然需要進行 doActualWork() 呼叫。

擁有應用程式的執行階段設定檔,可以為編譯器提供資料來區分呼叫。例如,如果執行階段設定檔將 if 條件記錄為 false 100 次,而 true 3 次,則應內嵌 doActualWork()。這是 PGO 的本質 — 在做出某些決策時,使用設定檔中的資訊為編譯器提供資料基礎。實際的決策和設定檔記錄的實際事件因階段而異,但前面的範例說明了一般概念。

請注意,PGO 期望在應用程式的檢測二進位檔上執行代表性工作負載。提供適得其反的設定檔 (記錄應用程式實際執行階段行為的完全相反設定檔) 會適得其反。對於上面的範例,這會導致使用以太少的參數呼叫 run() 方法的工作負載來執行檢測二進位檔,而實際的應用程式則不會。這會導致內嵌階段選擇內嵌 handleNotEnoughArguments,從而降低最佳化二進位檔的效能。

因此,目標是在盡可能與生產工作負載匹配的工作負載上收集設定檔。黃金標準是在檢測二進位檔上執行您預期在生產環境中執行的完全相同的工作負載。

如需更詳細的使用概述,請前往 設定檔導向最佳化的基本用法 文件。

延伸閱讀 #

與我們聯絡