ART和JIT的除了編譯的時機區別以外,對於編譯的方式有什麼區別嗎?

ART是提前編譯,JIT是即時編譯,我想問一下除了編譯的時機不同,他們具體的編譯過程是否是一樣,比如前端編譯和後端編譯他們是否有區別,是否都是davik位元組碼變成MIR然後變成LIR然後生成機器碼,編譯的具體實現是否一樣,另外請教如何學習ART的編譯,求推薦資料,是否直接參照編譯原理有關書籍


Android Runtime(ART)經過了幾年的發展,每個發布版本的情況都不太一樣,最好是指定版本來討論。

例如說,ART從最初只有AOT編譯器,到加上了解釋器,到進一步加上了JIT編譯模式;ART中包含的編譯器也經歷了多個不同實現的「鬥爭」。不指定版本很難說清楚。

按正式發布的大版本看,ART里的編譯器經歷了:

  • Android 4.4 KitKat:只有Quick編譯器可用。Portable編譯器的代碼雖然還存在,但並不能完全正常的工作。
  • Android 5.0 Lollipop:只有Quick編譯器可用。Portable編譯器狀況基本同上。此時還外加了新的名為Optimizing的編譯器,同樣尚未完全可用。
  • Android 6.0 Marshmallow:默認使用Optimizing編譯器,可選退回到Quick編譯器。編譯器既可以當作AOT使用,也可以當作JIT使用。Portable編譯器已被刪除。
  • AOSP dev master:只有Optimizing編譯器。Quick編譯器也已被刪除(2016年3月)

所以ART里有實際意義的值得討論的編譯器就只有兩個:

  • Quick:由Dalvik VM的JIT編譯器移植到ART的過渡階段用的編譯器;
  • Optimizing:由V8 Crankshaft / Dart VM移植到ART的編譯器。整體思路/設計源自HotSpot VM的Client Compiler(C1)。

如果還有同學糾結LLVM的話,上面說的「Portable」編譯器就是ART里基於LLVM的編譯器(的其中一個)。它在正式發布的Android中從來都不能完全正常的工作,後來被完全刪除了。

在Portable之前,ART還有過另外幾個基於LLVM的編譯器的嘗試。它們都掛了。

言歸正傳,題主問的是:

他們具體的編譯過程是否是一樣,比如前端編譯和後端編譯他們是否有區別,是否都是Davik位元組碼變成MIR然後變成LIR然後生成機器碼

大體上說ART里的編譯器用作AOT與用作JIT其實做的事情都差不多。要說有啥大體方向上的不同,最大的就是用作AOT時通常會生成PIC(position-independent code),而用作JIT時則生成帶有直接地址的代碼(非PIC)。

ART Quick編譯器的情況

Quick編譯器是從Dalvik直接移植到ART上的,所以仍然與原本Dalvik的版本保持的高度的相似性,主要是在細節上有差異而已。

發展到後來,ART Quick也一樣可以當作JIT編譯器使用,而不像剛開始只能用作AOT。

ART Quick與Dalvik JIT最大的不同是:前者只能以方法為編譯單元來編譯;而後者既可以以trace,也可以以方法為編譯單元,默認是以trace為編譯單元。

其它其實都大同小異。編譯流程還是一樣經過

輸入的Dex -&> 平台無關MIR -&> 平台相關LIR -&> 機器碼

的步驟。

ART Quick搞了那麼多年還是沒有把寄存器分配器做好。方法內聯也沒弄好。總之整體來說是個相當糟糕的編譯器。

ART Optimizing編譯器的情況

而新的名為Optimizing的編譯器則跟Quick完全不一樣。如前文所說,它的作者之前是在Dart VM組做編譯器的,轉到ART組自然還帶著一樣的思路,索性就把Dart VM里的編譯器移植到了ART上——「思路上」移植,代碼上是重新寫的。

Dart VM里的編譯器源自V8 Crankshaft。

而V8 Crankshaft又源自HotSpot VM Client Compiler(C1)。

這麼一來技術的源頭就又跟老大哥接上軌了 &>_&<

所以,要了解ART Optimizing編譯器,可以先從HotSpot C1的論文入手:

