40

是否存在 JIT 编译器比 C++ 等其他编译器更快的情况?

你认为未来 JIT 编译器只会看到一些小的优化、特性但遵循类似的性能,还是会有突破,使其无限优于其他编译器?

看起来多核范式有一些希望,但它不是通用的魔法。

有什么见解吗?

4

11 回答 11

53

是的,肯定有这样的场景。

  • JIT 编译可以使用运行时分析来优化特定情况,基于对代码当前实际执行的特征的测量,并且可以根据需要重新编译“热”代码。这不是理论上的。Java 的 HotSpot 实际上就是这样做的。
  • JITters 可以针对正在执行程序的实际硬件上使用的特定 CPU 和内存配置进行优化。例如,许多 .NET 应用程序将以 32 位或 64 位代码运行,具体取决于它们的 JIT 位置。在 64 位硬件上,它们将使用更多的寄存器、内存和更好的指令集。
  • 基于引用类型的运行时知识,可以将紧密循环内的虚拟方法调用替换为静态调用。

我认为未来会有突破。特别是我认为JIT编译和动态类型的结合会得到显着的提升。我们已经在 Chrome 的 V8 和 TraceMonkey 的 JavaScript 领域看到了这一点。我希望在不久的将来看到其他类似规模的改进。这很重要,因为即使是所谓的“静态类型”语言也往往具有许多动态特性。

于 2009-02-11T18:22:44.003 回答
14

是的,JIT 编译器可以生成针对当前环境优化的更快的机器代码。但实际上,VM 程序比 Native 程序慢,因为 JITing 本身会消耗时间(更多优化 == 更多时间),并且对于许多方法来说,JITing 它们可能比执行它们消耗更多时间。这就是在 .NET 中引入 GAC 的原因

JITing 的一个副作用是大量的内存消耗。但这与计算速度无关,它可能会减慢整个程序的执行速度,因为大量的内存消耗会增加您的代码被分页到辅助存储的可能性。

请原谅我的英语不好。

于 2009-02-11T18:38:00.473 回答
9

JIT 有优势,但我不认为它会完全接管。传统编译器可能会花费更多时间进行优化,而 JIT 需要在优化过多(花费的时间比优化节省的时间多)和过少(直接执行花费的时间过多)之间取得平衡。

显而易见的答案是在其优越的地方使用每一个。与传统优化器相比,JIT 可以更轻松地利用运行时分析(尽管有些编译器可以将运行时分析作为输入来指导优化),并且通常可以进行更多特定于 CPU 的优化(同样,许多传统的编译器会这样做,但如果您希望在不同的系统上运行可执行文件,他们就无法充分利用它)。传统的编译器可能会花费更多时间,并以不同的方式进行。

因此,未来的语言系统将具有良好的优化编译器,这些编译器将发出专为良好优化 JIT 编译器使用而设计的可执行代码。(对于许多人来说,这也是当前的语言系统。)(未来的语言系统还将支持从现代 Python/VB 脚本到最丑陋的高速数字运算的一切。)

与许多事情一样,Lisp 预示了这一点。很久以前,一些 Lisp 系统(不能说很多,还没有那么多 Common Lisp 实现)通过动态编译 Lisp 函数来解释它们。Lisp S 表达式(编写代码的内容)是对解析树的相当直接的描述,因此编译速度可以很快。同时,优化的 Lisp 编译器可以提前处理性能非常重要的代码。

于 2009-02-11T19:25:23.633 回答
1

基本上,JIT 编译器有机会实际分析正在运行的应用程序,并根据该信息进行一些提示。“离线”编译器将无法确定一个分支跳转的频率以及它失败的频率,而无需插入特殊代码,要求开发人员运行程序,通过它的速度,然后重新编译。

为什么这很重要?

//code before
if(errorCondition)
{
  //error handling
}
//code after

被转换成类似的东西:

//code before
Branch if not error to Code After
//error handling
Code After:
//Code After

在没有来自分支预测单元的信息的情况下,x86 处理器不会预测条件跳转。这意味着它可以预测要运行的错误处理代码,并且当处理器发现错误条件没有发生时,它将不得不刷新管道。

JIT 编译器可以看到这一点,并为分支插入提示,以便 CPU 预测正确的方向。诚然,离线编译器可以以一种避免错误预测的方式构造代码,但是如果你需要查看程序集,你可能不喜欢它到处乱跳......

于 2009-02-11T18:36:45.187 回答
1

