.Net Compact Framework CLR設(shè)計(jì)系列之JIT編譯器講解
這個(gè).Net Compact Framework CLR設(shè)計(jì)這個(gè)話題被很多人關(guān)注?,F(xiàn)在我們可以從設(shè)計(jì)者的角度,深入了解.Net Compact Framework CLR設(shè)計(jì)的內(nèi)部結(jié)構(gòu)。這部分我們要討論JIT編譯器的知識(shí)。感謝Steven Pratschner,感謝他給我們帶來了這么好的文章。
設(shè)計(jì)JIT編譯器
.Net Compact Framework CLR設(shè)計(jì)的***章,以后我們還會(huì)為大家提供更多的,希望大家關(guān)注。.Net Compact Framework的JIT編譯器與.Net Framework***的不同在于內(nèi)存使用。在內(nèi)存緊張的情況下,.Net Compact Framework可以釋放Jitted代碼,將內(nèi)存返還給操作系統(tǒng)。正如你所預(yù)料的那樣,如此設(shè)計(jì)的原因是因?yàn)橛糜诖鎯?chǔ)jitted代碼的堆是分配在應(yīng)用程序私有的32MB地址空間上的(更多信息可以參考***部分)。除了私有地址空間非常小之外,考慮到它們從來不被分頁,在內(nèi)存受限設(shè)備上運(yùn)行程序,必要時(shí)減少空間壓力的設(shè)計(jì)是絕對(duì)必要的。
當(dāng)程序被執(zhí)行時(shí),JIT編譯器會(huì)在堆上分配內(nèi)存,用來存儲(chǔ)每個(gè)方法編譯生成的本地代碼。因?yàn)榫幾g和內(nèi)存分配發(fā)生在每個(gè)方法運(yùn)行的時(shí)候,每次內(nèi)存分配都會(huì)讓堆相應(yīng)減少。換句話說,就是JIT堆在小幅度地逐漸增長(zhǎng)。在程序運(yùn)行過程中,JIT堆會(huì)增長(zhǎng)到很大的程度。在Compact Framework的早期版本中,JIT堆的尺寸被限制在一個(gè)固定的大小中。在第二版中,這個(gè)限制已經(jīng)被去掉了,因此在新方法需要被編譯時(shí),堆會(huì)增加。
三種情況的發(fā)生,會(huì)導(dǎo)致JIT堆的大部分空間被釋放并將內(nèi)存歸還給OS(這里只所以說“大部分空間”是因?yàn)镃ompact Framework必須始終保留當(dāng)前執(zhí)行應(yīng)用程序方法的jitted代碼)。首先,如果CLR試圖分配更多內(nèi)存時(shí),收到一個(gè)來自操作系統(tǒng)的錯(cuò)誤,JIT堆將會(huì)收縮。CLR會(huì)認(rèn)為這個(gè)失敗表明可用內(nèi)存數(shù)量不足,于是盡可能多的揮手JIT堆中的代碼。從JIT堆中釋放本地代碼的動(dòng)作是根據(jù)代碼存在期限決定的。其次,當(dāng)一個(gè)程序被切換到后臺(tái)時(shí),代碼會(huì)被回收。在Windows Mobile中,應(yīng)用程序通常不會(huì)被關(guān)閉,但是會(huì)被切換到后臺(tái)。當(dāng)一個(gè)程序被切換到后臺(tái)時(shí),通過釋放代碼,CLR可以獲得更多可用內(nèi)存供前臺(tái)程序使用,這樣可以增加同時(shí)運(yùn)行在設(shè)備上應(yīng)用程序的數(shù)量。***,當(dāng)一個(gè)托管應(yīng)用程序收到來自Windows CE的WM_HIBERNATE消息時(shí),CLR會(huì)回收jitted代碼。當(dāng)OS發(fā)現(xiàn)運(yùn)行的系統(tǒng)資源過低時(shí),會(huì)發(fā)出WM_HIBERNATE消息。當(dāng)設(shè)備資源缺乏時(shí),響應(yīng)WM_HIBERNATE消息的代碼回收是CLR釋放內(nèi)存和其他資源操作的一部分。
我在稍后章節(jié)討論自動(dòng)內(nèi)存管理時(shí),你將會(huì)看到,代碼回收是整個(gè)垃圾收集的一部分。
Figure 1The size of the JIT heap over the lifetime of an application.
圖1中的一些情況是十分值得注意的。首先,圖中的兩個(gè)低點(diǎn)發(fā)生的時(shí)間,對(duì)應(yīng)于程序被切換到后臺(tái)和堆的尺寸太大而開始代碼回收的時(shí)間。同樣,注意程序啟動(dòng)的時(shí)候比程序從后臺(tái)切換回來時(shí),會(huì)有更多代碼被jitted。這大概是因?yàn)閼?yīng)用程序包含一些初始化代碼,而這些代碼只是在程序開始時(shí)被調(diào)用。
因?yàn)镃LR會(huì)在內(nèi)存緊張或者程序切換到后臺(tái)時(shí)丟棄本地代碼,所以在程序繼續(xù)運(yùn)行時(shí),相同的IL代碼會(huì)被再次JIT編譯。正因?yàn)槿绱宋覀儾抛鞒隽说诙€(gè)關(guān)于JIT編譯器的設(shè)計(jì)決定:編譯IL代碼的時(shí)間通常優(yōu)先于生成本地代碼的質(zhì)量。作為一個(gè)優(yōu)秀的編譯器,Compact Framework JIT編譯器做了一些基本的優(yōu)化,但是為了讓應(yīng)用程序保持響應(yīng),就需要更快地生成代碼,更多地優(yōu)化措施要根據(jù)其速度來決定是否執(zhí)行。
JIT編譯器***一個(gè)關(guān)鍵設(shè)計(jì)原則是不涉及內(nèi)存使用,這樣做是為了讓JIT編譯器更方便移植。我在***部分曾提到,Compact Framework的運(yùn)行環(huán)境不僅要求它能夠在內(nèi)存受限設(shè)備上運(yùn)行,而且需要它可以在不同的處理器上運(yùn)行。.Net Compact Framework目前可以運(yùn)行在包括x86、Arm、SH和MIPS等處理器上,而且還可以根據(jù)要求支持更多的處理器。因?yàn)樾枰缭讲煌I(lǐng)域的設(shè)備,JIT編譯器被設(shè)計(jì)成花費(fèi)最少時(shí)間,便可以支持一種新的處理器類型的架構(gòu)。一種技術(shù)被用來增加可移植性,就是將處理器相關(guān)的操作限制在最小程度。
Why no Native Images?
桌面版的.Net Framework使用一種叫“本地映像”的技術(shù),當(dāng)應(yīng)用程序加載時(shí),IL代碼都需要被JIT編譯器編譯。如果利用了本地映像技術(shù),應(yīng)用程序可以更快地啟動(dòng)。本地映像是一個(gè)保存在硬盤上的文件,包含被編譯好的IL代碼。當(dāng).Net Framework安裝時(shí),它會(huì)調(diào)用JIT編譯器生成mscorlib、System.Windows.Forms等類庫的本地CPU指令。當(dāng)應(yīng)用程序啟動(dòng)時(shí),就可以直接調(diào)用儲(chǔ)存在本地映像中的已經(jīng)生成的本地代碼,從而節(jié)省了JIT編譯這些程序集的時(shí)間。用戶也可以為自己編寫的程序集產(chǎn)生本地映像(可以參考.Net Framework SDK中ngen.exe工具的文檔)。
.Net Compact Framework沒有使用本地映像的主要原因是他們的尺寸。根據(jù)本地指令集的不同,一個(gè)程序集被JIT編譯后產(chǎn)生的本地代碼大小大約是IL代碼的三到四倍。在壓縮之后,.Net Compact Framework類庫大約是4.5MB。如果相應(yīng)的本地映像是這個(gè)尺寸的四倍的話,你就會(huì)看到本地映像需要的存儲(chǔ)空間是設(shè)備中可用內(nèi)存中相當(dāng)大的一部分。另外一種可能就是可以將本地映像保存在外部存儲(chǔ)卡上。可是從存儲(chǔ)卡讀取本地映像文件的速度是非常慢的,因此我們不確定啟動(dòng)時(shí)間可以通過本地映像的方法可以縮短。
以上介紹.Net Compact Framework CLR設(shè)計(jì),Compact Framework JIT團(tuán)隊(duì)會(huì)在每個(gè)主要版本中考慮本地映像,也許在CLR的未來某個(gè)版本中,我們會(huì)看到本地映像,或者類似的技術(shù)。
【編輯推薦】