Design of the Java HotSpotTM Client Compiler for Java 6

先看看這篇論文里都提到了一些什麼技術(例如SSA形式、Global Value Numbering之類的名詞),然後再找資料來學習它們吧。我還是推薦我的書單:學習編程語言與編譯優化的一個書單

然後要留意的區別是:

HotSpot C1採用了兩層IR,平台無關的SSA形式的HIR,以及平台相關的傳統形式的LIR。前者用於優化,後者用於寄存器分配與代碼生成。

而目前ART Optimizing則只採用了一層SSA形式的HIR,優化、寄存器分配以及代碼生成全部都在這層IR上進行。它的HIR某種意義上說就像是HotSpot C1的HIR與LIR的融合版,設計得還挺簡潔巧妙。

它的寄存器分配的演算法是在SSA形式上的線性掃描(Linear Scan),論文可以參考HotSpot C1的一個實驗版:

Linear Scan Register Allocation on SSA Form

ART Optimizing的HIR最初只包含平台無關的操作,但發展一段時間之後大家覺得這對平台相關優化不利,所以在某些平台上(例如ARM、ARM64、x86)上HIR還有一些平台相關的擴展操作,以便在HIR層面上也可以做一定的平台相關優化。


要麼 AOT 和 JIT,要麼 ART 和 Dalvik。

ART 和 JIT 是什麼鬼。


ART的編譯與dalvik的jit很相似,也有MIR, SSA, LIR的過程。不過,與dalvik的JIT不同的是,在運行中用到的類、對象、域、方法以及常量的處理方法不一樣。

對於jit來說,這些東西在jit編譯代碼之前,就已經載入到內存了,所以jit可以直接在代碼中使用其絕對地址。對於ART來說,這些東西在編譯時都不存在。不能直接使用。

為了解決這個問題,ART採用了不同的方法。首先,它將所有bootclasspath下面的jar包內的dex文件同一編譯成一個boot.oat和boot.art。其中boot.oat包含了編譯出的代碼。

而boot.art則是一個image文件。即ART將framework/preload-classes文件內列出的class,全部載入到內存中,然後將這些class全部放置在一段內存區間內,然後將該區間內的內存全部寫入到boot.art文件中。

當系統啟動後,ART虛擬機直接將boot.art映射到一個固定的內存地址內,在boot.oat內部的代碼,就直接使用絕對地址訪問boot.art映射後的內存。

這是針對bootclasspath,而對於其他單個的應用程序,就必須老老實實的在用到的時候resolve類、field和method了。

jit還有與art不同的地方在於,jit不是基於一個method進行編譯,而是基於一段hot代碼進行編譯的。jit有幾個過程:1. 首先用計數器跟蹤那塊代碼被執行的次數多;2. 然後以trace方式執行。所謂的trace方式,就是解釋執行一條dalvik指令,就記錄一條指令,知道發生了跳轉、返回等操作;3. 將trace到的這塊代碼,編譯成機器指令。

當然,因為jit不是全部編譯,所以它的棧分配情況、寄存器分配、異常處理及函數調用方式全部都與解釋執行兼容,除了將解釋指令轉換外同等功能的機器碼外,與解釋模式沒有太大區別。

但是ART就沒有這個顧慮。它編譯整個dex文件,所以它重定義了method之間的調用規則,也將充分利用寄存器,能用寄存器保存的數據,不會在保存在棧中。另外,他還根據指令進行優化,刪除一些沒有用的讀寫操作。

比如,指令instance-of vA, vB, class@CCCC, 這樣的dalvik指令,需要將判斷vB是不是class@CCCC的類,並存儲結果到vA。然後從vA讀出數據,在判斷 if-eqz vA , BBBB 。對於art就可以直接省略中間讀寫操作,直接存儲結果到臨時寄存器,然後進行判斷和跳轉。


一個trace based

一個method based


推薦閱讀:

怎麼看小米4擁有3GB內存,卻不能在後台保存一個遊戲的進度?
Android 5.0 下拉菜單中加入「反色」開關是出於什麼考慮?

TAG:即時編譯JIT | Android | 編譯 | AndroidRuntimeART |