1. 程式人生 > >dexopt優化和驗證Dalvik (Dalvik Optimization and Verification With dexopt)

dexopt優化和驗證Dalvik (Dalvik Optimization and Verification With dexopt)

原文詳見:http://www.netmite.com/android/mydroid/dalvik/docs/dexopt.html

dalvik的設計的初衷就是執行在像Android這樣的小RAM,低速度flash memory,執行標準Linux系統的裝置。針對這樣的平臺特性,要想做到更好,我們需要考慮以下幾點:

1)為了減少系統的記憶體使用,位元組碼可以多程序共享。但出於安全性考慮,這樣的位元組碼不可以編輯。

2)為了保證響應速度,載入一個新的APP所需時間儘量少。

3)標準Java中把多個類檔案分別存放導致了大量的冗餘,為了節省APP的佔用空間,這個問題要解決。

4)載入類的時候解析類的欄位成員會導致額外的消耗,如果改成像C一樣直接訪問會比較好。

5)位元組碼verification很有必要,但很慢,我們需要把驗證與APP執行分開。

6)位元組碼optimization(比如指令優化、方法pruning)可以在很大程度上影響執行速度和電池消耗。

標準VM都是程式啟動時把每單個的類檔案解壓放入heap,每個程序都有一份copy。這樣的做法在記憶體佔用和時間上面都有損失,但方便了對指令的優化。

現在看看dalvik是怎麼做的:

1)多個類被整合進單一的DEX檔案。

2)DEX檔案在程序間以只讀方式共享。

3)byte ordering和word alignment根據local system來做調整。

4)位元組碼verification儘可能提前。

5)需要修改位元組碼的optimization必須提前進行。

這樣做的好處在下面一一介紹。

VM Operation

系統中的應用程式程式碼以.jar或.apk檔案存在。其實它們都是.zip的文件,只不過多了一些檔案頭資訊。DEX檔案也就是解壓.apk後的classes.dex檔案。classes.dex中的位元組碼是經過壓縮處理的,而且檔案頭部不一定是word aligned,所以不能直接mmap到記憶體直接執行,而是先解壓,然後做一些realignment,optimization,verification操作。下面詳細介紹一下這個過程。

Preparation

做到DEX檔案的執行前優化(優化後的DEX叫做ODEX,Optimization DEX),至少有三種方式:

1)VM的JIT技術。優化後的檔案放在/data/dalvik-cache目錄下。這種方式在模擬器和eng模式下編譯的系統中有效,只有這兩種情況下操作dalvik-cache目錄才不會有許可權問題。

2)安裝應用程式時,system installer做優化。這需要dalvik-cache目錄的寫許可權。

3)編譯系統原始碼時進行優化。這樣優化不會修改jar/apk檔案,但會對classes.dex進行優化,優化後的DEX與原檔案放在同一個目錄下一起寫入system image。

系統中的/data/dalvik-cache目錄屬於system/system,許可權是0771。儲存在這個目錄下的ODEX檔案被system和應用程式所屬的group擁有,許可權是0644。DRM-locked的應用程式使用640許可權。底線是你可以讀你自己的DEX檔案和其它的大多數應用程式,但不能建立、修改或刪除它們。

使用JIT和system installer做DEX檔案的Preparation要分成三步:

1)由system installer建立dalvik-cache資料夾,這個程式執行在有root許可權的installd程序中。

2)classes.dex被解壓出來,並在檔案頭部預留一些空間存放ODEX頭資訊。

3)為了方便使用和做一些針對特定系統的微調,把它mmap。比如byte-swapping,structure realigning等。我們還會做一些像檔案偏移量和資料索引是否越界等方面的基本檢查。

編譯系統使用一個很複雜的流程來做這些事:啟動模擬器,強制對所有相關DEX檔案執行JIT優化,最後把優化後的結果從dalvik-cache中提取出來。之所以這樣做而不是在PC上面使用一個工具來完成,在後面解釋Optimization時可以看到原因。

