設定檔引導最佳化 (PGO) 功能的運作方式

設定檔引導最佳化 (也稱為 PGO 或「pogo」) 是一種最佳化技術,可運用遊戲在實際運作時的行為方式資訊,進一步改善遊戲的最佳化版本。如此一來,不常執行的程式碼 (例如錯誤或邊緣案例) 就不會在程式碼的重要執行路徑中受到重視,因此可加快處理速度。

以視覺化方法概略呈現 PGO 運作方式的圖表

圖 1 PGO 的運作方式總覽。

如要使用 PGO,您必須先檢測版本,進而產生編譯器能夠運用的設定檔資料。接著執行該版本,並產生一或多個設定檔資料檔案,藉此執行程式碼。最後,請將這些檔案從裝置複製回來,並與編譯器搭配使用,以便運用擷取到的設定檔資訊將執行檔最佳化。

沒有 PGO 的最佳化版本如何運作

在不使用設定檔資料的情況下最佳化的版本,在決定如何產生最佳化程式碼時,會使用多項經驗法則。

有些經驗法則會由開發人員明確發出信號 (例如在 C++ 20 以上版本中),方法是使用 [[likely]][[unlikely]] 等分支方向提示。另一個例子則是使用 inline 關鍵字,甚至是 __forceinline (不過一般來說,使用前者較有彈性,效果也較佳)。根據預設,部分編譯器會假設分支的第一段 (也就是 if 陳述式,而非 else 部分) 是最有可能的分支。最佳化器可能也會根據程式碼的靜態分析,對其執行方式做出假設。不過,假設的範圍通常有限。

這些經驗法則的問題是,無法在所有情況下正確協助編譯器,即使採用詳盡的手動標記也是如此。因此,雖然產生的程式碼通常已順利完成最佳化,但效果可能仍不如編譯器在執行階段獲得更多行為資訊。

產生設定檔

如果您在「檢測」模式下啟用了 PGO,此時建構的執行檔會在每個程式碼區塊開頭 (例如函式開頭或各分支的開頭) 加入程式碼。此程式碼可用於追蹤執行程式碼每次進入的區塊數量,以供編譯器稍後用來產生最佳化程式碼。

除此之外,系統也會執行其他追蹤操作,例如追蹤某區塊的一般複製作業大小,這樣稍後就能產生該作業的內嵌快速版本。

遊戲執行某種代表性工作後,執行檔必須呼叫 __llvm_profile_write_file() 函式,將設定檔資料寫入裝置上的自訂位置。而在建構設定啟用 PGO 檢測之後,這個函式會自動連結至您的遊戲。

接著,寫入的設定檔資料檔案應複製回主機電腦,且最好與同一版本的其他設定檔存放在相同位置,以便搭配使用。

舉例來說,您可以修改遊戲程式碼,在目前的遊戲場景結束時呼叫 __llvm_profile_write_file()。接著,如要取得設定檔,請在開啟檢測模式的情況下建構遊戲,然後再將其部署至 Android 裝置。在執行過程中,系統會自動擷取設定檔資料,品質確保工程師會執行整個遊戲,針對各種情境進行演練 (或只說明一般測試已通過)。

將遊戲的各環節執行完畢後,您可以返回主選單,從而結束目前的遊戲場景,並寫入設定檔資料。

接著,您就可以使用指令碼將設定檔資料從測試裝置複製出來,並上傳至中央存放區,以供系統擷取留待日後使用。

合併設定檔資料

從裝置取得設定檔後,您需要將設定檔從檢測版本產生的設定檔資料檔案,轉換成編譯器能夠取用的形式。AGDE 會自動為您加入專案的任何設定檔資料檔案執行這項操作。

PGO 在設計上結合了多個檢測設定檔同時執行的結果,如果您在單一專案中有多個檔案,AGDE 也會自動為您完成這項作業。

合併設定檔資料集十分實用,舉例來說,假設您的實驗室有完整的品質確保工程師團隊,且每位工程師都在試玩遊戲的不同關卡。系統會記錄他們每個人的遊戲進度,然後根據遊戲的 PGO 檢測版本產生設定檔資料。只要合併設定檔,您就能將所有這些不同測試的結果結合起來,由於這可能會執行程式碼中截然不同的部分,因此可以產生更好的結果。

更棒的是,在執行縱向測試時,如果您在內部版本之間保留設定檔資料的副本,重新建構作業不一定會使舊設定檔資料失效。大致上來說,由於程式碼在各版本間相對穩定,因此舊有版本的設定檔資料仍可派上用場,而且不會立即過時。

產生設定檔引導最佳化版本

將設定檔資料新增至專案後,您可以在建構設定中啟用最佳化模式的 PGO,藉此使用設定檔資料建構執行檔。

