|
系列文章導(dǎo)航:
.NET Discovery 系列之一--string從入門到精通(上)
.NET Discovery 系列之二--string從入門到精通(勘誤版下)
.NET Discovery 系列之三--深入理解.NET垃圾收集機(jī)制(上)
.NET Discovery 系列之四--深入理解.NET垃圾收集機(jī)制(下)
.NET Discovery 系列之五--Me JIT(上)
.NET Discovery 系列之六--Me JIT(下)
.NET Discovery 系列之七--深入理解.NET垃圾收集機(jī)制(拾貝篇)
接上文
在初始化時,HashTable中各個方法指向的并不是對應(yīng)的內(nèi)存入口地址,而是一個JIT預(yù)編譯代理,這個函數(shù)負(fù)責(zé)將方法編譯為本地代碼。注意,這里JIT還沒有進(jìn)行編譯,只是建立了方法表!
下表(表1)為首次加載調(diào)用時HashTable的情況:
表1 方法表示意
方法槽 | 方法描述 |
a1() | PreJitStub |
a2() | PreJitStub |
a3() | PreJitStub |
好了有了這個HashTable后,JIT開始編譯第一個被調(diào)用的方法A.a1("First"),這是由一個JIT內(nèi)部函數(shù)來完成的(上面提到的),遺憾的事,目前還沒有發(fā)現(xiàn)介紹這個函數(shù)的相關(guān)資料,有些書中稱它為“JIT編譯者”,那本文也這么稱呼它吧。
下圖為首次調(diào)用方法時的示意圖:
圖2 觸發(fā)JIT編譯
JIT借助元數(shù)據(jù)和IL生成被調(diào)用方法的本地代碼后,會將這些代碼緩存在動態(tài)內(nèi)存中,然后修改HashTable中對應(yīng)方法的入口地址,將其修改為本地代碼的內(nèi)存片地址(如表2所示),并將這個地址返回給CLR經(jīng)行執(zhí)行,A.a1("First")執(zhí)行完畢,代碼繼續(xù)運(yùn)行。
運(yùn)行至A.a1("Second ")時,會直接執(zhí)行A.a1()方法的內(nèi)存代碼,不會進(jìn)行再次編譯,表2 為再次加載時HashTable的情況。
表2 方法表變化
方法槽 | 方法描述 |
a1() | XXXXXXXXX內(nèi)存地址 |
a2() | PreJitStub |
a3() | PreJitStub |
再次加載流程示意圖:
圖3 未觸發(fā)JIT編譯
圖4 方法表、方法描述、預(yù)編譯代理關(guān)系
圖2中所示的MS核心引擎指的是一個叫做MSCorEE的DLL,即Microsoft .NET Runtime Execution Engine,它是一個橋接DLL,連同mscorwks.dll主要完成以下工作:
- 查找程序集中包含的對應(yīng)類型清單,并調(diào)用元數(shù)據(jù)遍歷出包含的方法。
- 結(jié)合元數(shù)據(jù)獲得這個方法的IL。
- 分配內(nèi)存。
- 編譯IL為本地代碼,并保存在第3步所分配的內(nèi)存中。
- 將類型表(就是指上文中提到的HashTable)中方法地址修改為第3步所分配的內(nèi)存地址。
- 跳轉(zhuǎn)至本地代碼中執(zhí)行。
所以隨著程序的運(yùn)行時間增加,越來越多的方法的IL被編譯為本地代碼,JIT的調(diào)用次數(shù)也會不斷減少。
下面借助WinDbg來證實以上的說法,示例中的源程序可以到這里下載到:
http://files.cnblogs.com/isline/IsLine.JITTester.rar
代碼中定義了3個類,分別為A、B、C,在“GO”按鈕按下后,將調(diào)用類型A中的a1()方法,而Form1_Load 中什么也不做,目的是程序運(yùn)行后,在空載的情況下查看方法描述對應(yīng)地址入口的情況。
好,第一步運(yùn)行JITTester.exe程序,并打開WinDbg附加這個進(jìn)程
圖 5 附件進(jìn)程
第二步,附加進(jìn)程成功后,在WinDbg中加載SOS.dll
圖6 加載SOS.dll
第三步,使用name2ee命令遍歷所有已加載模塊,name2ee格式為name2ee *! [程序集].[類型]
圖7 查看類型信息
回車后注意高亮區(qū)域的信息:
圖8 JIT前A類型的信息
高亮區(qū)域顯示的是“”,這說明雖然運(yùn)行和程序,但未點(diǎn)擊按鈕時,A類型未被JIT,因為它還沒有入口地址。這一點(diǎn)體現(xiàn)了即時、按需編譯的思想。
同樣,!name2ee *!JITTester.B和!name2ee *!JITTester.C命令會得到同樣的結(jié)果。
好,現(xiàn)在做第4步操作,Detach Debuggee進(jìn)程,并回到程序中點(diǎn)擊“GO”按鈕
圖9 點(diǎn)擊按鈕
第五步 重新附加進(jìn)程(參考第一步),這時程序已經(jīng)調(diào)用了new A().a1()方法,并重新執(zhí)行命令!name2ee *!JITTester.A ,注意高亮部分
圖10 JIT后A類型的信息
和圖8中的信息比較,圖10中的方法表地址已經(jīng)變?yōu)镴IT后的內(nèi)存地址,這時圖4中的Stub槽將被一條強(qiáng)制跳轉(zhuǎn)語句替換,跳轉(zhuǎn)目標(biāo)與該地址有關(guān)。這一點(diǎn)說明JIT在大多情況下,只編譯一次代碼。
同樣命令查看B類型:
圖11 JIT后B類型的信息
該類型未被調(diào)用,所以還未被JIT。
C類型:
圖12 JIT后C類型的信息
由于實例化A類型時和C類型相關(guān),所以C類型已經(jīng)JIT了。
第三節(jié).Native Image Generator
Native Image Generator中文譯為本地代碼生成器,我更習(xí)慣叫它“本地映像”,因為通過工具NGen.exe生成的本地代碼是無法部分載入的,這意味著操作系統(tǒng)會加載整個程序集文件。
上一節(jié)中提到過,有兩種方法可以獲得本地代碼,JIT方式和Native Image Generator方式,JIT方式是在運(yùn)行時動態(tài)編譯需要的代碼,而NGen.exe會創(chuàng)建托管程序集的本機(jī)映像,并且將該映像安裝到GAC中,運(yùn)行該程序集時,就會自動使用該本機(jī)映像而不是JIT它們。
這聽起來似乎很美妙,但是你必須做好以下準(zhǔn)備:
- 當(dāng)FrameWork版本、CPU類型、操作系統(tǒng)版本發(fā)生變化時,.NET會恢復(fù)JIT機(jī)制。
- NGen.exe工具并不能避免發(fā)布IL,事實上,即使使用NGen.exe工具,CLR依然會使用到元數(shù)據(jù)和IL。
- 忽略了局部性原理(上一節(jié)中提到的),系統(tǒng)會加載整個映像文件到內(nèi)存中,并很可能重定位文件,修正內(nèi)存地址引用。
- NGen.exe生成的代碼無法在運(yùn)行時進(jìn)行優(yōu)化,無法直接訪問靜態(tài)資源,也無法在應(yīng)用程序域之間共享程序集。
此外,JIT不但有編譯的本事,還會根據(jù)內(nèi)存資源情況換出使用率低的代碼,節(jié)省資源,這對于一些基于.NET平臺的電子產(chǎn)品是很重要的。
所以,除非你已十分清楚程序性能是由于首次編譯造成的性能問題,否則盡量不要人工生成本地代碼。
NET技術(shù):.NET Discovery 系列之六--Me JIT(下),轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。