Android 編譯優化——ART與Dalvik區別

一、Android Runtime (ART) 和 Dalvik

Android Runtime (ART) 是 Android 上的應用和部分系統服務使用的託管式運行時。ART 及其前身 Dalvik 最初是專爲 Android 項目打造的。作爲運行時的 ART 可執行 Dalvik 可執行文件並遵循 Dex 字節碼規範。

ART 和 Dalvik 是運行 Dex 字節碼的兼容運行時,因此針對 Dalvik 開發的應用也能在 ART 環境中運作。不過,Dalvik 採用的一些技術並不適用於 ART。有關最重要問題的信息,請參閱在 Android Runtime (ART) 上驗證應用行爲

Dalvik 是一個 Google 開發的 VM(Virtual Machine),用在 Android 手機裏的運行系統。 DVM is based on JIT (Just In Time) compilation 這代表說,每一次使用者在 run app 的時候,有部分將被執行的 code 會在那時後被轉譯成裝置懂的語言。當使用者在使用 app 的時候,其他的 code 也會被轉譯再被儲存於內存。因爲 JIT 只轉譯部分的 Code ,所以也不會佔太多記憶體空間。

ART 功能

以下是 ART 實現的一些主要功能。

預先 (AOT) 編譯

ART 引入了預先編譯機制,可提高應用的性能。ART 還具有比 Dalvik 更嚴格的安裝時驗證。

在安裝時,ART 使用設備自帶的 dex2oat 工具來編譯應用。此實用工具接受 DEX 文件作爲輸入,併爲目標設備生成經過編譯的應用可執行文件。該工具應能夠順利編譯所有有效的 DEX 文件。但是,一些後處理工具會生成無效文件,Dalvik 可以接受這些文件,但 ART 無法編譯這些文件。如需瞭解詳情,請參閱處理垃圾回收問題

垃圾回收方面的優化

垃圾回收 (GC) 會耗費大量資源,這可能有損於應用性能,導致顯示不穩定、界面響應速度緩慢以及其他問題。ART 通過以下幾種方式對垃圾回收做了優化:

  • 大多采用併發設計,具有一次 GC 暫停
  • 併發複製,可減少後臺內存使用和碎片
  • GC 暫停的時間不受堆大小影響
  • 在清理最近分配的短時對象這種特殊情況中,回收器的總 GC 時間更短
  • 優化了垃圾回收的工效,能夠更加及時地進行並行垃圾回收,這使得 GC_FOR_ALLOC 事件在典型用例中極爲罕見

開發和調試方面的優化

ART 提供了大量功能來優化應用開發和調試。

支持採樣分析器

一直以來,開發者都使用 Traceview 工具(用於跟蹤應用執行情況)作爲分析器。雖然 Traceview 可提供有用的信息,但每次方法調用產生的開銷會導致 Dalvik 分析結果出現偏差,而且使用該工具明顯會影響運行時性能。

ART 添加了對沒有這些限制的專用採樣分析器的支持,因而可更準確地瞭解應用執行情況,而不會明顯減慢速度。KitKat 版本爲 Dalvik 的 Traceview 添加了採樣支持。

支持更多調試功能

ART 支持許多新的調試選項,特別是與監控和垃圾回收相關的功能。例如,您可以:

  • 查看堆棧跟蹤中保留了哪些鎖,然後跳轉到持有鎖的線程。
  • 詢問指定類的當前活動的實例數、請求查看實例,以及查看使對象保持有效狀態的參考。
  • 過濾特定實例的事件(如斷點)。
  • 查看方法退出(使用“method-exit”事件)時返回的值。
  • 設置字段觀察點,以在訪問和/或修改特定字段時暫停程序執行。

 

二、編譯方式

Android5.之前,採用的是Dalvik虛擬機,編譯方式和Java類似,爲JIT,JIT意思是Just In Time Compiler,就是即時編譯技術,與Dalvik虛擬機相關。

JIT是幹嘛的

