Android 8.0 版本的 Android 執行階段 (ART) 已大幅改善。下表列出裝置製造商可在 ART 中期待的強化功能。
並行壓縮垃圾收集器
如同 Google I/O 大會上所宣布,ART 在 Android 8.0 中提供全新的並行壓縮垃圾收集器 (GC)。這個收集器會在每次 GC 執行時和應用程式執行期間壓縮堆積,只會暫停一小段時間處理執行緒根。以下是這項功能的優點:
- GC 一律會壓縮堆積:與 Android 7.0 相比,堆積大小平均縮小 32%。
- 壓縮功能可啟用執行緒本機碰撞指標物件配置:配置速度比 Android 7.0 快上 70%。
- 與 Android 7.0 GC 相比,H2 基準測試的暫停時間縮短了 85%。
- 暫停時間不再隨著堆積大小而調整;應用程式應可使用大型堆積,而不必擔心發生卡頓情形。
- GC 實作詳細說明 - 讀取限制:
- 讀取障礙是指每個物件欄位讀取作業所需的少量工作。
- 這些項目會在編譯器中進行最佳化,但可能會減慢某些用途的速度。
迴圈最佳化
ART 在 Android 8.0 版本中採用多種迴圈最佳化技術:
- 邊界檢查消除
- 靜態:範圍在編譯時已證實在邊界內
- 動態:執行階段測試可確保迴圈維持在範圍內 (否則會 deopt)
- 消除誘導變數
- 移除無效的推導
- 將只在迴圈後方使用的推導法,替換為封閉式運算式
- 移除迴圈內部無效的程式碼,移除變成無效的整個迴圈
- 降低強度
- 迴圈轉換:反轉、交換、分割、展開、單模組等。
- SIMD 化 (也稱為向量化)
迴圈最佳化器位於 ART 編譯器中專屬的最佳化階段。大部分的迴圈最佳化作業都與其他地方的最佳化和簡化作業類似。有些最佳化方式會以比平常更複雜的方式重寫 CFG,因此會產生挑戰,因為大多數 CFG 公用程式 (請參閱 nodes.h) 都著重於建構 CFG,而非重寫 CFG。
類別階層分析
Android 8.0 中的 ART 會使用類別階層分析 (CHA),這是一種編譯器最佳化功能,可根據分析類別階層產生的資訊,將虛擬呼叫轉換為直接呼叫。虛擬呼叫的成本高昂,因為它們是圍繞 vtable 查詢實作,且需要幾個依附的載入作業。此外,虛擬呼叫也無法內嵌。
以下是相關強化功能的摘要:
- 動態單一實作方法狀態更新 - 在類別連結時間結束時,當 vtable 已填入時,ART 會逐一比較超類別的 vtable。
- 編譯器最佳化:編譯器會利用方法的單一實作資訊。如果方法 A.foo 已設定單一實作標記,編譯器就會將虛擬呼叫轉為直接呼叫,並進一步嘗試將直接呼叫內嵌為結果。
- 已編譯程式碼失效 - 在更新單一實作資訊的類別連結時間結束時,如果方法 A.foo 先前曾有單一實作,但該狀態現在已失效,則所有依據方法 A.foo 具有單一實作的假設而編譯的程式碼,都需要將已編譯程式碼設為失效。
- 反最佳化:針對堆疊中的即時編譯程式碼,系統會啟動反最佳化,強制將失效的編譯程式碼轉換為解譯器模式,以確保正確性。系統將使用新的非最佳化機制,這是同步和非同步非最佳化的混合型機制。
.oat 檔案中的內嵌快取
ART 現已採用內嵌快取,並針對擁有足夠資料的呼叫網站進行最佳化。內嵌快取功能會將額外的執行階段資訊記錄到設定檔,並使用這些資訊在編譯前加入動態最佳化功能。
Dexlayout
Dexlayout 是 Android 8.0 中推出的程式庫,可分析 DEX 檔案,並根據設定檔重新排序。Dexlayout 的目標是使用執行階段分析資訊,在裝置上執行閒置維護編譯作業時,重新排序 DEX 檔案的各個部分。將經常一起存取的 dex 檔案部分分組,可讓程式透過改善區域性,獲得更佳的記憶體存取模式,進而節省 RAM 並縮短啟動時間。
由於設定檔資訊目前僅會在應用程式執行後提供,因此 dexlayout 會在 dex2oat 的裝置端編譯作業期間,在閒置維護期間整合。
移除 DEX 快取
在 Android 7.0 之前,DexCache 物件擁有四個大型陣列,與 DexFile 中特定元素的數量成正比,分別為:
- 字串 (每個 DexFile::StringId 一個參照)
- 類型 (每個 DexFile::TypeId 一個參照)
- 方法 (每個 DexFile::MethodId 一個原生指標)
- 欄位 (每個 DexFile::FieldId 一個原生指標)。
這些陣列可用於快速擷取先前解析的物件。在 Android 8.0 中,除了方法陣列外,所有陣列都已移除。
解譯器效能
在 Android 7.0 版本中,我們推出了「mterp」,這是一種以組合語編寫的核心擷取/解碼/解譯機制為特色的轉譯器,大幅改善了轉譯器效能。Mterp 是以快速的 Dalvik 轉譯器為範本,支援 arm、arm64、x86、x86_64、mips 和 mips64。就運算程式碼而言,Art 的 mterp 大致可與 Dalvik 的快速轉譯器相提並論。不過,在某些情況下,速度可能會大幅降低:
- 叫用效能。
- 字串操作,以及其他大量使用在 Dalvik 中辨識為內建方法的使用者。
- 堆疊記憶體用量偏高。
Android 8.0 已解決這些問題。
更多內嵌功能
自 Android 6.0 起,ART 可在相同的 DEX 檔案中內嵌任何呼叫,但只能內嵌不同 DEX 檔案中的葉節點方法。這項限制有兩個原因:
- 從其他 DEX 檔案內嵌程式碼時,必須使用該 DEX 檔案的 DEX 快取,這與同一個 DEX 檔案內嵌程式碼不同,後者只會重複使用呼叫端的 DEX 快取。編譯程式碼時,需要使用 dex 快取來執行靜態呼叫、字串載入或類別載入等幾個指令。
- 堆疊對應項目只會對目前 dex 檔案中的一個方法索引進行編碼。
為解決這些限制,Android 8.0 會:
- 從編譯程式碼中移除 DEX 快取存取權 (請參閱「DEX 快取移除」一節)
- 擴充堆疊圖編碼。
改善同步處理功能
ART 團隊調整了 MonitorEnter/MonitorExit 程式碼路徑,並減少對 ARMv8 上傳統記憶體屏障的依賴,盡可能以較新的 (取得/釋放) 指令取代。
更快的原生方法
您可以使用 @FastNative
和 @CriticalNative
註解,加快對 Java Native Interface (JNI) 的原生呼叫。這些內建的 ART 執行階段最佳化功能可加快 JNI 轉換作業,並取代現已淘汰的 !bang JNI 符號。註解不會影響非原生方法,且僅適用於 bootclasspath
上的平台 Java 程式碼 (沒有 Play 商店更新)。
@FastNative
註解支援非靜態方法。如果方法以參數或傳回值的形式存取 jobject
,請使用此註解。
@CriticalNative
註解提供更快速的方式執行原生方法,但有下列限制:
-
方法必須為靜態,也就是沒有參數、傳回值或隱含
this
的物件。 - 只有原始類型會傳遞至原生方法。
-
原生方法不會在函式定義中使用
JNIEnv
和jclass
參數。 -
方法必須使用
RegisterNatives
註冊,而非依賴動態 JNI 連結。
@FastNative
可將原生方法效能提升至 3 倍,@CriticalNative
則可提升至 5 倍。舉例來說,在 Nexus 6P 裝置上測量 JNI 轉換的結果:
Java Native Interface (JNI) 叫用 | 執行時間 (以奈秒為單位) |
---|---|
一般 JNI | 115 |
!bang JNI | 60 |
@FastNative |
35 |
@CriticalNative |
25 |