.Net Discovery系列之十一-深入理解平台机制与性能影响 (中)
???上一篇文章中Aicken為大家介紹了.Net平臺(tái)的垃圾回收機(jī)制與其對性能的影響,這一篇中將繼續(xù)為大家介紹.Net平臺(tái)的另一批黑馬—JIT。
???有關(guān)JIT的機(jī)制分析
???● 機(jī)制分析
???以C#為例,在C#代碼運(yùn)行前,一般會(huì)經(jīng)過兩次編譯,第一階段是C#代碼向MSIL的編譯,第二階段是IL向本地代碼的編譯。第一階段的編譯成果是生成托管模塊,第二階段的編譯成果是生成本地代碼以供運(yùn)行,從這里各位同學(xué)可以看出,第一階段生成的MSIL是不能直接運(yùn)行的。必須指出的是JIT在第一次編譯IL后,會(huì)修改對應(yīng)方法相應(yīng)的內(nèi)存地址入口,下一次需要執(zhí)行這個(gè)方法時(shí),CLR會(huì)直接訪問對應(yīng)的內(nèi)存地址,而不會(huì)經(jīng)過JIT了。
???以Load()方法為例,假如Load()方法中調(diào)用了兩次同類型中的方法:
Void Load()
{
???A.a1("First");
???A.a1("Second");
}
static class A
{
???Public void a1(string str){}
???Public void a2(string str){}
???Public void a3(string str){}
}
???運(yùn)行時(shí),操作系統(tǒng)會(huì)根據(jù)托管模塊中各種頭信息,裝載相應(yīng)的運(yùn)行時(shí)框架,Load()被加載,由于是第一次加載,這會(huì)觸發(fā)對Load()的即時(shí)編譯,JIT會(huì)檢測Load()中引用的所有類型,并結(jié)合元數(shù)據(jù)遍歷這些類型中定義的所有方法實(shí)現(xiàn),并用一個(gè)特殊的HashTable(僅用于理解)儲(chǔ)存這些類型方法與其對應(yīng)的入口地址(在未被JIT前,這個(gè)入口地址為一個(gè)預(yù)編譯代理(PreJitStub),這個(gè)代理負(fù)責(zé)觸發(fā)JIT編譯),根據(jù)這些地址,就可以找到對應(yīng)的方法實(shí)現(xiàn)。
???在初始化時(shí),HashTable中各個(gè)方法指向的并不是對應(yīng)的內(nèi)存入口地址,而是一個(gè)JIT預(yù)編譯代理,這個(gè)函數(shù)負(fù)責(zé)將方法編譯為本地代碼。注意,這里JIT還沒有進(jìn)行編譯,只是建立了方法表!
?
圖2方法表、方法描述、預(yù)編譯代理關(guān)系
???圖2中所示的MS核心引擎指的是一個(gè)叫做MSCorEE的DLL,即Microsoft .NET Runtime Execution Engine,它是一個(gè)橋接DLL,連同mscorwks.dll主要完成以下工作:
???1.查找程序集中包含的對應(yīng)類型清單,并調(diào)用元數(shù)據(jù)遍歷出包含的方法。
???2.結(jié)合元數(shù)據(jù)獲得這個(gè)方法的IL。
???3.分配內(nèi)存。
???4.編譯IL本地代碼,并保存在第3步所分配的內(nèi)存中。
???5.將類型表(就是指上文中提到的HashTable)中方法地址修改為第3步所分配的內(nèi)存地址。
???6.跳轉(zhuǎn)至本地代碼中執(zhí)行。
???所以隨著程序的運(yùn)行時(shí)間增加,越來越多的方法的IL被編譯為本地代碼,JIT的調(diào)用次數(shù)也會(huì)不斷減少。
???下面借助WinDbg來證實(shí)以上的說法,加載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 = "調(diào)用完畢!";
??????}
???}
???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() { }
???}
}
???使用name2ee命令遍歷所有已加載模塊,如下圖:
?
圖3 查看類型信息
???回車后注意高亮區(qū)域的信息:
?
圖4 JIT前A類型的信息
???高亮區(qū)域顯示的是“”,這說明雖然運(yùn)行和程序,但未點(diǎn)擊按鈕時(shí),A類型未被JIT,因?yàn)樗€沒有入口地址。這一點(diǎn)體現(xiàn)了即時(shí)、按需編譯的思想。
???同樣,!name2ee *!JITTester.B和!name2ee *!JITTester.C命令會(huì)得到同樣的結(jié)果。
???好,現(xiàn)在繼續(xù),Detach Debuggee進(jìn)程,并回到程序中點(diǎn)擊“GO”按鈕
?
圖5 點(diǎn)擊按鈕
???然后重新附加進(jìn)程,這時(shí)程序已經(jīng)調(diào)用了new A().a1()方法,并重新執(zhí)行令!name2ee *!JITTester.A ,注意高亮部分
?
圖6 JIT后A類型的信息>
???和圖4中的信息比較,圖6中的方法表地址已經(jīng)變?yōu)镴IT后的內(nèi)存地址,這時(shí)圖2中的Stub槽將被一條強(qiáng)制跳轉(zhuǎn)語句替換,跳轉(zhuǎn)目標(biāo)與該地址有關(guān)。這一點(diǎn)說明JIT在大多情況下,只編譯一次代碼。
???同樣命令查看B類型:
?
圖7 JIT后B類型的信息
???該類型未被調(diào)用,所以還未被JIT。
???C類型:
?
圖8 JIT后C類型的信息
???由于實(shí)例化A類型時(shí)和C類型相關(guān),所以C類型已經(jīng)JIT了。
???這就是一個(gè)類型被JIT的全部過程。
???● 性能影響分析
???通過以上的分析,大家已經(jīng)能夠了解,即時(shí)編譯這個(gè)過程是在運(yùn)行時(shí)發(fā)生的,這會(huì)不會(huì)對性能產(chǎn)生影響呢?事實(shí)上答案是雖然是肯定的,但這種開銷物有所值,并且如上所說的,JIT在第一次編譯IL后,會(huì)修改對應(yīng)方法相應(yīng)的內(nèi)存地址入口(繞口啊~~),下一次需要執(zhí)行這個(gè)方法時(shí),CLR會(huì)直接訪問對應(yīng)的內(nèi)存地址,而不會(huì)經(jīng)過JIT了。
???1.JIT所造成的性能開銷并不顯著。
???2.JIT遵循計(jì)算機(jī)體系理論中兩個(gè)經(jīng)典理論:局部性原理與8020原則。局部性原理指出,程序總是趨向于使用最近使用過的數(shù)據(jù)和指令,這包括空間的和時(shí)間的,將局部性原理引申可以得出,程序總是趨向于使用最近使用過的數(shù)據(jù)和指令,以及這些正在使用的數(shù)據(jù)和指令臨近的數(shù)據(jù)和指令(憑印象寫的,但不曲解原意);而8020原則指出,系統(tǒng)大多數(shù)時(shí)間總是花費(fèi)80%的時(shí)間去執(zhí)行那20%的代碼。
???根據(jù)這兩個(gè)原則,JIT在運(yùn)行時(shí)會(huì)實(shí)時(shí)的向前、后優(yōu)化代碼,這樣的工作只有在運(yùn)行時(shí)才可以做到。
???3.JIT只編譯需要的那一段代碼,而不是全部,這樣節(jié)約了不必要的內(nèi)存開銷。
???4.JIT會(huì)根據(jù)運(yùn)行時(shí)環(huán)境,即時(shí)的優(yōu)化IL代碼,即同樣的IL代碼運(yùn)行在不同CPU上,JIT編譯出的本地代碼是不同的,這些不同代碼面向自己的CPU做出了優(yōu)化。
???5.JIT會(huì)對代碼的運(yùn)行情況進(jìn)行檢測,并對那些特殊的代碼經(jīng)行重新編譯,在運(yùn)行過程中不斷優(yōu)化。
???此外你可以利用NGen.exe創(chuàng)建托管程序集的本機(jī)映像,運(yùn)行該程序集時(shí),就會(huì)自動(dòng)使用該本機(jī)映像而不是JIT它們。這聽起來似乎很美妙,但是你必須做好以下準(zhǔn)備:
???1.當(dāng)FrameWork版本、CPU類型、操作系統(tǒng)版本發(fā)生變化時(shí),.Net會(huì)恢復(fù)JIT機(jī)制。
???2.NGen.exe工具并不能避免發(fā)布IL,事實(shí)上,即使使用NGen.exe工具,CLR依然會(huì)使用到元數(shù)據(jù)和IL。
???3.忽略了局部性原理(上一節(jié)中提到的),系統(tǒng)會(huì)加載整個(gè)映像文件到內(nèi)存中,并很可能重定位文件,修正內(nèi)存地址引用。
???4.NGen.exe生成的代碼無法在運(yùn)行時(shí)進(jìn)行優(yōu)化,無法直接訪問靜態(tài)資源,也無法在應(yīng)用程序域之間共享程序集。
???所以,除非你已十分清楚程序性能是由于首次編譯造成的性能問題,否則盡量不要人工生成本地代碼。
???JIT很優(yōu)秀,它不但有編譯的本事,還會(huì)根據(jù)內(nèi)存資源情況換出使用率低的代碼,節(jié)省資源,這對于一些基于.Net平臺(tái)的電子產(chǎn)品是很重要的。基于B/S模式運(yùn)行的系統(tǒng),如果使用率較高,可以基本忽略JIT帶來的性能損失,因?yàn)楦鶕?jù)局部性原理與8020原則,常用的模塊都是編譯完畢的,只有那些不常用的模塊,在第一次使用時(shí)會(huì)被編譯,并損失用一些時(shí)間。
未完
????? 下一篇中,Aicken將會(huì)為大家介紹.Net Exception機(jī)制、字符串駐留機(jī)制以及其帶來的性能問題,敬請期待。
????? 我是Aicken(李鳴) 請您繼續(xù)關(guān)注我的下一篇文章。
?
“.Net Discovery 系列”是講解.Net平臺(tái)本質(zhì)的文章,現(xiàn)在已經(jīng)有:
??? .Net Discovery 系列之七--深入理解.Net垃圾收集機(jī)制(拾貝篇) 發(fā)布在新年第一秒
??? .Net Discovery 系列之五--Me JIT(深入淺出.Net JIT 上)
??? .Net Discovery 系列之六--Me JIT(深入淺出.Net JIT 下)
??? .Net Discovery 系列之三--深入理解.Net垃圾收集機(jī)制(上)
??? .Net Discovery 系列之四--深入理解.Net垃圾收集機(jī)制(下)
??? .Net Discovery 系列之一--string從入門到精通(上)
??? .Net Discovery 系列之二--string從入門到精通(下)
轉(zhuǎn)載于:https://www.cnblogs.com/isline/archive/2010/04/07/1705966.html
總結(jié)
以上是生活随笔為你收集整理的.Net Discovery系列之十一-深入理解平台机制与性能影响 (中)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分割字符串
- 下一篇: 【.Net Micro Framewor