Android 8.0 ART 改善項目

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 版本中採用多種迴圈最佳化方式:

  • 界限檢查消除
    • 靜態:編譯時已證明範圍在界限內
    • 動態:執行階段測試可確保迴圈保持在界限內 (否則會取消最佳化)
  • 消除歸納變數
    • 移除感應器
    • 以封閉形式的運算式取代僅在迴圈後使用的歸納法 運算式
  • 在迴圈主體內消除無用程式碼,移除所有無用迴圈
  • 強度降低
  • 迴圈轉換:反轉、互換、分割、展開、單模等。
  • 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 中特定元素的數量成正比,也就是:

  • strings (one reference per DexFile::StringId),
  • 類型 (每個 DexFile::TypeId 各一個參照),
  • 方法 (每個 DexFile::MethodId 各有一個原生指標)、
  • 欄位 (每個 DexFile::FieldId 各有一個原生指標)。

這些陣列用於快速擷取先前解析的物件。在 Android 8.0 中,除了方法陣列以外,所有陣列都已移除。

翻譯人員表現

Android 7.0 版推出「mterp」後,解譯器效能大幅提升。mterp 是一種解譯器,採用以組合語言編寫的核心擷取/解碼/解譯機制。Mterp 是以快速 Dalvik 解譯器為模型,支援 arm、arm64、x86、x86_64、mips 和 mips64。就運算程式碼而言,Art 的 mterp 大致上與 Dalvik 的快速解譯器相當。不過,在某些情況下,速度可能會明顯變慢,甚至大幅變慢:

  1. 叫用效能。
  2. 字串操控,以及其他大量使用 Dalvik 中視為內建函式的方法。
  3. 堆疊記憶體用量較高。

Android 8.0 修正了這些問題。

更多內嵌

自 Android 6.0 起,ART 可在相同 DEX 檔案中內嵌任何呼叫,但只能內嵌不同 DEX 檔案中的葉節點方法。這項限制有兩個原因:

  1. 與相同 DEX 檔案的內嵌不同,從另一個 DEX 檔案內嵌需要使用該 DEX 檔案的 DEX 快取,而相同 DEX 檔案的內嵌則可重複使用呼叫端的 DEX 快取。編譯後的程式碼需要 DEX 快取,才能執行靜態呼叫、字串載入或類別載入等指令。
  2. 堆疊對映只會編碼目前 dex 檔案中的方法索引。

為解決這些限制,Android 8.0 採取了下列措施:

  1. 從已編譯的程式碼中移除 DEX 快取存取權 (另請參閱「移除 DEX 快取」一節)
  2. 擴充堆疊對應編碼。

同步處理功能改善

ART 團隊調整了 MonitorEnter/MonitorExit 程式碼路徑,並減少對 ARMv8 上傳統記憶體屏障的依賴,盡可能以較新的 (取得/釋放) 指令取而代之。

更快速的原生方法

您可以使用 @FastNative@CriticalNative 註解,更快地對 Java Native Interface (JNI) 進行原生呼叫。這些內建的 ART 執行階段最佳化功能可加快 JNI 轉換速度,並取代現已淘汰的 !bang JNI 標記。註解不會影響非原生方法,且僅適用於 bootclasspath 上的平台 Java 語言程式碼 (不會更新 Play 商店)。

@FastNative 註解支援非靜態方法。如果方法以參數或傳回值的形式存取 jobject,請使用這個方法。

@CriticalNative 註解提供更快速的方式來執行原生方法,但有以下限制:

  • 方法必須是靜態,不得有參數、傳回值或隱含 this 的物件。
  • 只有原始型別會傳遞至原生方法。
  • 原生方法不會在函式定義中使用 JNIEnvjclass 參數。
  • 方法必須向 RegisterNatives 註冊,而不是依賴動態 JNI 連結。

@FastNative 可將原生方法效能提升至 3 倍,@CriticalNative 則可提升至 5 倍。舉例來說,在 Nexus 6P 裝置上測得的 JNI 轉換如下:

Java Native Interface (JNI) 呼叫 執行時間 (奈秒)
一般 JNI 115
!bang JNI 60
@FastNative 35
@CriticalNative 25