當代碼的byte-swapping和align完成時,我們的preparation就完成了。再做完verification和optimization,最後,我們就會把一些相關計算出來的資訊新增到ODEX檔案的頭部然後開始執行。

dexopt

其實,如果我們想優化DEX中的類檔案的話,最簡單最安全的辦法就是把所有類載入到VM中然後執行一遍,執行失敗的就是沒有verification和optimization的。但是,這樣會分配一些很難釋放的資源。比如,載入本地庫時。所以,不能使用執行程式的那個VM來做。

我們的解決方案就是使用dexopt這個程式,它會初始化一個VM,載入DEX檔案並執行verification和optimization過程。完成後,程序退出,釋放所有資源。這個過程中,也可以多個VM使用同一個DEX。file lock會讓dexopt只執行一次。

verification

位元組碼的verification過程涉及到每個DEX檔案中的所有類和類中的所有方法中的指令。目標就是檢查非法指令序列,這樣做完以後,執行的時候就不必管了。這個過程中涉及到的許多計算也存在於GC過程中。

出於效率上的考慮,下一節提到的optimization會假設verification已經成功執行通過。預設情況下,dalvik會對所有類進行verification,而只對verification成功的類執行optimization。在進行verification過程中出現失敗時,我們不一定會報告(比如在不同的包中呼叫一個作用範圍為包內的類),我們會在執行時丟擲一個異常。因為檢查每個方法的訪問許可權很慢。

執行verification成功的類在ODEX檔案中有一個flag set,當它們被載入時,就不會再進行verification。linux系統的安全機制會防止這個檔案被破壞,但如果你能繞過去,還是能去破壞它的。ODEX檔案有一個32-bit的checksum,但只能做一個快速檢查。

Optimization

VM直譯器在第一次執行一段程式碼時會做一些optimization。比如,把常量池引用替換成指向內部資料結構的指標,一些永遠成功的操作或固定的程式碼被替換成更簡單的形式。做這些optimization需要的資訊有的只能在執行時得到,有的可以推斷出來。

dalvik做的optimization包含下面這些:

1)對於虛方法的呼叫,把方法索引修改成vtable索引。

2)把field的get/put修改成位元組偏移量。把boolean/byte/char/short等型別的變數合併到一個32-bit的形式,更少的程式碼可以更有效地利用CPU的I-cache。

3)把一些大量使用的簡單方法進行inline,比如String.length()。這樣能減少方法呼叫的開銷。

4)刪除空方法。

5)加入一些計算好的資料。比如,VM需要一個hash table來查詢類名字,我們就可以在Optimization階段進行計算,不用放到DEX載入的時候了。

所有的指令修改都是使用一個Dalvik標準沒有定義的指令去替換原有指令。這樣,我們就可以讓優化和沒有優化的指令自由搭配。具體的操作與VM版本有關。

Optimization過程有兩個地方需要我們注意:

1)VM如果更新的話,vtable索引和位元組偏移量可能會更新。

2)如果兩個DEX互相信賴,而其中一個DEX更新的話,確保優化後的索引和偏移量有效。

Dependencies and Limitations

優化後的DEX會包含一個它信賴的DEX檔案列表,並添加了CRC-32和修改時間。檔案列表中包含了dalvik-cache目錄下的檔案的路徑和相應的SHA-1簽名。而檔案在裝置上的timestamp不可信也不能用。另外還有VM版本號。

如果當前DEX所依賴的DEX有更新,我們也需要更新當前DEX。如果我們可以做一個JIT的dexopt呼叫,更新過程很easy。但如果只能依賴installer daemon,或者這個DEX被裝到ODEX中的話,VM只能拒絕它了。

dexopt的輸出與平臺版本,VM版本有關,想編寫一個執行在PC上,而優化後的輸出在其它裝置使用的dexopt很難。因此,dexopt是在目標裝置上或者目標裝置的模擬器上執行。