JIT在Android2.2到Android4.4版本(7.0版本也有,後文會敘述),JIT的目的是爲了提高Android的運行效率。

Dalvik虛擬機可以看做是一個Java虛擬機。在 Android系統初期,每次運行程序的時候,Dalvik負責將dex翻譯爲機器碼交由系統調用。這樣有一個缺陷每次執行代碼,都需要Dalvik將操作碼代碼翻譯爲機器對應的微處理器指令,然後交給底層系統處理,運行效率很低

爲了提升效率Android在2.2版本中添加了JIT編譯器,當App運行時,每當遇到一個新類,JIT編譯器就會對這個類進行即時編譯,經過編譯後的代碼,會被優化成相當精簡的原生型指令碼(即native code),這樣在下次執行到相同邏輯的時候,速度就會更快。JIT 編譯器可以對執行次數頻繁的 dex/odex 代碼進行編譯與優化,將 dex/odex 中的 Dalvik Code(Smali 指令集)翻譯成相當精簡的 Native Code 去執行,JIT 的引入使得 Dalvik 的性能提升了 3~6 倍。

JIT優勢:

能夠根據當前硬件狀況實時編譯生成最優機器指令(ps. AOT也能夠作到,在用戶使用是使用字節碼根據機器狀況在作一次編譯)
能夠根據當前程序的運行狀況生成最優的機器指令序列
當程序須要支持動態連接時,只能使用JIT
能夠根據進程中內存的實際狀況調整代碼,使內存可以更充分的利用

JIT缺陷

  • 編譯須要佔用運行時資源,會致使進程卡頓
  • 因爲編譯時間須要佔用運行時間,對於某些代碼的編譯優化不能徹底支持,須要在程序流暢和編譯時間之間作權衡
  • 在編譯準備和識別頻繁使用的方法須要佔用時間,使得初始編譯不能達到最高性能
  • 由於JIT Code Cache是內存緩存,因此每次運行都需要重新編譯

ART和AOT

AOT是指"Ahead Of Time",與"Just In Time"不同,從字面來看是說提前編譯。

AOT是幹嘛的

JIT是運行時編譯,是動態編譯,可以對執行次數頻繁的dex代碼進行編譯和優化,減少以後使用時的翻譯時間,雖然可以加快Dalvik運行速度,但是有一個很大的問題:將dex翻譯爲本地機器碼也要佔用時間。 所以Google在4.4推出了全新的虛擬機運行環境ART(Android RunTime),用來替換Dalvik(4.4上ART和Dalvik共存,用戶可以手動選擇,5.0 後Dalvik被替換)。

AOT 是靜態編譯,應用在安裝的時候會啓動 dex2oat 過程把 dex預編譯成 ELF 文件,每次運行程序的時候不用重新編譯。 ART 對 Garbage Collection(GC)過程的也進行了改進:

AOT的優點

  • 在程序運行前編譯,能夠避免在運行時的編譯性能消耗和內存消耗
  • 能夠在程序運行初期就達到最高性能
  • 能夠顯著的加快程序的啓動

AOT的缺陷

  • 應用安裝和系統升級之後的應用優化比較耗時(重新編譯,把程序代碼轉換成機器語言)
  • 優化後的文件會佔用額外的存儲空間(緩存轉換結果)

圖片來自:dexopt 與 dex2oat 區別

ELF: 包含dex和native code,native code: 經過ART編譯後的ELF文件,系統能夠直接運行

  • dexopt 是對 dex 文件 進行 verification 和 optimization 的操作,其對 dex 文件的優化結果變成了 odex 文件,這個文件和 dex 文件很像,只是使用了一些優化操作碼(譬如優化調用虛擬指令等)。

  • dex2oat 是對 dex 文件的 AOT 提前編譯操作,其需要一個 dex 文件,然後對其進行編譯,結果是一個本地可執行的 ELF 文件,可以直接被本地處理器執行。

三、Android N混合編譯