本次对话中跳过的另一件事是,当您 JIT 一段代码时,它可以编译到内存中的空闲位置。在像 C++ 这样的语言中,如果 DLL 的基础是那块内存不可用,那么它将不得不经历代价高昂的变基过程。将代码 JIT 到未使用的地址然后将已编译的 DLL 重新设置为可用内存空间会更快。更糟糕的是,无法再共享重新设置的 DLL。(参见http://msdn.microsoft.com/en-us/magazine/cc163610.aspx

我对 C# 3.5 JIT 代码中的一些优化印象不深。压缩所需的位旋转等简单的事情效率非常低(它拒绝在 CPU 寄存器中缓存值,而是为每个操作都进入内存)。我不知道它为什么会这样做,但它会产生巨大的影响,我对此无能为力。

我个人认为一个好的解决方案是优化级别(1-100),你可以设置它来告诉 JIT 编译器你认为它应该花多少时间优化你的代码。唯一的其他解决方案是 AOT(提前)编译器,然后您将失去 JIT 代码的许多优点。

于 2009-11-09T23:20:59.603 回答
1

JIT 编译器比静态编译器更了解系统。一旦机器开始工作,就可以在运行中添加特定于机器的 multitheading 可以极大地提高速度。

JIT 编译器通常有一点启动延迟,其中程序/代码的第一次运行可能预编译代码慢得多。冷启动缺点。

JIT 编译的另一大优势是编译器可以在您的程序构建完成后更新并获得新的编译器技巧,而无需完整的新程序部署。

于 2010-10-20T10:22:17.320 回答
1

很多人回答也许我在略读(也许我的棒子的一端是错误的),但对我来说,它们是两个不同的东西:

AFAIK 没有什么能阻止你 JIT'ing 编译的 c++ 例如项目 Dynamo JIT'ed 机器代码:

http://arstechnica.com/reviews/1q00/dynamo/dynamo-1.html

在某些情况下,它确实提供了速度改进。

在 c++ 编译器的意义上,编译代码意味着获取用一种语言编写的代码并将其转换为一组指令(或在某些情况下另一种语言然后再次编译),这些指令可以由某种逻辑设备执行。

例如 c++ 编译为程序集(我认为 ;-) 或 c# 编译为 IL 或 Java 编译为字节码

JIT 是在执行期间发生的过程。执行代码的机器对其进行分析,看它是否可以改进它。当编译器为 VM 准备命令时,Java 和 C# 能够同时使用这两者,然后 VM 至少有机会再进行一次优化。

有些程序没有编译它们被解释,这意味着运行它们的机器会读取您编写的确切代码。这些机器有机会做一些 JIT,但请记住它们也可以静态编译,可能以语言的原始设计者从未想过的方式成为第三方供应商。

因此,要回答您的问题,我认为 JIT 不会取代静态编译器。我认为总会有(只要至少有编程)有一个地方来表示程序并将其转换为某种类型的机器的一组指令。(可能会在执行此操作时进行优化)

然而,我确实认为 JIT 可能会成为故事的重要组成部分,随着 Java 运行时和 .net 运行时的发展,我相信 JIT 会变得更好,并且考虑到像 Dynamo 项目这样的东西,我猜硬件占用 JIT 的空间同样,您的处理器所做的一切都会根据运行时环境重新优化。

于 2010-11-08T07:33:17.537 回答
1

JITing 的一个尚未被提及的优点是程序可以定义无限数量的泛型类型。例如:

interface IGenericAction { bool Act<T>(); }

struct Blah<T>
{
  public static void ActUpon(IGenericAction action)
  {
     if (action.Act<T>())
       Blah<Blah<T>>.ActUpon(action);
  }
}

打电话Blah<Int32>.ActUpon(act)会打电话act.Act<Int32>()。如果该方法返回 true,它将调用Blah<Blah<Int32>>.ActUpon(act),然后调用act.Act<Blah<Int32>>()。如果返回 true,将使用嵌套更深的类型执行更多调用。为所有可以ActUpon调用的方法生成代码是不可能的,但幸运的是没有必要。在使用之前不需要生成类型。如果返回 false,则 不会调用,也不需要创建后一种类型。action<Blah<...50 levels deep...>>.Act()Blah<Blah<...50 levels deep...>>.ActUponBlah<Blah<...51 levels deep...>>.ActUpon

于 2013-12-02T23:25:18.927 回答
1

还没有提到有利于“离线”编译器的一点是,这样的编译器可以有效地针对具有少量 RAM 的平台——甚至只有 16 个字节。可以肯定的是,任何甚至远程 PC 兼容的东西都倾向于拥有(字面上)数百万倍的 RAM,但我认为需要一段时间才能找到一台价格低于 0.50 美元且消耗大量内存的机器。运行期间的功率小于一毫瓦。

请注意,16 字节的 RAM 并不像听起来那么微弱,因为具有如此小的 RAM 的芯片不会在 RAM 中存储代码——有一个单独的非易失性内存区域来保存代码(384 字节是我所知道的最小的)。当然,这并不多,但足以让 0.25 美元的处理器执行原本需要价值 1.00 美元的分立元件的功能。

于 2013-12-04T15:57:49.700 回答
1

是否存在 JIT 编译器比 C++ 等其他编译器更快的情况?

确实。例如,如果您发现一个非常糟糕的 AOT 编译器和一个非常好的 JIT 编译器,那么您自然可以期望 JIT 对于这两种实现来说会更快。

你认为未来 JIT 编译器只会看到一些小的优化、特性但遵循类似的性能,还是会有突破,使其无限优于其他编译器?

不,相反的可能性要大得多。

从历史上看,大多数 AOT 实现已经被开发人员(而不是最终用户)使用,导致他们针对通用目标进行优化(例如,“所有 64 位 80x86,谁知道什么类型的 RAM”)而不是最终用户的特定硬件(例如“AMD Ryzen model 2345 with 16 GiB of DDR3-2400 RAM”);并且软件被分解成单独优化的“编译单元”(以创建目标文件),然后在没有进一步优化的情况下链接。这造成了阻碍 AOT 实现其能力的主要优化障碍。

近年来,推动整个程序优化(以链接时间优化和/或链接时间代码生成的形式)以打破其中一个优化障碍。

为了打破另一个优化障碍(在编译时不知道具体目标),一些编译器(例如英特尔的 ICC)会生成某些部分代码的多个版本,并选择(在运行时)要使用的版本。还有一些情况会出现“安装时AOT”(例如Gentoo Linux);在某些情况下,开发人员提供了许多单独的二进制文件(针对许多不同的目标进行了优化)并且安装程序选择下载/安装哪个二进制文件。

优化的另一个障碍来自使用——例如更改代码以更好地适应它给出的数据。没有什么可以阻止 AOT 为不同的场景生成不同的代码并根据运行时数据选择要使用的版本。最简单和最常见的情况是“ memcpy()”,您可以预期执行复制的代码的多个版本,其中使用的版本是根据被复制的数据量选择的。使用足够先进的 AOT(可能与分析器引导的优化相结合),同样的技术可以变得更加复杂。

本质上; 这些 (AOT) 优化的“历史障碍”对于 JIT 来说是不存在的,这也是 JIT 可以接近 AOT 性能的原因;AOT 更有可能继续寻找避免/修复这些障碍的方法,并且 AOT 将增加其相对于 JIT 的性能优势。

另一方面; JIT 不能做诸如整个程序优化之类的事情(没有成为 AOT 的一种形式),因此无论 JIT 编译器变得多么先进,它都永远不会像 AOT 一样好。

此外; 在运行时修改或生成代码会在现代 CPU 上产生性能成本(由于破坏推测执行和需要特殊序列化,此外还会污染跟踪缓存和分支预测数据等内容);而对于现代多线程软件来说,情况更糟,因为 JIT 编译器还需要确保代码在所有 CPU 上保持一致;并确保可以修改代码也有“持久成本”(例如,使用间接调用而不是直接调用,这样您就可以在完成 JITting 后自动更新以指向另一段代码的单个位置) 即使没有修改代码并且所有 JITting 都已完成。

对于(虚拟)内存消耗,JIT 也差很多——而不是拥有程序的代码和程序的数据;你有程序的原始代码和程序的数据,加上程序的 JITted 代码、JIT 编译器的代码和 JIT 编译器的数据。随着 JIT 编译器变得更先进,它们消耗更多的内存并且内存消耗变得更糟。更高的内存消耗也会稍微降低性能(由于在较低层管理内存的“不是 O(1)”开销 - 页表等),但也会使程序更接近“内存不足”的情况(例如交换空间使用,内存分配失败和某些操作系统中的“OOM杀手”)。

当然大多数系统都是多任务的,全局资源由多个进程共享;这意味着如果一个进程使用更多的 CPU 时间和更多的内存,那么其他完全不相关的进程(以及操作系统的不相关部分 - 例如文件数据缓存)将使用更少的资源。即使 JIT 的低效率对一个进程无关紧要(一个进程仍然“足够快”并且本身不会耗尽内存),它也会影响其他一切,并且对所有其他进程都很重要。

换句话说; 如果您要比较特定的实现,JIT 可能会更好(或类似,或更糟),但这是一个实现细节,并不是因为 JIT 实际上更好。任何比较 JIT 和 AOT 的公平基准,表明 JIT 与 AOT 一样好,都必须依赖于特定的实现;并暗示使用的 AOT 编译器的实现可以而且应该改进(并不暗示 JIT 与 AOT 可以或应该一样好)。

然而...

还是会有突破,使其无限优于其他编译器?

这实际上取决于您所说的“优越”。与 AOT 相比,JIT 有一个巨大的优势 - 开发人员等待代码编译所花费的时间(其中对利润的渴望意味着更高的开发成本意味着更高的最终产品成本)。

多年来/几十年,对于许多领域,我们看到了牺牲最终结果的质量/效率以减少开发时间/成本的趋势(例如,转向“Web 应用程序”)。我们在不相关的市场上也看到了同样的情况(为了好玩,看看很难找到简单的衣夹,这些衣夹不便宜,而且几年后会在阳光下腐烂的讨厌的塑料)。

到底; “低质量、低成本”获胜,除非有法律强制执行的质量标准(确实存在,主要是出于安全原因 - 例如汽车、医疗设备、电气、易燃气体……);并且(因为几乎所有软件都没有任何质量标准)可以合理地假设 JIT 将变得更加占主导地位,因为它更便宜(尽管质量较低/性能较差)。

于 2020-10-09T18:59:13.533 回答
0

JIT 编译器有更多的数据可以用来影响优化。当然,实际上必须有人编写代码才能使用这些数据,所以这不是那么简单。

于 2009-02-11T18:35:44.970 回答