
上圖是Android整體的架構(gòu),Android Runtime之于Android而言相當(dāng)于心臟之于人體,是Android程序加載和運(yùn)行的環(huán)境。這篇文章主要針對(duì)Android Runtime部分進(jìn)行展開(kāi),探討Android Runtime的發(fā)展以及目前現(xiàn)狀,并介紹應(yīng)用Profile-Guided Optimization(PGO)技術(shù)對(duì)應(yīng)用啟動(dòng)速度進(jìn)行優(yōu)化的可行性。轉(zhuǎn)載請(qǐng)注明來(lái)源「申國(guó)駿」
App運(yùn)行時(shí)演進(jìn)
JVM
Android原生代碼使用Java或者Kotlin編寫(xiě),這些代碼會(huì)通過(guò)javac或者kotlinc編譯成.class文件,在Android之前,這些.class文件會(huì)被輸入到JVM中執(zhí)行。JVM可以簡(jiǎn)單分為三個(gè)子系統(tǒng),分別是Class Loader、Runtime Data Area以及Execution Engine。其中Class Loader主要負(fù)責(zé)加載類(lèi)、校驗(yàn)字節(jié)碼、符號(hào)引用鏈接及對(duì)靜態(tài)變量和靜態(tài)方法分配內(nèi)存并初始化。Runtime Data負(fù)責(zé)存儲(chǔ)數(shù)據(jù),分為方法區(qū)、堆區(qū)、棧區(qū)、程序計(jì)數(shù)器以及本地方法棧。Execution Engine負(fù)責(zé)二進(jìn)制代碼的執(zhí)行以及垃圾回收。

Execution Engine中,會(huì)采用Interpreter或者JIT執(zhí)行。其中Interpreter表示在運(yùn)行的過(guò)程中對(duì)二進(jìn)制代碼進(jìn)行解釋?zhuān)看螆?zhí)行相同的二進(jìn)制代碼都進(jìn)行解釋比較浪費(fèi)資源,因此對(duì)于熱區(qū)的二進(jìn)制代碼會(huì)進(jìn)行JIT即時(shí)編譯,對(duì)二進(jìn)制代碼編譯成機(jī)器碼,這樣相同的二進(jìn)制代碼執(zhí)行時(shí),就不用再次進(jìn)行解釋。

DVM(Android 2.1/2.2)
JVM是stack-based的運(yùn)行環(huán)境,在移動(dòng)設(shè)備中對(duì)性能和存儲(chǔ)空間要求較高,因此Android使用了register-based的Dalvik VM。從JVM轉(zhuǎn)換到DVM我們需要將.class文件轉(zhuǎn)換為.dex文件,從.class轉(zhuǎn)換到.dex的過(guò)程需要經(jīng)過(guò) desugar -> proguard -> dex compiler三個(gè)過(guò)程,這三個(gè)過(guò)程后來(lái)逐步變成 proguard -> D8(Desugar) 直到演變到今天只需要一步R8(D8(Desugar))。

我們主要關(guān)注Android中Runtime Engine與JVM的區(qū)別。在Android早期的版本里面,只存在Interpreter解釋器,到了Android2.2版本將JIT引入,這個(gè)版本Dalvik與JVM的Runtime Engine區(qū)別不大。

ART-AOT(Android 4.4/5.0)
為了加快應(yīng)用的啟動(dòng)速度和體驗(yàn),到了Android4.4,Google提供了一個(gè)新的運(yùn)行時(shí)環(huán)境ART(Android Runtime),到了Android5.0,ART替換Dalvik成為唯一的運(yùn)行時(shí)環(huán)境。

ART運(yùn)行時(shí)環(huán)境中,采用了AOT(Ahead-of-time)編譯方式,即在應(yīng)用安裝的時(shí)候就將.dex提前編譯成機(jī)器碼,經(jīng)過(guò)AOT編譯之后.dex文件會(huì)生成.oat文件。這樣在應(yīng)用啟動(dòng)執(zhí)行的時(shí)候,因?yàn)椴恍枰M(jìn)行解釋編譯,大大加快了啟動(dòng)速度。

