.Net Discovery 系列之六--深入浅出.Net实时编译机制(下)
????接上文
??? 在初始化時,HashTable中各個方法指向的并不是對應的內存入口地址,而是一個JIT預編譯代理,這個函數負責將方法編譯為本地代碼。注意,這里JIT還沒有進行編譯,只是建立了方法表!
??? 下表(表1)為首次加載調用時HashTable的情況:
?????????????????????表1 方法表示意
| 方法槽 | 方法描述 |
| a1() | PreJitStub |
| a2() | PreJitStub |
| a3() | PreJitStub |
??? 好了有了這個HashTable后,JIT開始編譯第一個被調用的方法A.a1("First"),這是由一個JIT內部函數來完成的(上面提到的),遺憾的事,目前還沒有發現介紹這個函數的相關資料,有些書中稱它為“JIT編譯者”,那本文也這么稱呼它吧。
??? 下圖為首次調用方法時的示意圖:
?
?
圖2 觸發JIT編譯
??? JIT借助元數據和IL生成被調用方法的本地代碼后,會將這些代碼緩存在動態內存中,然后修改HashTable中對應方法的入口地址,將其修改為本地代碼的內存片地址(如表2所示),并將這個地址返回給CLR經行執行,A.a1("First")執行完畢,代碼繼續運行。
??? 運行至A.a1("Second ")時,會直接執行A.a1()方法的內存代碼,不會進行再次編譯,表2 為再次加載時HashTable的情況。
????????????? 表2 方法表變化
| 方法槽 | 方法描述 |
| a1() | XXXXXXXXX內存地址 |
| a2() | PreJitStub |
| a3() | PreJitStub |
??? 再次加載流程示意圖:
?
圖3 未觸發JIT編譯
?
?
圖4 方法表、方法描述、預編譯代理關系
?
??? 圖2中所示的MS核心引擎指的是一個叫做MSCorEE的DLL,即Microsoft .NET Runtime Execution Engine,它是一個橋接DLL,連同mscorwks.dll主要完成以下工作:
??? 所以隨著程序的運行時間增加,越來越多的方法的IL被編譯為本地代碼,JIT的調用次數也會不斷減少。
????? 下面借助WinDbg來證實以上的說法,示例中的源程序可以到這里下載到:
????? http://files.cnblogs.com/isline/IsLine.JITTester.rar
?
namespace JITTester{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void GO_Click(object sender, EventArgs e)
{
new A().a1();
lb_msg.Text = "調用完畢!";
}
}
class A
{
public void a1() { }
public C a2 = new C();
}
class B
{
public void b1() { }
public void b2() { }
}
class C
{
public void c1() { }
public void c2() { }
}
}
?
????? 代碼中定義了3個類,分別為A、B、C,在“GO”按鈕按下后,將調用類型A中的a1()方法,而Form1_Load 中什么也不做,目的是程序運行后,在空載的情況下查看方法描述對應地址入口的情況。
??? 好,第一步運行JITTester.exe程序,并打開WinDbg附加這個進程
?
圖 5 附件進程
?
?? 第二步,附加進程成功后,在WinDbg中加載SOS.dll
?
圖6 加載SOS.dll
??? 第三步,使用name2ee命令遍歷所有已加載模塊,name2ee格式為name2ee *! [程序集].[類型]
?
圖7 查看類型信息
??? 回車后注意高亮區域的信息:
?
圖8 JIT前A類型的信息
??? 高亮區域顯示的是“<not loaded yet>”,這說明雖然運行和程序,但未點擊按鈕時,A類型未被JIT,因為它還沒有入口地址。這一點體現了即時、按需編譯的思想。
?? 同樣,!name2ee *!JITTester.B和!name2ee *!JITTester.C命令會得到同樣的結果。
??? 好,現在做第4步操作,Detach Debuggee進程,并回到程序中點擊“GO”按鈕
?
?
圖9 點擊按鈕
?
??? 第五步 重新附加進程(參考第一步),這時程序已經調用了new A().a1()方法,并重新執行命令!name2ee *!JITTester.A ,注意高亮部分
?
圖10 JIT后A類型的信息
??? 和圖8中的信息比較,圖10中的方法表地址已經變為JIT后的內存地址,這時圖4中的Stub槽將被一條強制跳轉語句替換,跳轉目標與該地址有關。這一點說明JIT在大多情況下,只編譯一次代碼。
??? 同樣命令查看B類型:
?
圖11 JIT后B類型的信息
??? 該類型未被調用,所以還未被JIT。
??? C類型:
?
圖12 JIT后C類型的信息
?
??? 由于實例化A類型時和C類型相關,所以C類型已經JIT了。
??? 第三節.Native Image Generator
??? Native Image Generator中文譯為本地代碼生成器,我更習慣叫它“本地映像”,因為通過工具NGen.exe生成的本地代碼是無法部分載入的,這意味著操作系統會加載整個程序集文件。
??? 上一節中提到過,有兩種方法可以獲得本地代碼,JIT方式和Native Image Generator方式,JIT方式是在運行時動態編譯需要的代碼,而NGen.exe會創建托管程序集的本機映像,并且將該映像安裝到GAC中,運行該程序集時,就會自動使用該本機映像而不是JIT它們。
這聽起來似乎很美妙,但是你必須做好以下準備:
??? 此外,JIT不但有編譯的本事,還會根據內存資源情況換出使用率低的代碼,節省資源,這對于一些基于.Net平臺的電子產品是很重要的。?
??? 所以,除非你已十分清楚程序性能是由于首次編譯造成的性能問題,否則盡量不要人工生成本地代碼。?
?
??? 我是李鳴(Aicken) 請您繼續關注我的下一篇文章。
?
????“.Net Discovery 系列”推薦:
???? .Net Discovery 系列之五--Me JIT(上)
???? .Net Discovery 系列之三--深入理解.Net垃圾收集機制(上)
??? ?.Net Discovery 系列之四--深入理解.Net垃圾收集機制(下)
??? ?.Net Discovery 系列之一--string從入門到精通(上)
??? ?.Net Discovery 系列之二--string從入門到精通(下)
總結
以上是生活随笔為你收集整理的.Net Discovery 系列之六--深入浅出.Net实时编译机制(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网页如何调用flash的方法
- 下一篇: IT技术革命