.Net Discovery 系列之五–深入淺出.NetJust-In-Time 編譯機制(上)

來源:互聯網
上載者:User

    歡迎閱讀“.Net Discovery 系列”文章,本文將分上、下兩部分為大家講解.Net JIT方面的知識,敬請雅正。

 

    JIT(Just In Time簡稱JIT)是.Net邊運行邊編譯的一種機制,這種機制的命名來源於豐田汽車在20世紀60年代實行的一種生產方式,中文譯為“準時制”。

    .Net 的JIT編譯器在設計初衷和運行方式來上講,都與豐田汽車的這種“準時生產”思想體系有著很大的相似之處,所以讓我們先來透過“準時生產”方式來理解.Net的JIT機制吧。

    “準時生產”的基本思想可概括為“在需要的時候,按需要的量生產所需的產品”,這正是.Net JIT編譯器的設計初衷,即在需要的時候編譯需要的代碼。

 

    第一節.Me JIT

    以C#為例,在C#代碼運行前,一般會經過兩次編譯,第一階段是C#代碼向MSIL的編譯,第二階段是IL向本地代碼的編譯。第一階段的編譯成果是產生託管模組,第二階段的編譯成果是產生本地代碼以供運行,從這裡各位同學可以看出,第一階段產生的MSIL是不能直接啟動並執行。

這裡先要解釋一下什麼是MSIL和託管模組。

    MSIL:

    MSIL 全稱為Microsoft Intermediate Language,中文譯為“微軟中繼語言”,它是一種介於進階語言和組合語言之間的偽組合語言(姑且這麼叫,各位有不同意見的同學不必激動)。當使用者編譯運行一個.NET程式時,進階語言編譯器會將原始碼翻譯成一組可以獨立於CPU的指令。

    可以看出IL 包括用於載入(ldstr )、儲存(壓棧、彈棧)和初始化對象(locals)以及調用對象方法(call)的指令,還包括用於算術和邏輯運算、控制流程、直接記憶體存取、異常處理和其他動作的指令。

    C#代碼:

string str_test = "test";
System.String Str_test = "test";

  對應IL碼:   

 // 代碼大小       14 (0xe)
     .maxstack 1
     .locals init ([0] string str_test,
     [1] string Str_test)
     IL_0000: nop
     IL_0001: ldstr "test"
     IL_0006: stloc.0
     IL_0007: ldstr "test"
     IL_000c: stloc.1
     IL_000d: ret

    託管模組:

    託管模組(managed module)是一個標準32位或64位Microsoft  Windows可移植可執行體(PE32或PE32+)檔案,託管模組需要CLR才能執行,它包含了上面介紹的IL代碼,還包含中繼資料、PE頭、CLR頭幾部分。

    中繼資料(metadata)可以理解為一個HashTable,Table中映射了內建類型和成員以及引用的類型和成員,這些類型與成員供IL使用,所以中繼資料總是需要關聯對應的IL代碼,編譯器也是同時產生中繼資料與IL,以保證自描述的同步。

    PE頭(Portable Executable,中文譯為可移植的可執行檔)包括了PE32與PE32+,標示了託管模組的運行環境以及JIT最佳化本地代碼時所要用到的資訊,這在後面會講到。

    CLR頭主要包括方法的入口地址標記,以及資源、強命名等資訊,這些資訊是GAC重要的參數依據。

    可以表示出JIT的介入時機:

 

圖1 JIT工作時機

    JIT是運行時的一個重要職責模組,它將IL轉換為本地CPU指令,從可以看出,也許你不敢相信,即時編譯這個過程是在運行時發生的,這會不會對效能產生影響呢?事實上答案是雖然是肯定的,但這種開銷物有所值:

  1. JIT所造成的效能開銷並不顯著。
  2. JIT遵循電腦體系理論中兩個經典理論:局部性原理與8020原則。局部性原理指出,程式總是趨向於使用最近使用過的資料和指令,這包括空間的和時間的,將局部性原理引申可以得出,程式總是趨向於使用最近使用過的資料和指令,以及這些正在使用的資料和指令臨近的資料和指令(憑印象寫的,但不曲解原意);而8020原則指出,系統大多數時間總是花費80%的時間去執行那20%的代碼。   根據這兩個原則,JIT在運行時會即時的向前、後最佳化代碼,這樣的工作只有在運行時才可以做到。
  3. JIT只編譯需要的那一段代碼,而不是全部,這樣節約了不必要的記憶體開銷。
  4. JIT會根據運行時環境,即時的最佳化IL代碼,即同樣的IL代碼運行在不同CPU上,JIT編譯出的本地代碼是不同的,這些不同代碼面向自己的CPU做出了最佳化。
  5. JIT會對代碼的運行情況進行檢測,並對那些特殊的代碼經行重新編譯,在運行過程中不斷最佳化。

    實際上JIT的優點還不止如此,它對內聯、策略引擎(.Net Discovery 系列之四--深入理解.Net垃圾收集機制(下)  中包含對策略引擎的描述)、CLR反饋、代碼回收(非記憶體回收,這在第二節中會有介紹)等方面都會有不可磨滅的貢獻。

    必須指出的是JIT在第一次編譯IL後,會修改對應方法相應的記憶體位址入口(繞口啊~~),下一次需要執行這個方法時,CLR會直接存取對應的記憶體位址,而不會經過JIT了。

 

   第二節.編譯與執行

    在上一節中我們討論了與JIT相關的一些元素和JIT的優勢,這一節將為大家重點介紹JIT在編譯方面的原理。

    C#等進階語言必須被編譯為IL才可被執行,IL在執行前必須被便以為本地代碼才可運行,這裡有兩種方法可以獲得本地代碼,JIT方式和Native Image Generator方式,本節主要討論JIT方式。

    必須指出的是JIT在第一次編譯IL後,會修改對應方法相應的記憶體位址入口,下一次需要執行這個方法時,CLR會直接存取對應的記憶體位址,而不會經過JIT了,這樣無疑加快了程式啟動並執行速度,這是怎樣的一個過程呢?

    以Load()方法為例,假如Load()方法中調用了兩次同類型中的方法:

 

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){}

}

 

    運行時,作業系統會根據託管模組中各種頭資訊,裝載相應的運行時架構,Load()被載入,由於是第一次載入,這會觸發對Load()的即時編譯,JIT會檢測Load()中引用的所有類型,並結合中繼資料遍曆這些類型中定義的所有方法實現,並用一個特殊的HashTable(僅用於理解)儲存這些類型方法與其對應的入口地址(在未被JIT前,這個入口地址為一個先行編譯代理(PreJitStub),這個代理負責觸發JIT編譯),根據這些地址,就可以找到對應的方法實現。

 

    未完待續

    我是李鳴(Aicken) 請您繼續關注我的下一篇文章。

 

    “.Net Discovery 系列”推薦:

     .Net Discovery 系列之三--深入理解.Net垃圾收集機制(上)

     .Net Discovery 系列之四--深入理解.Net垃圾收集機制(下)

     .Net Discovery 系列之一--string從入門到精通(上)

     .Net Discovery 系列之二--string從入門到精通(下)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.