這會指示編譯器的最佳化器在做出最佳化決策時,使用您先前擷取的設定檔資料。

使用設定檔引導最佳化功能的時機

PGO 不適合在開發初期,或在程式碼的每日疊代期間啟用。在開發過程中,您應專注於演算法和資料版面配置的最佳化,因為這樣可以帶來更多優勢。

在開發過程後期,也就是您發布版本時,PGO 才會登場。您可以把設定檔引導最佳化功能視為最上層,等到您自行花時間自行最佳化程式碼後,即可消除程式碼的最後一部分效能。

PGO 預期的效能改善幅度

這取決於許多因素,包括設定檔的全面性和過時程度,以及透過傳統最佳化版本讓程式碼達到最佳狀態的程度。

通常,以「非常」保守的方式估計,主執行緒中的 CPU 成本約會降低 5%,可能會看到不同的結果,

檢測開銷

PGO 的全方位檢測作業會自動產生,但並非完全免費。PGO 檢測作業的開銷,可能因程式碼集而有所不同。

設定檔引導檢測作業的效能成本

使用檢測版本時,您可能會遇到影格速率下降的情況。在某些情況下,取決於正常作業期間的 CPU 使用率接近 100%,這樣的下降幅度可能非常大,導致一般遊戲難以得逞。

大部分開發人員皆應聽從我們的建議,為自家遊戲建構半確定性的重播模式。這類功能可讓品質確保團隊在遊戲中的已知可重複開始位置 (例如遊戲進度存檔或特定測試關卡) 啟動遊戲,然後記錄他們的輸入內容。這項輸入內容是從測試版本記錄而來,無論處理個別影格所需的時間為何,皆可匯入 PGO 檢測版本中、播放,並產生實際設定檔資料,即使遊戲執行速度過慢,以致於無法播放也一樣。

這類功能也有其他主要優勢,例如可以讓測試人員事半功倍:一名測試人員可在裝置上記錄輸入內容,然後在多部不同類型的裝置上播放,以便進行冒煙測試。

這類重複播放系統在 Android 上有很大的優勢,因為此生態系統有許多裝置變化版本,而優點不僅止於此,此系統還能成為持續整合建構系統的核心部分,可讓您定期執行整晚的效能迴歸和冒煙測試。

這份記錄應在遊戲輸入機制中最適當的時間點記下使用者輸入內容 (可能不是直接的觸控螢幕事件,而是將「後果」記錄為指令)。這些輸入內容也應包含遊戲過程中單調遞增的影格數,因此在播放期間,重播機制可以等到適當影格時才觸發事件。

在播放模式中,遊戲應避免採取線上登入方式,也不應顯示廣告,而且應以固定時步 (依目標影格速率) 運作。請考慮停用 vsync。

遊戲中的所有內容 (例如粒子系統) 是否完全確定會重複並不重要,但相同的動作仍應在遊戲內提供相同的後果和結果,也就是說,遊戲過程應並無二致。

設定檔引導檢測作業的記憶體成本

PGO 檢測作業的記憶體開銷,會因所編譯的特定程式庫而有極大差異。在測試中,我們看到測試執行檔的整體大小增加了約 2.2 倍。這個增大幅度是因檢測程式碼區塊所需的額外程式碼,以及儲存計數器所需的空間而導致。上述測試並未涵蓋所有情況,您的使用體驗也可能因此而異。

更新或捨棄設定檔資料的時機

每當您對程式碼 (或遊戲內容) 做出重大變更後,都應更新設定檔。

這確切代表什麼涵義,則取決於您的建構環境和所處的開發階段。

如前文所述,您不應在主要建構環境變更時傳送設定檔資料;雖然這不會導致無法建構或中斷建構,但這樣做會降低使用 PGO 的效能優勢,因為只有極少的設定檔資料可在新的建構環境中使用。但請注意,在其他情況下,您的設定檔資料也可能會過時。

我們先假設您只會在準備發布的開發階段尾聲才使用 PGO,而在這之後,您可能會收集每週擷取資料,以便讓著重成效的工程師能確保在即將發布時不會發生任何意外問題。

隨著發布期到來、品質確保團隊每天測試並全面執行遊戲,上述情況會有所變動。在這個階段中,您可以每天都根據該資料產生設定檔,並用於制定日後的效能測試版本,同時調整自己的效能預算。

當您準備發布時,應該將計劃發布的建構版本鎖定起來,然後讓品質確保團隊執行該版本,以產生新的設定檔資料。然後,您就可以使用這項資料來建構,產生最終版的執行檔。

透過品質確保程序,這項經過最佳化的發布版本就能完成最終作業,確保能夠正常發布。