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 實現細節 - 閱讀障礙:
    • 讀取障礙是為讀取的每個對象字段所做的少量工作。
    • 這些在編譯器中進行了優化,但可能會減慢某些用例。

循環優化

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

  • 邊界檢查消除
    • 靜態:範圍在編譯時被證明在界限內
    • 動態:運行時測試確保循環保持在界限內(否則 deopt)
  • 感應變量消除
    • 去除死感應
    • 用封閉式表達式替換僅在循環之後使用的歸納
  • 循環體內的死代碼消除,刪除整個死循環
  • 強度降低
  • 循環變換:反轉、互換、拆分、展開、單模等。
  • SIMDization(也稱為矢量化)

循環優化器駐留在 ART 編譯器中它自己的優化過程中。大多數循環優化類似於其他地方的優化和簡化。一些優化以比通常更精細的方式重寫 CFG 帶來了挑戰,因為大多數 CFG 實用程序(參見 nodes.h)專注於構建 CFG,而不是重寫。

類層次分析

Android 8.0 中的 ART 使用類層次結構分析 (CHA),這是一種編譯器優化,可根據分析類層次結構生成的信息將虛擬調用虛擬化為直接調用。虛擬調用很昂貴,因為它們是圍繞 vtable 查找實現的,並且它們需要一些依賴負載。虛擬呼叫也不能內聯。

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

  • 動態單實現方法狀態更新 - 在類鏈接時間結束時,當 vtable 已填充時,ART 對超類的 vtable 進行逐項比較。
  • 編譯器優化 - 編譯器將利用方法的單一實現信息。如果方法 A.foo 設置了單一實現標誌,編譯器會將虛擬調用去虛擬化為直接調用,並因此進一步嘗試內聯直接調用。
  • 編譯代碼失效 - 同樣在類鏈接時間結束時更新單實現信息時,如果先前具有單實現但該狀態現在已失效的方法 A.foo,則所有編譯代碼都取決於方法 A. 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 中,除方法數組外,所有數組均已移除。

口譯員表現

解釋器性能在 Android 7.0 版本中顯著提高,引入了“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 Native Interface (JNI)。這些內置的 ART 運行時優化可加快 JNI 轉換並替換現在已棄用的!bang JNI表示法。註釋對非本機方法沒有影響,並且僅可用於引導類bootclasspath上的平台 Java 語言代碼(無 Play 商店更新)。

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

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

  • 方法必須是靜態的——沒有參數、返回值或隱式this的對象。
  • 只有原始類型被傳遞給本機方法。
  • 本機方法在其函數定義中不使用JNIEnvjclass參數。
  • 該方法必須使用RegisterNatives ,而不是依賴動態 JNI 鏈接。

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

Java 本機接口 (JNI) 調用執行時間(以納秒為單位)
常規 JNI 115
!bang JNI 60
@FastNative 35
@CriticalNative 25