Android 7.0/7.1的ART引入了全新的Hybrid模式(Interpreter + JIT + AOT),主要是爲了解決如下問題

  1. 應用安裝時間過長;在N之前,應用在安裝時需要對所有ClassN.dex做AOT機器碼編譯,類似微信這種比較大型的APP可能會耗時數分鐘。但是往往我們只會使用一個應用20%的功能,剩下的80%我們付出了時間成本,卻沒帶來太大的收益。
  2. 降低佔ROM空間;同樣全量編譯AOT機器碼,12M的dex編譯結果往往可以達到50M之多。只編譯用戶用到或常用的20%功能,這對於存儲空間不足的設備尤其重要。
  3. 提升系統與應用性能;減少了全量編譯,降低了系統的耗電。在boot.art的基礎上,每個應用增加了base.art(這塊後面會詳細解析), 通過預加載與緩存提升應用性能。
  4. 快速的系統升級;以往廠商ota時,需要對安裝的所有應用做全量的AOT編譯,這耗時非常久。事實上,同樣只有20%的應用是我們經常使用的,給不常用的應用,不常用的功能付出的這些成本是不值得的。

原理:

App在安裝時不編譯, 所以安裝速度快。在運行App時, 先走JIT解釋器, 然後熱點函數會被識別,並被JIT進行編譯, 存儲在jit code cache, 併產生profile文件(記錄熱點函數信息)。
等手機進入charging和idle狀態下, 系統會每隔一段時間掃描App目錄下profile文件,並執行AOT編譯(Google官方稱之爲profile-guided compilation)。不論是jit編譯的binary code, 還是AOT編譯的binary code, 它們之間的性能差別不大, 因爲它們使用同一個optimizing compiler進行編譯。
 

優點: App安裝速度快,佔用存儲少(只編譯熱點函數)。
缺點: 前幾次運行會較慢, 只有用戶操作得次數越多,jit 和AOT編譯後, 性能纔會跟上來。

(上圖有幾個箭頭存在問題 )

  • commits code 應該是由optimizing compiler指向jit code cache
  • gets ProfileingInfos 方向是反了
  • jit code cache應該指向ART Runtime:Execute Method

 

Android N的ART模式

JIT的解釋器

  • 對字節碼進行解釋
    • 基於計算的跳轉指令
    • 基於Arm彙編的Operation Code處理
  • Profiling以及JIT編譯器的觸發
    • 基於函數執行次數以及搜索式的代碼熱度
  • JIT代碼緩存
    • 管理編譯過的緩存代碼
    • 爲Hot Methods分配ProfilingInfo對象

JIT的編譯器
函數粒度的編譯

  • 後臺編譯
    • 避免Block App的UI線程
  • 基於ART優化的編譯器
    • 使用和AOT一樣的編譯器
  • 在優化編譯器中會增強JIT的編譯能力

使用單獨的ProfileSaver線程生成Profile文件

  • 生成Profile文件
    • 讀取根據Hot Methods生成ProfilingInfo
    • 把ProfilingInfo寫到磁盤文件中
  • 最低的消耗
    • 減少Wakeup的次數
    • 寫入最少的信息

使用混編模式的原因

  • 部分用戶只使用APP的部分功能。而且這些經常使用的功能是值得被編譯成Native Code的
  • 使用JIT階段找出來經常使用的代碼
  • 使用AOT編譯以及優化來提升經常使用的這些功能
  • 避免爲了一些不常用的代碼而付出資源(編譯、存儲等等)

混編模式的實現

  • 在JIT的過程中,生成Offline的Profile
  • Offline Profile的文件格式
  • 使用AOT增強過後的編譯器(dex2oat)
  • 編譯所使用的Daemon Service
    • 只在充電或者系統IDLE的情況下才會進行AOT編譯

Profile文件會在JIT運行的過程中生成:

  • 每個APP都會有自己的Profile文件
  • 保存在App本身的Local Storage中
  • Profile會保存所調用的類以及函數的Index
  • 通過profman工具進行分析生成

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章