深入解析JIT編譯
你知道JIT這個技術術語嗎?JIT的全稱是Just-In-Time,即時的意思。JIT編譯器是執行JIT編譯的工具,它是.NET應用程式的一個重要特性,今天我們來深入分析一下。
如何讓託管應用程式可移植?
我們知道.NET Framework和CLR(通用語言執行庫)為針對該平臺的應用程式提供了很多有用的功能,比如自動記憶體管理。不過,提出託管執行時的一個主要目的就是實現應用的可移植。
那麼應用程式是可移植的意味著什麼呢?這意味著,首先,它可以在任何型別的硬體上執行。理想情況下,它也應該與軟體無關(特別是與作業系統無關),例如微軟建立的ans正在積極開發一個多平臺的 .NET Core。
這種可移植性不僅使應用程式可以在任何硬體或軟體平臺上啟動,而且還使開發人員不必關心底層的低階結構。例如,當使用TPL(任務並行庫)時,程式設計師通常不需要考慮底層硬體(例如CPU的數量或體系結構)來修改程式碼。對於記憶體分配來說,它們是相同的,其中許多CTM(低階語言程式設計介面)細節根據作業系統的體系結構(32/64位)而有所不同,CLR對其進行處理。
但是,在某些時候,每個應用程式都需要由處理器執行,這需要有機器碼,這樣彙編指令才能理解並且能夠由CPU執行。根據作業系統或CPU的體系架構,有時需要在彙編程式碼中使用完全不同的CPU指令集。為了使原始碼真正可移植,不能直接編譯為機器程式碼,我們還需要使用中間語言。
中間語言(IL)
由於上述原因,託管執行時的程式語言原始碼(如C#,F#或Java)不會直接編譯為組合語言。而是,它首先被編譯為中間語言(IL)。CLR的中間語言也稱為MSIL(Microsoft中間語言)。
將原始碼編譯為IL中是由特定的語言編譯器執行的。這個過程是當你在Visual Studio構建應用時按F6或使用csc.exe進行編譯時發生的。
原始碼到IL的編譯,由特定語言的編譯器完成。例如,C#中由Roslyn編譯,Roslyn是一個C#編譯器,如圖:
檢視MSIL
為了檢視已編譯的EXE / DLL檔案中包含的MSIL程式碼,你可以使用 ofollow,noindex">ILSpy Visual Studio擴充套件 ,安裝這個擴充套件時,會在Visual Studio中新增一個選單選項(tools – > ILSpy),你可以開啟任何已編譯的檔案並檢視包含物件的IL程式碼,如圖:
實際上有哪些東西編譯成IL程式碼呢?現在我們可以說,最重要東西就是methods(當然分為類,名稱空間等)。還有許多其他的東西被編譯(肯定比你預想的要多,通過檢視原始碼你就知道了),但我們現在將重點關注methods。
好的,我們現在有了IL程式碼,但是CPU還是無法理解它。它是如何以及何時編譯為彙編程式碼的?
JIT編譯(即時編譯)
豐田如何引入JIT?
我們先從一些歷史背景開始說起。在20世紀60年代早期,由於倉儲成本非常高,豐田的日本工程師不得不重新組織倉庫管理。而且由於他們提前訂購了大量零件,所以供應商交付也需要很長時間。每次交付都必須由人工去處理,因此他們需要很多員工。但是訂購的零件會長期存放在倉庫中,需要大量儲存空間和並進行維護。
為了最大限度的降低成本,他們開創了即時製造(也稱為豐田生產系統)。其主要原則是僅在倉庫庫存達到最低水平時才從供應商處訂購貨物。這樣一來,貨物是在生產線上正好需要它們的時間裡交付的。它最大限度的減少了交付人員(和倉庫員工)的數量,同時,也不像以前那樣需要那麼多的儲存空間。
整個想法可以想象為以下流程順序圖:
JIT編譯器
在豐田取得成功之後,CLR建立者在框架中引入了即時(JIT)編譯器。它的作用是根據硬體和作業系統的特性(取決於對程式碼進行JIT編譯的主機)將中間語言程式碼編譯成組合語言。與非託管語言不同,非託管語言在程式執行之前將原始碼編譯為機器語言,而IL預設情況下在執行時編譯為CPU指令。這就是.NET工程師按時向CPU提供機器碼的原理。
JIT編譯器是CLR的一部分(類似於垃圾收集器)。簡單來說,當某方法首次被呼叫時,JIT會編譯該方法的原始碼。當然,它也會考慮到硬體,例如CPU型別及指令集來生成正確的彙編程式碼。然後,這個機器碼就會儲存在快取中,並且在相同的應用程式執行,呼叫相同的方法時重新使用。這意味著在程式執行期間未呼叫的部分程式碼可能根本沒有進行JIT編譯。
JIT編譯實際上是兩種方法的結合:提前編譯和解釋,混合了兩者的優點和缺點。但是,在執行時將IL程式碼編譯為組合語言時有幾個特色功能,如動態型別(在程式執行時能夠獲取實際物件的型別)。這也是為什麼JIT編譯通常被視為動態編譯的一種形式。
使用JIT編譯的另一個優點是我們編寫的原始碼離硬體更遠,因此程式碼可以編寫的更具可讀性和人性化,而與硬體互動的東西隱藏在IL中(不得不說,MSIL開發者確實厲害)
JIT編譯也會有一些成本,在執行時編譯程式碼需要時間。另一方面,不同的CPU單元具有不同的強大指令集或處理單元,這些指令集或處理單元會在由JIT生成的彙編程式碼中使用,而在提前編譯中,則不是這種情況(沒有任何特殊調整的情況下)。
我們還需要提前編譯嗎?
如果你真的不喜歡JIT編譯(怎麼會不喜歡?),有很多方法可以在應用程式執行之前預先編譯你的EXE / DLL。這稱為Pre-JIT編譯。其中一個原因可能是許多使用者將從相同的EXE / DLL檔案執行我們的應用程式,JIT編譯器通常會為每個使用者對IL進行JIT編譯。在這種情況下,出於效能和記憶體使用原因,使用預先JIT編譯應用程式檔案可能會更合適。可以使用NGen(適用於所有.NET版本)或.NET Native(適用於Windows 10的.NET應用程式)來完成。我們本文不會詳細講解這些工具(你可以在網際網路上找到更多資訊)。
總結
JIT編譯是在執行時將中間語言(IL)編譯為原生(機器)程式碼的過程。它提供了託管應用程式的可移植性。這是一個非常重要的概念,因為它已經在許多當前使用的程式設計框架中引入,如Java,.NET和Android。
最後,請記住: