程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> .Net Discovery系列之五 Me JIT(上)

.Net Discovery系列之五 Me JIT(上)

編輯:關於.NET

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指令,從上圖可以看出,也許你不敢相信,即時編譯這個過程是在運行時發生的,這會不會對性能產生影響呢?事實上答案是雖然是肯定的,但這種開銷物有所值:

JIT所造成的性能開銷並不顯著。

JIT遵循計算機體系理論中兩個經典理論:局部性原理與8020原則。局部性原理指出,程序總是趨向於使用最近使用過的數據和指令,這包括空間的和時間的,將局部性原理引申可以得出,程序總是趨向於使用最近使用過的數據和指令,以及這些正在使用的數據和指令臨近的數據和指令(憑印象寫的,但不曲解原意);而8020原則指出,系統大多數時間總是花費80%的時間去執行那20%的代碼。   根據這兩個原則,JIT在運行時會實時的向前、後優化代碼,這樣的工作只有在運行時才可以做到。

JIT只編譯需要的那一段代碼,而不是全部,這樣節約了不必要的內存開銷。

JIT會根據運行時環境,即時的優化IL代碼,即同樣的IL代碼運行在不同CPU上,JIT編譯出的本地代碼是不同的,這些不同代碼面向自己的CPU做出了優化。

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編譯),根據這些地址,就可以找到對應的方法實現。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved