深入解析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代碼,你可以使用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。
最後,請記住:
華盛頓曾經說過,不要相信互聯網上的一切!
※深入理解GIF文件格式
※H1-5411 CTF通關Write-up
TAG:嘶吼RoarTalk |