隨著科技的發展,手機在我們的生活中扮演了很重要的角色,手機在便利我們生活的同時,也會對我們造成很重要的影響.手機已經成為了我們的必需品,不僅是通訊工具,而且很大程度上都是娛樂功能。手機應用主要指安裝在智能手機上的軟件,完善原始系統的不足與個性化。使手機完善其功能,為用戶提供更豐富的使用體驗的主要手段。 ART 將會取代Dalvik虛擬機, 因為 在Dalvik下, 應用每次運行的時候, 字節碼都需要通過即時編譯器轉換為機器碼, 這會拖慢應用的運行效率, 而在ART 環境中, 應用在第一次安裝的時候, 字節碼就會預先編譯成機器碼, 使其成為真正的本地應用。 在最新的Google I/O大會上, Google 發布了關于Android上最新的運行時庫的情況。 這就是Android RunTime (ART). ART 將會取代Dalvik虛擬機, 成為Android平臺上Java代碼的執行工具。 雖然自從Android KitKat, 就有了一些關于ART的消息, 但是基本都是一些新聞性質的, 缺乏具體技術細節方面的介紹。 本文嘗試綜合目前已有的各種消息, 以及最新放出的 Android L 預覽版本的ROM的情況, 對ART運行時庫做個詳細的分析。 ![]() 和IOS, Windows, Tizen之類的移動平臺直接將軟件編譯成能夠直接運行在特定硬件平臺上的本地代碼不同。 Android平臺上的軟件會被編譯器首先編譯成通用的“byte-code”, 然后再在具體的移動設備上被轉換成本地指令執行。 從Android誕生至今的十幾年時間里, Dalvik從開始時非常簡單的Java Byte-Code執行虛擬機, 逐漸增加各種新的特性, 滿足應用程序對性能的需求, 以及與硬件設備協同演進。 這其中包括在Android 2.2版本中引入的即時編譯器(JIT-Compiler), 以及隨后的多線程支持, 以及其他一些優化。 ![]() 不過, 在近兩年里, Android整個生態系統的進步對Android虛擬機的需求, 目前的Dalvik虛擬機的開發已經無法滿足了。 Dalvik 最初設計時, 處理器的性能很弱, 移動設備的內存空間非常有限, 而且都是32位的系統。 于是Google開始構建一個新的虛擬機來更好的面對未來的發展趨勢。 這種虛擬機的性能能夠在目前的多核處理器, 甚至未來的8核處理上輕松擴展, 能夠滿足對大容量存儲的支持, 以及大容量內存的支持。 于是乎, ART出現了。 1 架構介紹 ![]() 首先, ART的首要設計需求就是完全兼容能在Dalvik上運行的byte-code, 即dex(Dalvik executable)。 這樣的話, 對于程序員來說, 就不需要對重新編譯已有的程序, 直接拿APK就可以在Dalvik和ART虛擬機上運行。 ART帶來的最大的變化, 就是使用預編譯技術(Ahead-of-Time compile)取代Dalvik中的即時編譯技術(Just-In-Time compile)。 之前, 在應用程序每次執行的時候, 虛擬機需要將bytecode編譯成本地碼執行, 而在ART中這種編譯操作只需執行一次, 隨后對該應用程序的執行都可以通過直接執行保存下來的本地碼完成。 當然, 這種預編譯技術, 需要占用額外的存儲空間來存儲本地碼。 正是因為現在移動設備的存儲空間越來越大, 這種技術才得以應用。 這種預編譯技術使得很多原來無法執行的編譯優化技術在新的Android平臺上成為可能。 因為代碼只被編譯和優化一次, 因此值得花費更多的時間在這次編譯上, 以便進行更多的優化。 Google表示, 現在可以在應用程序的整體代碼技術上進行更高層次的優化, 因為編譯器現在能夠看到應用程序的整體代碼, 而之前的即時編譯, 編譯器只能看到并優化應用程序中某個函數或者非常小的一部分代碼。 采用ART后, 代碼中異常檢查帶來的開銷絕大部分可以避免, 對方法和接口的調用也加快了很多。 完成這部分功能的是新添加的“dex2oat”組件, 用來替代Dalvik中對應的“dexopt”組件。 Dalvik中的 Odex文件(優化后的dex)文件, 在ART中也用ELF文件代替了。 因為ART目前編譯生成ELF可執行文件, 內核就可以直接對載入內存中的代碼進行分頁管理, 這也會帶來更加高效的內存管理, 以及更少的內存占用。 說到這里, 我非常好奇內核中的KSM(Kernel same-page merging)在ART中會有什么樣的影響, 應該能帶來不錯的效果吧。 我們拭目以待。 ART對續航時間的影響也是非常顯著的。 因為不再需要解釋執行, JIT也不用在程序運行時工作, 這樣會直接節省CPU需要執行的指令數, 因而耗電降低。 因為預編譯時引入了更多分析和優化, 編譯的時間會變長, 這是ART可能會帶來的一個副作用。 因此相比Dalvik虛擬機, 當設備首次啟動及應用程序第一次安裝時, 需要花費的時間更久。 Google聲稱, 這種時間上的增加并不那么恐怖。 他們希望并預期日后ART上完成上述動作的時間會和目前的 Dalvik差不多, 甚至更短些。 ![]() 上面的圖顯示, ART帶來的性能提升是非常明顯的。 對于同樣的代碼, 性能提升約2倍左右。 Google稱, 將Android L最終發布的時候, 可以預計的性能提升將會像Chessbench一樣, 有3x的加速。 2 垃圾收集:理論和實踐 Android虛擬機依賴自動化的內存管理機制, 即自動垃圾收集。 這一Java語言編程模式的基石也是Android系統自誕生之日起, 非常重要的一部分。 這里向不太了解垃圾收集概念的朋友解釋一下, 所謂自動垃圾收集, 就是說程序員在編程過程中, 不需要自己負責物理內存的存儲的分配和釋放。 只需要使用固定的模式創建你需要的變量或者對象, 然后直接利用該變量或對象即可。 程序的運行環境會自動在內存中分配相應的內存空間存儲該變量或者對象, 并在該變量或者對象失效后, 自動釋放所分配的內存。 這是和其他需要人工進行存儲管理的較低層次語言最大的區別。 自動垃圾收集的好處是, 程序員不必再在編程時擔心內存管理的問題, 當然, 這也是有代價的, 那就是程序員無法控制內存何時分配和釋放, 因而無法在需要時進行優化(Java語言有一些編程接口可以供程序員手工優化程序, 但控制方式和粒度有限). Android曾經被Dalvik的垃圾收集機制折騰了很久。 Android平臺的內存普遍較小, 每次應用程序需要分配內存, 當堆空間(分配給應用程序的一塊內存空間)不能提供如此大小的空間時, Dalvik的垃圾收集器就會啟動。 垃圾收集器會遍歷整個堆空間, 查看每一個應用程序分配的對象, 并對所有可到達的對象(即還會被使用的對象)標記, 并將那些沒有標記的對象空間釋放掉。 在Dalvik虛擬機中, 垃圾收集器執行的過程將導致兩次應用程序的停頓: 一是在遍歷堆地址空間階段, 另一個是標記階段。 所謂停頓, 即應用程序所有正在執行的進程將暫停。 如果停頓時間過長, 將會導致應用程序在渲染時出現丟幀現象, 進而導致應用程序的卡頓現象, 大大降低用戶體驗。 Google聲稱, 在Nexus 5手機上, 這種停頓的平均長度在54ms。 這個停頓時間將導致平均每次垃圾收集會導致在應用程序渲染顯式時丟掉4幀的。 我自己的經驗和測試表明, 根據應用程序的不同, 停頓的時間可能會增大很多。 比如, 在官方的FIFA應用程序這一典型程序中, 垃圾收集的停頓會非常厲害。 07-01 15:56:14.275: D/dalvikvm(30615): GCFORALLOC freed 4442K, 25% free 20183K/26856K, paused 24ms, total 24ms 07-01 15:56:16.785: I/dalvikvm-heap(30615): Grow heap (frag case) to 38.179MB for 8294416-byte allocation 07-01 15:56:17.225: I/dalvikvm-heap(30615): Grow heap (frag case) to 48.279MB for 7361296-byte allocation 07-01 15:56:17.625: I/Choreographer(30615): Skipped 35 frames! The application may be doing too much work on its main thread. 07-01 15:56:19.035: D/dalvikvm(30615): GCCONCURRENT freed 35838K, 43% free 51351K/89052K, paused 3ms+5ms, total 106ms 07-01 15:56:19.035: D/dalvikvm(30615): WAITFORCONCURRENTGC blocked 96ms 07-01 15:56:19.815: D/dalvikvm(30615): GCCONCURRENT freed 7078K, 42% free 52464K/89052K, paused 14ms+4ms, total 96ms 07-01 15:56:19.815: D/dalvikvm(30615): WAITFORCONCURRENTGC blocked 74ms 07-01 15:56:20.035: I/Choreographer(30615): Skipped 141 frames! The application may be doing too much work on its main thread. 07-01 15:56:20.275: D/dalvikvm(30615): GCFORALLOC freed 4774K, 45% free 49801K/89052K, paused 168ms, total 168ms 07-01 15:56:20.295: I/dalvikvm-heap(30615): Grow heap (frag case) to 56.900MB for 4665616-byte allocation 07-01 15:56:21.315: D/dalvikvm(30615): GCFORALLOC freed 1359K, 42% free 55045K/93612K, paused 95ms, total 95ms 07-01 15:56:21.965: D/dalvikvm(30615): GCCONCURRENT freed 6376K, 40% free 56861K/93612K, paused 16ms+8ms, total 126ms 07-01 15:56:21.965: D/dalvikvm(30615): WAITFORCONCURRENTGC blocked 111ms 07-01 15:56:21.965: D/dalvikvm(30615): WAITFORCONCURRENTGC blocked 97ms 07-01 15:56:22.085: I/Choreographer(30615): Skipped 38 frames! The application may be doing too much work on its main thread. 07-01 15:56:22.195: D/dalvikvm(30615): GCFORALLOC freed 1539K, 40% free 56833K/93612K, paused 87ms, total 87ms 07-01 15:56:22.195: I/dalvikvm-heap(30615): Grow heap (frag case) to 60.588MB for 1331732-byte allocation 07-01 15:56:22.475: D/dalvikvm(30615): GCFORALLOC freed 308K, 39% free 59497K/96216K, paused 84ms, total 84ms 07-01 15:56:22.815: D/dalvikvm(30615): GCFORALLOC freed 287K, 38% free 60878K/97516K, paused 95ms, total 95ms 上面的log是從FIFA應用程序運行后的幾秒鐘時間里截取的。 垃圾收集器在短短的8秒內被執行了9次, 導致應用程序總共卡頓了603ms, 丟幀達214次。 絕大多數的卡頓都來自內存分配請求, 在log中以”GC_FOR_ALLOC“標簽描述。 ART將整個垃圾收集系統做了重新設計和實現。 為了能做些對比, 下面給出使用ART運行相同的應用程序, 在相同的場景下提取的log: 07-01 16:00:44.531: I/art(198): Explicit concurrent mark sweep GC freed 700(30KB) AllocSpace objects, 0(0B) LOS objects, 792% free, 18MB/21MB, paused 186us total 12.763ms 07-01 16:00:44.545: I/art(198): Explicit concurrent mark sweep GC freed 7(240B) AllocSpace objects, 0(0B) LOS objects, 792% free, 18MB/21MB, paused 198us total 9.465ms 07-01 16:00:44.554: I/art(198): Explicit concurrent mark sweep GC freed 5(160B) AllocSpace objects, 0(0B) LOS objects, 792% free, 18MB/21MB, paused 224us total 9.045ms 07-01 16:00:44.690: I/art(801): Explicit concurrent mark sweep GC freed 65595(3MB) AllocSpace objects, 9(4MB) LOS objects, 810% free, 38MB/58MB, paused 1.195ms total 87.219ms 07-01 16:00:46.517: I/art(29197): Background partial concurrent mark sweep GC freed 74626(3MB) AllocSpace objects, 39(4MB) LOS objects, 1496% free, 25MB/32MB, paused 4.422ms total 1.371747s 07-01 16:00:48.534: I/Choreographer(29197): Skipped 30 frames! The application may be doing too much work on its main thread. 07-01 16:00:48.566: I/art(29197): Background sticky concurrent mark sweep GC freed 70319(3MB) AllocSpace objects, 59(5MB) LOS objects, 825% free, 49MB/56MB, paused 6.139ms total 52.868ms 07-01 16:00:49.282: I/Choreographer(29197): Skipped 33 frames! The application may be doing too much work on its main thread. 07-01 16:00:49.652: I/art(1287): Heap transition to ProcessStateJankImperceptible took 45.636146ms saved at least 723KB 07-01 16:00:49.660: I/art(1256): Heap transition to ProcessStateJankImperceptible took 52.650677ms saved at least 966KB ART和Dalvik的差別非常大, 新的運行時內存管理僅僅停頓了12.364ms, 運行了4次前臺垃圾收集, 以及2次后臺垃圾收集。 在應用程序執行的過程中, 應用程序的堆空間大小并沒有增加, 而Dalvik虛擬機中堆空間共增加了4次。 丟幀的個數方面, ART虛擬機也降到了63幀。 上面這段示例, 只不過是一個開發并不完善的應用程序中最壞的一個場景。 因為即使在ART虛擬機中, 這個應用程序還是丟掉了不少幀渲染圖像。 不過上面的log對比依然很有參考價值, 畢竟牛逼的程序員沒幾個, 大多數的Android程序都沒辦法開發的很完美。 Android需要能hold住這種情況。 ART將一些通常需要垃圾收集器做的工作, 拆分給應用程序本身完成。 這樣, Dalvik中因為遍歷堆空間引入的第一次停頓, 就被完全消除了。 而第二次停頓也因為一項預清理技術 (packard pre-cleaning)的應用而大大縮短。 使用該技術后, 只需要在清理完成后, 簡單的檢查和驗證時稍微停頓一下即可。 Google聲稱, 他們已經設法將這類停頓的時間縮短到3ms左右, 相比Dalvik虛擬機的垃圾收集器來說, 基本上是一個多數量級的降低, 很不錯了。 ![]() ART還引入了一個特殊的超大對象存儲空間(large object space, LOS), 這個空間與堆空間是分開的, 不過仍然駐留在應用程序內存空間中。 這一特殊的設計是為了讓ART可以更好的管理較大的對象, 比如位圖對象(bitmaps)。 在對堆空間分段時, 這種較大的對象會帶來一些問題。 比如, 在分配一個此類對象時, 相比其他普通對象, 會導致垃圾收集器啟動的次數增加很多。 有了這個超大對象存儲空間的支持, 垃圾收集器因堆空間分段而引發調用次數將會大大降低, 這樣垃圾收集器就能做更加合理的內存分配, 從而降低運行時開銷。 一個很好的例子, 就是運行Hangouts(環聊)應用程序時, 在Dalvik虛擬機中, 我們能看到數次因為分配內存, 運行GC而導致的停頓。 07-01 06:37:13.481: D/dalvikvm(7403): GCEXPLICIT freed 2315K, 46% free 18483K/34016K, paused 3ms+4ms, total 40ms 07-01 06:37:13.901: D/dalvikvm(9871): GCCONCURRENT freed 3779K, 22% free 21193K/26856K, paused 3ms+3ms, total 36ms 07-01 06:37:14.041: D/dalvikvm(9871): GCFORALLOC freed 368K, 21% free 21451K/26856K, paused 25ms, total 25ms 07-01 06:37:14.041: I/dalvikvm-heap(9871): Grow heap (frag case) to 24.907MB for 147472-byte allocation 07-01 06:37:14.071: D/dalvikvm(9871): GCFORALLOC freed 4K, 20% free 22167K/27596K, paused 25ms, total 25ms 07-01 06:37:14.111: D/dalvikvm(9871): GCFORALLOC freed 9K, 19% free 23892K/29372K, paused 27ms, total 28ms 我們從所有的垃圾收集log中截取了上述一段。 其中的顯式(GC_EXPLICIT)和并發(GC_CONCURRENT)是垃圾收集器中比較通用的清理和維護性調用。 GC_FOR_ALLOC則是在內存分配器嘗試分配新的內存空間, 但堆空間不夠用時, 調用的。 上面的log中, 我們能看到堆空間因為分段操作而擴充了堆空間, 但仍然無法裝下大對象。 在整個大對象分配的過程中, 停頓時間長達90ms。 相比之下, 下面這段log是從Android L預覽版本的ART運行log中提取的。 07-01 06:35:19.718: I/art(10844): Heap transition to ProcessStateJankPerceptible took 17.989063ms saved at least -138KB 07-01 06:35:24.171: I/art(1256): Heap transition to ProcessStateJankImperceptible took 42.936250ms saved at least 258KB 07-01 06:35:24.806: I/art(801): Explicit concurrent mark sweep GC freed 85790(3MB) AllocSpace objects, 4(10MB) LOS objects, 850% free, 35MB/56MB, paused 961us total 83.110ms 我們目前還不知道log中的”Heap Transition”表達的什么意思, 不過可以猜測應該是堆空間大小重設機制。 在應用程序已經運行之后, 唯一的對垃圾收集器的調用僅消耗的961us。 我們并沒有在這段截取的log之前, 發現任何對垃圾收集器的調用操作。 這段log中比較有趣的, 就是LOS的統計。 能夠看到, 在LOS中有4個較大的對象, 共10MB。 這塊內存并沒有分配在堆空間內, 否則應該會有類似Dalvik的提示。 ART的內存分配系統本身也被重寫了。 雖然ART相比Dalvik, 在內存分配方面, 能夠帶來大約25%的性能提升, 不過Google顯然對此不滿意, 因此引入了一個新的內存分配器來取代當前使用的“malloc”分配器。 這個新的內存分配器, “rosalloc”(Runs-of-Slots-Allocator)是依據多線程Java應用程序的特點而設計的。 此內存分配器有更細粒度的鎖機制, 可以直接對獨立的對象上鎖, 而非對整個待分配的內存空間上鎖。 在線程局部區域中的小對象的分配, 完全可以無視鎖的存在了。 沒有了鎖的請求和釋放, 線程局部小對象的訪問速度也就大幅提升了。 這個新的內存分配器大幅提升了內存分配的速度, 加速比達到了10x。 同時, ART的垃圾回收算法也做了改進, 提升了用戶使用體驗, 避免應用程序的卡頓。 這些算法在Google內部目前仍然正在開發中。 近期, Google僅僅介紹了一個新算法, “Moving Garbage Collector”.核心思想是, 當應用程序運行在后臺時, 將程序的堆空間做段合并操作。 3 64位支持 ART在設計時充分考慮了將日后可能運行的各種平臺進行模塊化。 因此, ART提供了大量的編譯器后端, 用于生成目前常見的體系結構的代碼, 例如ARM, X86和MIPS, 其中包括對ARM64, X86-64的支持, 以及尚未實現的對MIPS64的支持。 ![]() 對于ARM的64位系統帶來的好處, 相比很多朋友都了解了。 更大的內存地址空間, 普適的性能提升, 以及加解密的能力和性能提升, 此外還有對已有32位應用程序的兼容。 除此之外, Google還在ART中引入了引用壓縮技術, 來避免ART堆空間內部因為64位指針的引入導致的內存占用變大問題。 其實, 就是在執行時, 所有的指針都采用32位表示, 而非64位系統應該采用的64位指針。 ![]() Google公開了一些ARM和X86平臺上應用程序在64位和32位模式下的性能對比。 這只是一些預覽性質數據。 X86的性能測試在Intel的 BayTrail系統上進行, 對于不同的RenderScript測試程序, 性能提升從2x到4.5x不等。 ARM平臺方面, 分別在A57和A53系統上, 對crypto的性能做了對比。 這些數據因為都是針對非常小的例子, 所以代表性不大, 因此還無法代表實際應用場景的情況。 不過, Google也放出了一些有趣的數據, 這些數據是在他們內部使用的系統Panorama上測試的。 通過簡單的從32位ABI轉換為64位 ABI, 能夠獲得13%到19%的性能提升。 還有個喜人的結論, 那就是ARM的Cortex A53在AArch64模式下能獲得性能提升比A57核要多。 Google還聲稱, 目前應用商店中85%的應用程序都可以直接在64位模式下運行, 也就是說僅有15%的應用程序在某種程度上使用了本地代碼, 需要重新為64位平臺編譯該應用程序。 這對Google來說將是一個非常大的優勢。 明年, 當大多數芯片廠商都開始推64位片上系統的時候, 從32位 Android系統到64位Android系統的的切換將會非常快。 4 結論 結合上面介紹的諸多方面, ART是Google發布的一款性能提升大殺器, 并且ART也解決了多個數年來困擾Android系統的諸多問題。 ART 有效地改進了多個解釋執行應用程序面臨的問題, 也提供了一個自動化的高效的存儲管理系統。 對于開發者來說, 許多過去需要手工添加代碼解決的性能問題, 現在都能被ART輕松hold住了。 這也意味著Android系統終于能夠在系統平滑度, 應用程序性能方面與IOS勢均力敵了。 對消費者來說, 是件喜大普奔的事情。 Google目前仍在, 而且在未來一段時間內還將大力改進ART。 ART目前的狀況, 與6個月前已經大不相同了, 預計等到Android L真正發布的時候, 又會有翻天覆地的變化。 前途是光明的, 讓我們拭目以待, 翹首期盼吧。 手機APP這些安裝在手機里面豐富多彩的軟件,讓我們的生活更加方便和精彩。游戲、社交、購物、視頻、音樂、學習......我們可以隨時隨地,隨處進行。 |
溫馨提示:喜歡本站的話,請收藏一下本站!