近期,Android 官方宣布了 Android Runtime 在編譯時間上實現了 18% 的顯著優化,同時不犧牲編譯代碼的質量,也沒有增加峰值記憶體使用,換句話說,這屬於是一個“速度提升 + 零損失”的優化成果。
實際上這個調整作為 2025 年的關鍵 KPI,目前已經實現了分階段 rollout:部分優化已經在 2025 年 6 月的 Android 發布中上線,其餘會在年底發布中完成,所有運行 Android 12 及以上版本的用戶都可以通過 mainline 更新得到這些改進。
“mainline 更新”( Project Mainline / Google Play System Updates) 是一種模組化更新機制,它支持 Google 透過 Google Play 直接更新 Android 系統的核心組件(如 Android Runtime、媒體框架、安全模組等),這個更新不需要用戶等待 OTA。
而這次優化更加具體的說就是:
並且,這些改進對於 JIT 和 AOT(預先編譯)都有用,當然這裡提到的 Android Runtime(ART)“編譯速度”不是你在本地用 Gradle 構建 APK 的速度,而是「設備端 ART 對 app 字節碼進行 AOT / JIT 編譯的速度」,一般而言針對 ART 內部的“編譯器執行速度”會有:
例如在 ART 裡有很多 pass(比如 GVN、某些資料流分析),每次都會遍歷 IR,即使當前方法根本不滿足優化條件也會完整跑一遍邏輯,這種行為帶來的開銷並沒有產生收益:
官方提到,在之前有一個名為全局值編號 (GVN) 的階段,這個過程裡它有一個名為
Kill的方法,這個方法會根據過濾器刪除一些節點,由於它需要遍歷所有節點並逐個檢查,因此非常耗時,而事實上無論當時有多少節點存活,其實都能預先知道檢查結果為 false,那麼在這種情況下完全可以跳過遍歷,從而將性能消耗從 1.023% 降低到約 0.3%,並將 GVN 的運行時間縮短約 15%。
其他的一些案例,包括有:
FindReferenceInfoOf 的查找優化LoadStoreAnalysis 階段的方法 FindReferenceInfoOf 原本使用線性搜索 O(n) 在向量中查找,而現在將資料結構改為以指令 ID 為索引,实现 O(1) 查找,並預分配向量以避免 resize, 從而在這個階段加速 34 - 66%,總編譯時間提升 0.5-1.8%,雖然增加了一個計數欄位,但峰值記憶體沒有增加。
代碼庫中使用了一個自定義的 HashSet,多年前是為了處理極少數的大型集合而優化的,但現在的用法變成了創建大量小型的、短生命周期的集合,所以本地調整實現以適應“小而短”的用法,減少創建和銷毀開銷,從而讓編譯時間提升 1.3-2%,且記憶體使用量反而下降了 0.5-1%。
還有通過將資料結構以引用方式傳遞給 lambda 表達式,避免了資料結構的複製,從而將編譯時間縮短了約 0.5% 到 1% ,這一點在最初的代碼審查中被忽略了,並在代碼庫中保留了多年:

編譯器為了性能會內聯函數,原本的流程是先計算大量資料,最後再做“最終檢查”(如指令數、寄存器需求)決定是否內聯,而現在將這些檢查從“計算後”移到了“計算前”作為啟發式規則(Heuristics),避免了大量無效計算,僅指令數檢查一項的移動就帶來了約 2% 的提升。
事實上官方在進行這些調整時也遇到了不少問題,因為即使你發現某個區域佔用了大量編譯時間,並且投入了開發時間嘗試改進,有時也找不到解決方案,當你修改了 A 問題後,自然而然就帶了 B 問題,比如:
HashSet),或者是因為代碼審查疏忽(將物件按值傳遞而不是按引用傳遞)針對這些問題,官方進行了一系列的調整嘗試:

pprof 工具生成 Flame Graph 和 Bottom-up 視圖,精確定位那些“隱形”的開銷(如頻繁的 Kill 方法或意外的物件拷貝) 

BitVectorView 替代可變長的 BitVector,並利用模板化實現讓 Union() 操作在 64 位平台上一次處理兩倍的位數除此之外還有:
InputAt(.) 為每個輸入調用虛函數 HInstruction::GetInputRecords()最後,看不懂不要緊,只需要知道它很厲害,並且還能讓 Android 12 以後的機器變得更快,在用戶端體現出來就是:
同時也展示了教科書級別的優化策略,不僅要看速度,更要看記憶體、穩定性、可維護性等綜合指標,所以官方這份報告不僅僅是技術公告,更是一份優秀的編譯器工程的案例研究。