什麼是JIT ?

奥楚蔑洛夫 2024-05-21 11:00 1次浏览 0 条评论 taohigo.com

在計算中,即時( JIT )編譯(也稱為動態翻譯或運行時編譯)是一種執行計算機代碼的方式,它涉及在程序執行期間(運行時)而不是執行前進行編譯。這可能包括源代碼翻譯,但更常見的是字節碼到機器碼的翻譯,然後直接執行。實現 JIT 編譯器的系統通常會持續分析正在執行的代碼,並識別代碼中從編譯或重新編譯中獲得的加速比編譯該代碼的開銷更大的部分。JIT 編譯結合瞭兩種傳統的機器代碼翻譯方法——提前編譯 (AOT) 和解釋——並結合瞭兩者的一些優點和缺點。粗略地說,JIT 編譯將編譯代碼的速度與解釋的靈活性、解釋器的開銷以及編譯和鏈接(不僅僅是解釋)的額外開銷結合在一起。JIT 編譯是動態編譯的一種形式,並允許自適應優化,例如動態重新編譯和特定於微體系結構的加速。解釋和 JIT 編譯特別適合動態編程語言,因為運行時系統可以處理後期綁定數據類型並強制執行安全保證。

什麼是 JDK、JRE、JVM 和JIT ?

Java 虛擬機 (JVM)是一種抽象計算機器。Java 運行時環境 (JRE)是 JVM 的一種實現。Java 開發工具包 (JDK)包含 JRE 以及各種開發工具,如 Java 庫、Java 源代碼編譯器、Java 調試器、捆綁和部署工具。即時編譯器 (JIT)在程序開始執行後即時運行。它可以訪問運行時信息並優化代碼以獲得更好的性能。JVM 在 Java 程序運行時成為 JRE 的一個實例。它被廣泛稱為運行時解釋器。Java 虛擬機 (JVM) 是構建 Java 技術的基石。它是負責其硬件和平臺獨立性的 Java 技術組件。JVM 在很大程度上有助於從使用 JDK 程序庫的程序員那裡抽象出內部實現。一個編譯器,從動態語言到可執行代碼,它可以及時運行,即按需運行。當需要執行代碼時,甚至在第一次需要執行代碼之後,代碼會被編譯成可由某些機器(可能是解釋器)執行的代碼。一個典型的架構是面向方法的動態 OO 語言,它被編譯成可解釋的字節碼。JIT不是解釋,而是將字節碼編譯為主機處理器的機器代碼(本機代碼),然後執行該代碼。綁定到尚未進行 JIT 處理的字節碼方法的代碼中的方法調用將調用JIT將其編譯為本機代碼。

另一種架構 JIT 將解釋器的蹤跡轉換為機器代碼並執行這些蹤跡。這稱為跟蹤JIT 。一個更復雜的架構可能有一個第一級JIT ,它產生中等性能的代碼,比解釋器快得多,其中包括性能計數器。計數器在大量使用時跳閘,從而識別“熱點”、可以從更積極的優化中受益的代碼,然後調用更徹底的優化JIT 。這些架構被稱為自適應優化器(因為代碼根據使用情況進行優化,使優化工作適應工作負載)或推測內聯器(因為優化代碼是在沒有完整類型信息的情況下生成的,因為用動態語言編寫的程序可能會遇到新類型,例如execution proceeds do inlining 是推測性的,如果程序以某些方式發展,則可能必須丟棄)。有許多方法可以構建包含此類系統的 VM。一些 JavaScript VM 在加載時(例如,當訪問包含 JavaScript 代碼的網頁時)將 JavaScript 源代碼編譯為字節碼,並在首次執行 JavaScript 函數時讓第一級JIT將字節碼編譯為包含性能計數器的本機代碼。

