在计算中,即时( 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++ 时代不知道如何有效解决的问题已经解决了,或者至少已经很好地理解了:即内存分配、垃圾收集、类型推断、动态类型强制转换,以及其他一些问题。但还有其他因素。首先,性能对大多数应用程序来说并不重要;谷歌需要充分利用每个计算周期,但对于其他用户来说这不是主要问题。但是,在计算机编程中还有其他问题需要解决,这可能需要语言设计的新“升级”,而解释型语言可能再次具有一些优势。让我们看看它是如何发展的。
上一篇
下一篇