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)。舉例來說,在 64 位元平台上,這項功能可讓 Union() 等作業一次處理的位元數增加一倍。編譯 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 以上版本則可透過主線更新取得。希望這次深入探索最佳化程序,能讓您進一步瞭解編譯器工程的複雜性與好處!
繼續閱讀
-
產品新訊
每位開發人員的 AI 工作流程和需求都不盡相同,因此選擇 AI 輔助開發的方式非常重要。我們在 1 月推出這項功能,讓您選擇任何本機或遠端 AI 模型,為 Android Studio 中的 AI 功能提供支援
Matthew Warner • 閱讀時間:2 分鐘
-
產品新訊
Android Studio Panda 3 現已推出穩定版,可用於正式工作環境。本次發布內容提供更多 AI 輔助工作流程的控制和自訂選項,讓您更輕鬆地建構優質 Android 應用程式。
Matt Dyor • 3 分鐘可讀完
-
產品新訊
Google 致力於將最強大的 AI 模型直接帶進你的 Android 裝置。我們很高興宣布推出最新最先進的開放模型:Gemma 4。
Caren Chang, David Chou • 3 分鐘可讀完
隨時掌握最新消息
每週透過電子郵件接收最新的 Android 開發洞察資訊。