避免在第一次使用時編譯可能是有利的;將字節碼語言編譯成機器碼有點像慢速解釋,隻有大量使用機器碼,整體性能才會更高。單次使用可能更有效地解釋。因此,混合模式 VM 可能隻會在第二次使用某個方法時進行JIT ,因此解釋隻使用一次的方法,通常會縮短啟動時間。在 Smalltalk-80 系統中,隻要在代碼瀏覽器中編輯方法、加載源包或評估“do it”(一個 Smalltalk 表達式在文本編輯器、調試器等,在程序員選擇的任何時間),sobyrecode 始終可以執行。Cog/opensmalltalk-vm 在第一次使用時解釋字節碼(立即執行 JITing),並在第二次使用時將JIT編譯為本機代碼。在其自適應優化/推測內聯配置中,第一級JIT將性能計數器添加到為條件分支字節碼生成的代碼中。當這些計數器觸發時,將調用一個似乎以字節碼運行的 Smalltalk 級優化器。VM將本地代碼中內聯方法緩存中的計數器信息和類型信息映射到字節碼域中。然後優化器從字節碼到字節碼進行優化,方法是將字節碼映射到 SSA 並返回,將許多方法內聯到一個方法中,並使用特殊字節碼為不安全的機器代碼序列編碼,從而消除安全檢查。優化器在證明變量是特定類型或推測它們具有特定類型時使用此類指令,並添加保護措施以確保不安全的字節編碼僅用於預期類型。優化的字節碼方法安裝在類方法字典和運行時堆棧中。在執行精煉或後續發送JIT然後將優化的字節碼方法編譯為本機代碼,避免性能計數器,因為該方法使用標志標識為優化的字節碼方法。這種架構將自適應優化器與 JIT 分開,在 VM 之外的 Smalltalk 中實現它(參見 Graal,一個類似的 Java 內部優化器),使用 Snalltalk 的一流激活記錄(上下文)來檢查運行時狀態。

是機器無關的,因為從字節碼到字節碼都進行瞭優化存儲獨立於平臺的優化代碼以快速啟動,因為優化的字節碼方法可以像普通方法一樣存儲在 Smalltalk 快照中依賴於一個相對簡單的模板 JIT 來非常快速地從字節碼生成平臺特定的本機代碼,因此,另一個即時發生的過程是去優化,優化的本機代碼遇到一些意外的類型,守衛失敗,作為響應,代碼和運行時狀態被去優化為第一級JIT代碼的激活,或解釋代碼等,通過這種方式,現代 JIT 提供瞭高性能,同時允許編寫動態程序。

與真正的計算機器一樣,JVM 具有指令集並在運行時操作各種內存區域。因此,對於不同的硬件平臺,可以使用相應的 JVM 實現作為供應商提供的 JRE。使用虛擬機來實現編程語言是很常見的。Java 虛擬機指令由指定要執行的操作的操作碼組成,後跟零個或多個包含要操作的值的操作數。從編譯器的角度來看,Java 虛擬機 (JVM) 隻是另一個帶有指令集的處理器,即 Java 字節碼,可以為其生成代碼。生命周期如下,源代碼到字節碼,由 JRE 解釋並轉換為平臺特定的可執行代碼。

Sun 的 Java 虛擬機 (JVM) 實現本身稱為 JRE。Sun 的 JRE 可作為單獨的應用程序使用,也可作為 JDK 的一部分使用。Sun 的 Java 開發工具包 (JDK) 帶有用於字節碼編譯的實用工具“javac”。然後使用“java”和 JDK 二進制目錄中的更多實用程序通過 java 程序執行字節碼。'java' 工具分叉 JRE。除瞭 Sun Micro Systems 之外,其他公司也積極發佈瞭 JVM 的實現。

其他語言的 JVM

JVM 也可用於實現 Java 以外的編程語言。例如,Ada 源代碼可以編譯成 Java 字節碼,然後可以由 Java 虛擬機 (JVM) 執行。也就是說,任何具有可以用有效類文件表示的功能的語言都可以由 Java 虛擬機 (JVM) 托管。受到普遍可用的、獨立於機器的平臺的吸引,其他語言的實現者正在轉向 Java 虛擬機 (JVM) 作為他們語言的交付工具。

即時編譯器 ( JIT )

JIT是 Java 虛擬機 (JVM) 的一部分,用於加快執行時間。JIT同時編譯部分具有相似功能的字節碼,因此減少瞭編譯所需的時間。這裡的術語“編譯器”是指從 Java 虛擬機 (JVM) 的指令集到特定 CPU 的指令集的翻譯器。

Java 是編譯型語言還是解釋型語言?有什麼區別?

兩個都是。

首先讓我解釋一下什麼是編譯器:編譯器是一種將一種語言翻譯成另一種語言的程序。目標語言通常是機器代碼,但不一定是。例如,將 javascript 轉換為 C 的程序符合編譯器的定義。當你用 java 編寫程序時,你要做的第一件事就是使用 javac 編譯器將它翻譯成 java 字節碼。通常編譯後的字節碼的擴展名為 .class,並被打包成一個 .jar 文件。例如:

# compile the java source to java bytecodes: javac Test.java # optionally package it into a jar: jar cf Test.jar Test.class

當您運行該應用程序時,Java 虛擬機可以通過以下兩種方式之一執行它:JVM 一次讀取一個字節碼並執行它需要的操作來實現字節碼的語義。這是java解釋器。Java 虛擬機可能會決定使用第二個編譯器。一種將字節碼翻譯成機器碼。然後跳轉到生成的代碼。這是 JIT 編譯器。這兩種機制存在的原因是性能。如果一個函數被執行一次或兩次,那麼隻使用解釋器會更快。如果該函數執行瞭一千次,那麼花時間對其進行JIT編譯然後執行生成的代碼 1000 次會更快。簡而言之,您的 java 源代碼被編譯為字節碼,然後您的字節碼要麼被編譯為機器碼,要麼在運行時被解釋。這是執行 java 代碼的典型方式。語言本身並不意味著編譯或解釋。理論上,您可以為包括機器代碼在內的任何其他語言創建嚴格的解釋器或編譯器。

為什麼很難為 Python編寫JIT ?

原因很“簡單”。

當您查看 Java 代碼時:

public static void main(String[] args){ System.out.println("sum is "+sum(1,2)); } public static int sum(int a,int b){ return a+b; }

對比:

print "sum is",sum(1,2)
def sum(a,b): return a+b

第二個代碼更短。我們甚至可以這樣做:

print "sum is",sum("1","2") def sum(a,b): return a+b

它會起作用!

當然,連接兩個字符串和添加兩個不同的數字需要兩個不同的實現。在匯編中,添加是一個操作碼。連接字符串分配足夠的空間,遍歷第一個字符串然後第二個字符串。所以,在 Java 中,這很容易。您可以對每個方法進行JIT而無需瞭解它的使用方式。在 python 中,函數會根據調用方式改變行為。是的,你讀得很好。python 函數不是自定義的。具體化參數是其定義的一部分!(例如加法與連接)

因此,雖然JIT java很容易,因為每個信息都在本地可用。在 python 中,JIT的代碼是上下文相關的。對於簡單的情況,它看起來易於管理。但是像這樣的代碼呢:

def zipSum(a,b):
sums=[] for (i,j) in zip(a,b): sums.append(i+j) return sums print "sum is",zipSum([1,"2"],[3,"4"])

顯示“總和為 4,24”。

是的。該功能隻是以不同的方式處理元素!這仍然是非常簡單的代碼。但是對於JIT已經相當費解瞭。如何解決那個?為每個可能的行為創建代碼片段 ? 然後通過檢查參數類型分派到適當的方法?它不會減慢執行速度並擊敗JIT嗎?哦,還有 4 種可能的行為(如果我們簡化並說 python 隻有字符串和數字),實際上隻有 2 種。有 10 個參數和 10 種不同的類型,它就變成瞭一個有趣的數字。所以,這就是為什麼 JAVA 比 python 容易得多的原因。當然,可以對 python 的某些部分進行 JIT。Kotlin 編譯器實際上進行流分析並且可以檢測可空類型是否不再為空。它還可以檢測執行路徑是否需要給定類型的變量。這意味著就好像變量是強類型的,並且可以進行高效的JIT 。它不是用 Python 完成的,可能是因為資源和語言的靈活性。Kotlin 已由 Jetbrains 實施,他們對此有濃厚的興趣。他們投入瞭大量資源。它生成與 Java8 字節碼兼容的 Java 字節碼。因此,他們受益於 Sun 和 Oracle 專業知識。

通過JIT和 VM運行語言會過時嗎?

我認為這不僅僅是一個語言設計問題。

語言解釋器 VM 對它們帶來的好處有非常強烈的吸引力:在許多平臺上運行相同代碼的能力。語言解釋器 VM 還有另一個屬性,因為它們允許語言開發人員抽象出機器相關的細節並專註於語言本身。也就是說,我相信 Go 在成為一種直接可編譯的語言方面做得很好,但它也很大程度上歸功於基於 VM 的語言/環境,例如 Python 和 Javascript(許多其他人肯定會記得)。如果不開發這些其他語言,Go 將永遠不可能,而這些語言的發展在很大程度上歸功於它們基於 VM 的設計。

解釋性或基於 VM 的語言使嘗試新功能變得更加容易。編譯語言更多地依賴於低級實現細節,因此更難開發復雜的實驗。我相信計算機語言的未來取決於這兩種技術:偉大的基於解釋/VM 的語言不斷突破極限,而JIT和純編譯語言緊隨其後提高性能。至於現在,可能有轉向編譯語言的趨勢,無論是 Go 還是其他選擇。許多我們在早期的 C++ 時代不知道如何有效解決的問題已經解決瞭,或者至少已經很好地理解瞭:即內存分配、垃圾收集、類型推斷、動態類型強制轉換,以及其他一些問題。但還有其他因素。首先,性能對大多數應用程序來說並不重要;谷歌需要充分利用每個計算周期,但對於其他用戶來說這不是主要問題。但是,在計算機編程中還有其他問題需要解決,這可能需要語言設計的新“升級”,而解釋型語言可能再次具有一些優勢。讓我們看看它是如何發展的。