Android 執行階段 (ART) 團隊將編譯時間縮短 18%,且不會影響編譯程式碼或任何尖峰記憶體迴歸。這項改善措施是 2025 年計畫的一部分,旨在縮短編譯時間,同時不影響記憶體用量或編譯程式碼的品質。
最佳化編譯時間速度對 ART 來說至關重要。舉例來說,即時 (JIT) 編譯會直接影響應用程式效率和整體裝置效能。編譯速度越快,最佳化作業生效前的等待時間就越短,使用者體驗也更流暢、更靈敏。此外,無論是 JIT 還是 AOT,編譯時間速度提升,代表編譯過程中的資源消耗減少,進而延長電池續航力並降低裝置溫度,尤其是在低階裝置上。
部分編譯時間速度改善項目已於 2025 年 6 月的 Android 版本推出,其餘項目將於年底的 Android 版本推出。此外,所有 Android 12 以上版本的使用者,都能透過主線更新取得這些改善項目。
最佳化編譯器
最佳化編譯器時,一律需要權衡取捨。速度並非憑空而來,必須有所取捨。我們為自己設定了非常明確且具挑戰性的目標:加快編譯器速度,但不能造成記憶體迴歸,更重要的是,不能降低產生的程式碼品質。如果編譯器速度變快,但應用程式執行速度變慢,就表示我們失敗了。
我們願意投入的資源,只有自己的開發時間,用來深入研究、調查,並找出符合這些嚴格標準的巧妙解決方案。接下來,我們將進一步說明如何找出需要改善的部分,以及如何針對各種問題找到合適的解決方案。
找出值得採用的最佳化建議
您必須先能夠評估指標,才能開始進行最佳化。否則,您就無法確定是否有所改善。幸運的是,只要採取一些預防措施,例如在變更前後使用同一部裝置進行測量,並確保裝置不會因過熱而降頻,編譯時間速度就會相當一致。此外,我們也提供編譯器統計資料等確定性評估,協助您瞭解幕後運作情形。
為了做出這些改善,我們犧牲了開發時間,因此希望盡可能加快疊代速度。也就是說,我們挑選了幾款具代表性的應用程式 (包括第一方應用程式、第三方應用程式和 Android 作業系統本身),用於設計解決方案的原型。之後,我們透過廣泛的手動和自動測試,驗證最終實作成果是否值得。
有了這組精選 APK,我們就能在本機觸發手動編譯、取得編譯設定檔,並使用 pprof 以視覺化方式呈現時間花費。
pprof 中的設定檔火焰圖範例
pprof 工具非常強大,可讓我們切片、篩選及排序資料,例如查看哪些編譯器階段或方法耗費最多時間。我們不會詳細說明 pprof 本身,只要知道長條越大,代表編譯時間越長即可。
其中一個檢視畫面是「由下而上」,可查看哪些方法耗費最多時間。在下圖中,我們可以看到名為 Kill 的方法,占編譯時間超過 1%。本文稍後也會討論其他幾種熱門方法。
由下往上查看設定檔
在最佳化編譯器中,有一個階段稱為全域值編號 (GVN)。您不必擔心它整體的運作方式,但相關部分是知道它有一個名為「Kill」的方法,會根據篩選器刪除一些節點。這項作業必須逐一檢查所有節點,因此相當耗時。我們發現,在某些情況下,無論當時有多少個節點處於運作狀態,我們都能預先知道檢查結果會是 false。在這種情況下,我們可以完全略過疊代,將疊代率從 1.023% 降至約 0.3%,並將 GVN 的執行階段縮短約 15%。
實施有價值的最佳化
我們介紹了如何評估及偵測時間花費,但這只是開始。下一步是瞭解如何縮短編譯時間。
通常,在上述「Kill」案例中,我們會查看如何疊代節點,並透過平行處理或改進演算法本身等方式加快疊代速度。事實上,我們一開始就是這麼做的,但當我們找不到任何可執行的操作時,我們才意識到「等一下…」,並發現解決方案是 (在某些情況下) 根本不疊代!進行這類最佳化時,很容易見樹不見林。
在其他情況下,我們使用了幾種不同的技術,包括:
- 使用經驗法則判斷最佳化作業是否無法產生有價值的結果,進而略過
- 使用額外的資料結構快取運算資料
- 變更目前的資料結構,加快速度
- 延遲運算結果,避免在某些情況下出現循環
- 使用正確的抽象化 - 不必要的功能可能會降低程式碼速度
- 避免透過多次載入追蹤常用指標
如何判斷是否值得進行最佳化?
這就是厲害的地方,你不需要。當您發現某個區域耗費大量編譯時間,並投入開發時間嘗試改善後,有時可能無法找到解決方案。也許是無計可施、實作時間太長、會大幅降低另一項指標、增加程式碼集複雜度等。雖然這篇網誌文章介紹了許多成功的最佳化案例,但其實還有無數個案例並未成功。
如果您也遇到類似情況,請試著估算要投入多少心力,才能改善指標。這表示:
- 根據已收集的指標或直覺進行估算
- 使用快速粗略的原型進行估算
- 導入解決方案。
別忘了評估解決方案的缺點。舉例來說,如果您要依賴額外的資料結構,您願意使用多少記憶體?
深入探索
廢話不多說,一起來看看我們實施了哪些變更。
我們調整並最佳化了 FindReferenceInfoOf 方法。這個方法會對向量執行線性搜尋,找出項目。我們更新了該資料結構,以便依指令 ID 建立索引,這樣 FindReferenceInfoOf 就會是 O(1),而不是 O(n)。此外,我們預先分配向量,避免調整大小。我們稍微增加了記憶體,因為我們必須新增一個額外欄位,用來計算在向量中插入的項目數量,但由於記憶體用量峰值並未增加,因此這只是小小的犧牲。這讓 LoadStoreAnalysis 階段的速度提升 34% 至 66%,進而縮短約 0.5% 至 1.8% 的編譯時間。
我們在多個地方使用自訂的 HashSet 實作。建立這個資料結構需要相當長的時間,我們也找到了原因。多年前,這個資料結構只用於少數使用大型 HashSet 的地方,並經過調整,針對該用途進行最佳化。不過,現在這個資料表的使用方向相反,只有少數項目,且生命週期很短。這表示我們建立這個龐大的 HashSet,但只用來處理幾個項目就捨棄,因此浪費了週期。這項變更將編譯時間縮短了約 1.3% 到 2%。此外,由於我們不再使用像以前那樣大的資料結構,記憶體用量也減少了約 0.5% 到 1%。
我們透過參照將資料結構傳遞至 lambda,避免複製資料結構,進而將編譯時間縮短約 0.5% 至 1%。這項問題在原始審查中遭到遺漏,並在程式碼集中存在多年。我們在 pprof 中查看設定檔,發現這些方法會建立及刪除大量資料結構,因此我們著手調查並進行最佳化。
我們快取了計算值,加快寫入編譯輸出的階段,使總編譯時間縮短約 1.3% 至 2.8%。可惜的是,額外的記錄工作太過繁重,自動化測試也提醒我們記憶體迴歸。後來,我們重新檢視相同的程式碼,並實作新版本,不僅解決記憶體迴歸問題,還將編譯時間進一步縮短約 0.5% 至 1.8%!在第二項變更中,我們必須重構並重新構思這個階段的運作方式,才能擺脫其中一個資料結構。
最佳化編譯器有一個階段會內嵌函式呼叫,以提升效能。為了選擇要內嵌的方法,我們會在進行任何運算前使用這兩種經驗法則,並在完成工作後、完成內嵌前進行最終檢查。如果其中任何一項偵測到內嵌不值得 (例如,會新增太多新指令),則我們不會內嵌方法呼叫。
我們將兩項檢查從「最終檢查」類別移至「啟發式」類別,以便在執行任何耗時的運算之前,預估內嵌是否會成功。由於這是預估值,因此並不完美,但我們已驗證新的啟發式演算法涵蓋 99.9% 的內嵌內容,且不會影響效能。其中一項新的啟發式演算法與所需的 DEX 暫存器有關 (改善約 0.2% 至 1.3%),另一項則與指令數量有關 (改善約 2%)。
我們在多個位置使用 BitVector 的自訂實作。我們將可調整大小的 BitVector 類別,替換為更簡單的 BitVectorView,用於特定固定大小的位元向量。這樣可以消除部分間接和執行階段範圍檢查,並加快位元向量物件的建構速度。
此外,BitVectorView 類別是以基礎儲存類型為範本 (而非一律使用 uint32_t 做為舊的 BitVector)。這樣一來,某些作業 (例如 Union()) 就能在 64 位元平台上同時處理兩倍的位元。編譯 Android 作業系統時,受影響函式的樣本總數減少超過 1%。這項作業是透過多項變更完成 [1、2、3、4、5、6]
如果我們詳細討論所有最佳化功能,可能要花上一整天!如果您想進一步最佳化,可以參考我們實作的其他變更:
- 新增簿記,將編譯時間縮短約 0.6% 至 1.6%。
- 延遲運算資料,盡可能避免循環。
- 重構程式碼,在不使用預先運算作業時略過。
- 如果可以輕鬆從其他位置取得分配器,請避免使用某些依附元件載入鏈。
- 另一個新增檢查以避免不必要工作的例子。
- 在暫存器配置器中,避免頻繁分支暫存器類型 (核心/FP)。
- 確保部分陣列在編譯時初始化。請勿依賴 clang 執行這項操作。
- 清除部分迴圈。使用範圍迴圈,clang 就能更有效地最佳化,因為它不需要因迴圈副作用而重新載入容器的內部指標。避免在迴圈中透過每個輸入的內嵌 `InputAt(.)` 呼叫虛擬函式 `HInstruction::GetInputRecords()`。
- 利用編譯器最佳化,避免使用訪客模式的 Accept() 函式。
結論
我們致力提升 ART 的編譯時間速度,並取得顯著成效,讓 Android 運作更流暢、更有效率,同時延長電池續航力並改善裝置散熱。我們努力找出並實作最佳化措施,證明瞭在不影響記憶體用量或程式碼品質的情況下,可以大幅縮短編譯時間。
我們使用 pprof 等工具進行剖析,並願意反覆測試,有時甚至會放棄成效不彰的方法。ART 團隊的共同努力不僅大幅縮短編譯時間,也為未來的進展奠定基礎。
上述所有改善項目都包含在 2025 年底的 Android 更新中,Android 12 以上版本則可透過 Mainline 更新取得。希望這篇深入探討最佳化程序的文章,能讓您深入瞭解編譯器工程的複雜性和成果!
-
產品最新消息今天,我們在 Android 開發人員高峰會上宣布,將廣泛提供多項工具和 API,旨在消除日常摩擦,讓您更靈活地建構出色的 Android 應用程式。
Matthew McCullough • 閱讀時間:4 分鐘 -
產品最新消息Android Studio Rabbit 2 穩定版已推出,可供您在正式環境中使用。此版本包含多項重要功能,可提升開發效率,例如「自備代理」、全新 XR 模擬器功能、LeakCanary 更新、全新代理技能管理工具、AI 程式碼審查等。
Matthew Warner • 閱讀時間:3 分鐘 -
產品最新消息如果應用程式指定即將推出的 Android 版本,將無法再透過 WRITE_CONTACTS 權限直接寫入聯絡人資料。
Roxanna Aliabadi Walker • 閱讀時間:6 分鐘
每週透過電子郵件接收 Android 開發人員的最新深入分析。