然而AOT帶來(lái)了以下兩個(gè)問(wèn)題:
- 應(yīng)用安裝時(shí)間大幅增加,由于在安裝的過(guò)程中同時(shí)需要編譯成機(jī)器碼,應(yīng)用安裝時(shí)間會(huì)比較長(zhǎng),特別在系統(tǒng)升級(jí)的時(shí)候,需要對(duì)所有應(yīng)用進(jìn)行重新編譯,出現(xiàn)了經(jīng)典的升級(jí)等待噩夢(mèng)。

- 應(yīng)用占用過(guò)多的存儲(chǔ)空間,由于所有應(yīng)用都被編譯成.oat機(jī)器碼,應(yīng)用所占的存儲(chǔ)空間大大增加,使得本來(lái)并不充裕的存儲(chǔ)空間變得雪上加霜。
進(jìn)一步思考對(duì)應(yīng)用全量進(jìn)行編譯可能是沒(méi)有必要的,因?yàn)橛脩艨赡苤粫?huì)用到一個(gè)應(yīng)用的部分常用功能,并且全量編譯之后更大的機(jī)器碼加載會(huì)占用IO資源。
ART-PGO(Android 7.0)
從Android7.0開(kāi)始,Google重新引入了JIT的編譯方式,不再對(duì)應(yīng)用進(jìn)行全量編譯,結(jié)合AOT、JIT、Interpreter三者的優(yōu)勢(shì)提出了PGO(Profile-guided optimization)的編譯方式。
在應(yīng)用執(zhí)行的過(guò)程中,先使用Interpreter直接解釋?zhuān)?dāng)某些二進(jìn)制代碼被調(diào)用次數(shù)較多時(shí),會(huì)生成一個(gè)Profile文件記錄這些方法存儲(chǔ)起來(lái),當(dāng)二進(jìn)制代碼被頻繁調(diào)用時(shí),則直接進(jìn)行JIT即時(shí)編譯并緩存起來(lái)。
當(dāng)應(yīng)用處于空閑(屏幕關(guān)閉且充電)的狀態(tài)時(shí),編譯守護(hù)進(jìn)程會(huì)根據(jù)Profile文件進(jìn)行AOT編譯。
當(dāng)應(yīng)用重新打開(kāi)時(shí),進(jìn)行過(guò)JIT和AOT編譯的代碼可以直接執(zhí)行。
這樣就可以在應(yīng)用安裝速度以及應(yīng)用打開(kāi)速度之間取得平衡。


JIT 工作流程:

ART-Cloud Profile(Android 9.0)
不過(guò)這里還是有一個(gè)問(wèn)題,就是當(dāng)用戶第一次安裝應(yīng)用的時(shí)候并沒(méi)有進(jìn)行任何的AOT優(yōu)化,通常會(huì)經(jīng)過(guò)用戶多次的使用才能使得啟動(dòng)速度得到優(yōu)化。

考慮到一個(gè)應(yīng)用通常會(huì)有一些用戶經(jīng)常使用執(zhí)行的代碼(例如啟動(dòng)部分以及用戶常用功能)并且大多數(shù)時(shí)候會(huì)有先行版本用于收集Profile數(shù)據(jù),因此Google考慮將用戶生成的Profile文件上傳到Google Play中,并在應(yīng)用安裝時(shí)同時(shí)帶上這個(gè)Profile文件,在安裝的過(guò)程中,會(huì)根據(jù)這個(gè)Profile對(duì)應(yīng)用進(jìn)行部分的AOT編譯。這樣當(dāng)用戶安裝完第一次打開(kāi)的時(shí)候,就能達(dá)到較快的啟動(dòng)速度。


Profile in cloude 需要系統(tǒng)應(yīng)用市場(chǎng)支持,在國(guó)內(nèi)市場(chǎng)使用Google Play的占比非常低,因此cloud profile的優(yōu)化在國(guó)內(nèi)幾乎是沒(méi)有作用的,不過(guò)Profile的機(jī)制提供了一個(gè)可以做啟動(dòng)優(yōu)化的思路。早在2019年,支付寶就在秒開(kāi)技術(shù)的回應(yīng)的里面提到過(guò)profile-based compile的技術(shù),參考:如何看待今日頭條自媒體發(fā)布謠言稱(chēng)「支付寶幾乎秒開(kāi)是因?yàn)椴捎萌A為方舟編譯器」?,這也是我們一直研究Profile技術(shù)的原因。困擾著我們的一直有兩個(gè)問(wèn)題,第一個(gè)問(wèn)題是如何生成Profile文件,第二個(gè)問(wèn)題是怎么使用生成的Profile文件。對(duì)于第一個(gè)問(wèn)題的解決相對(duì)還是有思路的,因?yàn)閍pp運(yùn)行就會(huì)生成profile文件,因此我們手動(dòng)運(yùn)行幾次app就能在文件系統(tǒng)中收集到這個(gè)文件,不過(guò)如何以一種較為自動(dòng)化的手段收集仍然是個(gè)問(wèn)題。第二個(gè)問(wèn)題我們知道Profile文件最終生成的位置,因此我們可以把生成的文件放到相應(yīng)的系統(tǒng)目錄,不過(guò)大多數(shù)手機(jī)和應(yīng)用都沒(méi)有權(quán)限直接放置這個(gè)文件。因此Profile優(yōu)化技術(shù)一直都沒(méi)有落地,直到Baseline Proflie讓我們看到了希望。
Baseline Profile
Baseline Profile是一套生成和使用Profile文件的工具,在2022年一月份開(kāi)始進(jìn)入視野,隨后在Google I/O 2022隨著Jetpack新變化得到廣泛關(guān)注。其背景是Google Map加快了發(fā)版速度,Cloud Profle還沒(méi)完全收集好就上新版,導(dǎo)致Cloud Proflie失效。還有一個(gè)背景是Jetpack Compose 不是系統(tǒng)代碼,因此沒(méi)有完全編譯成機(jī)器碼,而且Jetpack Compose庫(kù)比較大,因此在Profile生成之前使用了Jetpack Compose的應(yīng)用啟動(dòng)會(huì)產(chǎn)生性能問(wèn)題。最后Google為了解決這些問(wèn)題,創(chuàng)造了收集Profile的BaselineProfileRule Macrobenchmark以及使用Profile的ProfileInstaller。
使用Baseline Profile的機(jī)制可以在Android7及以上的手機(jī)上得到應(yīng)用的啟動(dòng)加速,因?yàn)閺纳鲜鲋繟ndroid7就已經(jīng)開(kāi)始有PGO(Profile-guided optimization)的編譯方式。生成的Profile文件會(huì)打包到apk里面,并且會(huì)結(jié)合Google Play的Cloud Profile來(lái)引導(dǎo)AOT編譯。雖然在國(guó)內(nèi)基本上用不了Cloud Profile,不過(guò)Baseline Profile是可以獨(dú)立于Google Play單獨(dú)使用的。

在使用了Baseline Proflie之后,有道詞典的啟動(dòng)速度從線上統(tǒng)計(jì)上看,冷啟動(dòng)時(shí)間有15%的提升。
這篇文章主要介紹了Android Runtime的演進(jìn)以及對(duì)于應(yīng)用啟動(dòng)的影響,下一篇文章我會(huì)詳細(xì)介紹關(guān)于Profile&dex文件優(yōu)化、Baseline Profile工具庫(kù)原理,以及在實(shí)際操作上如何使用的問(wèn)題,敬請(qǐng)大家期待一下!
參考
Improving app performance with ART optimizing profiles in the cloud
Understanding Android Runtime (ART) for faster apps (Google I/O'19)
Performance and memory improvements in Android Run Time (ART) (Google I/O '17)
Deep dive into ART(Android Runtime) for dynamic binary analysis | SungHyoun Song | Nullcon 2021
Android 強(qiáng)推的 Baseline Profiles 國(guó)內(nèi)能用嗎?我找 Google 工程師求證了!