Android 8.0 ART 改進

Android 運行時 (ART) 在 Android 8.0 版本中得到了顯著改進。下面的清單總結了設備製造商可以在 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 實作細節 - 讀取屏障:
    • 讀取屏障是為每個物件欄位讀取完成的少量工作。
    • 這些在編譯器中進行了最佳化,但可能會減慢某些用例的速度。

循環優化

Android 8.0 版本中的 ART 採用了多種循環優化:

  • 邊界檢查消除
    • 靜態:範圍在編譯時證明在範圍內
    • 動態:運行時測試確保循環保持在範圍內(否則取消優化)
  • 歸納變數消除
    • 消除死感應
    • 將僅在循環之後使用的歸納法替換為閉式表達式
  • 消除循環體內的死程式碼,刪除整個死循環
  • 強度降低
  • 循環變換:反轉、交換、分割、展開、單模等。
  • SIMD 化(也稱為向量化)

循環優化器駐留在 ART 編譯器中自己的最佳化通道中。大多數循環優化與其他地方的優化和簡化類似。一些以比通常更複雜的方式重寫 CFG 的最佳化會帶來挑戰,因為大多數 CFG 實用程式(請參閱nodes.h)專注於建立 CFG,而不是重寫 CFG。

類別層次結構分析

Android 8.0 中的 ART 使用類別層次結構分析 (CHA),這是一種編譯器最佳化,可根據分析類別層次結構產生的資訊將虛擬呼叫虛擬化為直接呼叫。虛擬呼叫非常昂貴,因為它們是圍繞 vtable 查找實現的,並且需要一些相關負載。虛擬呼叫也不能內聯。

以下是相關增強功能的摘要:

  • 動態單實作方法狀態更新 - 在類別連結時間結束時,當 vtable 已填入時,ART 會與超類別的 vtable 進行逐條目比較。
  • 編譯器最佳化 - 編譯器將利用方法的單一實作資訊。如果方法 A.foo 設定了單實現標誌,編譯器會將虛擬調用虛擬化為直接調用,並進一步嘗試內聯直接調用作為結果。
  • 編譯程式碼失效 - 同樣在類別連結時間結束時更新單實現資訊時,如果方法 A.foo 以前具有單實現但該狀態現在無效,則所有依賴於方法 A.foo 的假設的編譯程式碼。foo 具有單一實現需要使其編譯的程式碼無效。
  • 去最佳化 - 對於堆疊上的即時編譯程式碼,將啟動去最佳化以強制無效的編譯程式碼進入解釋器模式以確保正確性。將使用一種新的去優化機制,它是同步和非同步去優化的混合。

.oat 檔案中的內聯緩存

ART 現在採用內聯快取並優化存在足夠資料的呼叫站點。內聯快取功能將額外的運行時資訊記錄到設定檔中,並使用它來為提前編譯添加動態最佳化。

德克斯佈局

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中,除了methods數組之外的所有數組都已刪除。

口譯員表現

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 快取來執行靜態呼叫、字串載入或類別載入等指令。
  2. 堆疊映射僅對目前 dex 檔案中的方法索引進行編碼。

為了解決這些限制,Android 8.0:

  1. 從編譯程式碼中刪除 dex 快取存取(另請參閱“Dex 快取刪除”部分)
  2. 擴展堆疊映射編碼。

同步改進

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

更快的本機方法

使用@FastNative@CriticalNative註解可以更快地呼叫 Java 本機介面 (JNI)。這些內建的 ART 運行時優化加速了 JNI 轉換並取代了現已棄用的!bang JNI表示法。這些註解對非本機方法沒有影響,並且僅適用於bootclasspath上的平台 Java 語言程式碼(無 Play 商店更新)。

@FastNative註解支援非靜態方法。如果方法將jobject作為參數或傳回值來訪問,請使用此選項。

@CriticalNative註解提供了一種更快的方式來運行本機方法,但具有以下限制:

  • 方法必須是靜態的-沒有參數物件、回傳值或隱式this
  • 僅原始類型會傳遞給本機方法。
  • 本機方法在其函數定義中不使用JNIEnvjclass參數。
  • 此方法必須使用RegisterNatives註冊,而不是依賴動態 JNI 連結。

@FastNative可以將本機方法效能提高最多 3 倍, @CriticalNative最多可以提高 5 倍。例如,在 Nexus 6P 設備上測量的 JNI 轉換:

Java 本機介面 (JNI) 呼叫執行時間(以奈秒為單位)
常規 JNI 115
!bang JNI 60
@FastNative 35
@CriticalNative 25