JIT(即时编译器)是否比其他编译器,如C++更快的情况?
您认为在未来,JIT编译器是否只会进行一些小的优化和功能改进,但仍然保持类似性能,还是会出现突破,使其无限超越其他编译器?
多核范例似乎有一些前途,但并非万能。
任何见解?
JIT(即时编译器)是否比其他编译器,如C++更快的情况?
您认为在未来,JIT编译器是否只会进行一些小的优化和功能改进,但仍然保持类似性能,还是会出现突破,使其无限超越其他编译器?
多核范例似乎有一些前途,但并非万能。
任何见解?
确实存在这样的情况。
我认为未来会有突破。特别是,我认为JIT编译和动态类型的结合将得到显着改进。我们已经在JavaScript领域看到了这一点,Chrome的V8和TraceMonkey就是如此。我期待在不久的将来看到其他类似于此的改进。这很重要,因为即使所谓的“静态类型”语言也倾向于具有许多动态特性。
是的,JIT编译器可以生成针对当前环境优化的更快的机器码。但实际上,虚拟机程序比原生程序慢,因为JIT本身需要消耗时间(更多的优化==更长的时间),并且对于许多方法,JIT可能会消耗比执行它们更多的时间。这就是为什么在.NET中引入了GAC。
JIT的一个副作用是占用大量内存。然而,这与计算速度无关,它可能会减慢整个程序的执行,因为大量的内存消耗增加了代码被分页到二级存储的概率。
对于我的糟糕英语请谅解。
JIT编译器是否有比其他编译器如C ++更快的情况?
当然可以。例如,如果您找到了一个非常糟糕的AOT编译器和一个极好的JIT编译器,则自然可以预期这两种实现中JIT会更快。
您认为未来JIT编译器只会看到轻微的优化、功能,但性能类似,还是会有突破使其无限超越其他编译器?
不会。相反,后者更有可能发生。
从历史上看,大多数AOT的实现都是由开发人员使用(而不是最终用户),导致他们对通用目标进行优化(例如,“所有64位80x86,拥有谁知道多少RAM”的类型),而不是最终用户的特定硬件(例如,“带有16 GiB DDR3-2400 RAM的AMD Ryzen模型2345”); 而软件被分解为“编译单元”,它们被单独优化(以创建对象文件),然后链接而没有进一步优化。这造成了主要优化障碍,阻止了AOT实现其所能实现的性能。
近年来,出现了朝向整个程序优化的推动(以链接时优化和/或链接时代码生成的形式)以打破其中之一的优化障碍。
为了突破另一个优化障碍(在编译时不知道特定目标),一些编译器(例如英特尔的ICC)会为某些代码的多个版本生成代码,并在运行时选择要使用的版本。还有一些情况下发生“安装时间AOT”(例如Gentoo Linux);以及一些情况下开发人员提供许多单独的二进制文件(针对许多不同的目标进行了优化),而安装程序则选择要下载/安装的二进制文件。
优化的另一个障碍来自于使用-例如改变代码以更好地适应给定的数据。没有什么可以阻止AOT为不同的场景生成不同的代码,并根据运行时数据选择要使用的版本。最简单最常见的情况是“memcpy()”,您可以期望复制数据的代码有多个版本,使用的版本是基于要复制的数据量进行选择的。使用足够先进的AOT(可能与分析器指导的优化结合使用),这种技术可能会变得极其复杂。
基本上,这些(AOT)优化的“历史性障碍”并不存在于JIT中,这也是为什么JIT可以接近AOT的性能的原因;而AOT将继续寻找避免/修复这些障碍的方法,并且AOT将增加其对JIT的性能优势。
另一方面,JIT不能像整个程序优化那样工作(除非成为AOT的一种形式),因此无论JIT编译器变得多么先进,也永远无法像AOT那样变得出色。
此外,在运行时修改或生成代码会对现代CPU的性能产生影响(由于破坏了预测执行并需要特殊串行化,除了污染跟踪高速缓存和分支预测数据等原因);对于现代多线程软件而言更糟糕,因为JIT编译器还需要确保代码在所有CPU上是一致的;而确保代码可以修改也有"持久成本"(例如使用间接调用而不是直接调用,以便在JIT完成后可以原子更新到指向不同代码段的单个位置),即使代码没有被修改并且所有JITting已经完成。对于(虚拟)内存消耗而言,JIT也更糟糕 - 而不是程序的代码和程序的数据;你拥有程序的原始代码和程序的数据,加上程序的JIT代码、JIT编译器的代码和JIT编译器的数据。随着JIT编译器变得更加先进,它们会消耗更多的内存,并且内存消耗将变得更加糟糕。更高的内存消耗也会稍微降低性能(由于在较低层次管理内存时存在“非O(1)”的开销 - 页面表等),但也会将程序推向“内存不足”的状态(例如交换空间使用、内存分配失败和某些操作系统中的“OOM killer”)。
当然,大多数系统都是多任务的,并且全局资源由多个进程共享;这意味着如果一个进程使用更多的CPU时间和内存,则其他完全不相关的进程(以及操作系统的不相关部分 - 例如文件数据缓存)就会有较少的资源可用。即使JIT的低效对于一个进程没有关系(那个进程仍然“足够快”,并且自身不会耗尽内存),它也会影响其他所有进程,并且可能对所有其他进程都很重要。
换句话说,如果你正在比较具体的实现,那么JIT可能会更好(或类似,或更差),但这是一项实现细节,并不是因为JIT本身更好。任何公平的比较JIT和AOT的基准测试都必须依赖于特定的实现,并暗示所使用的AOT编译器的实现可以和应该得到改进(并不意味着JIT可以和AOT一样好,或者应该变得像AOT一样好)。
然而...
或者会不会出现突破,使其相比其他编译器无限制优越?
这实际上取决于你对“优越”的定义是什么。JIT有一个巨大的优势,即开发人员等待代码编译的时间较少(在渴望利润的情况下,高开发成本意味着最终产品的成本更高)。
多年来/几十年来,在许多领域中,我们已经看到了一种向牺牲最终结果的质量/效率以降低开发时间/成本的趋势(例如转向“Web应用程序”)。我们也在不相关的市场上看到了相同的趋势(为了好玩,请尝试找到简单的衣物夹,它们不是廉价而恶心的塑料,在阳光下几年后就
在这次对话中被忽略的另一件事是,当您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代码的优势。
http://arstechnica.com/reviews/1q00/dynamo/dynamo-1.html
实际上,在某些情况下,它确实提供了速度改进。
编译代码,从C++编译器的角度来看,意味着将用一种语言编写的代码转换为一组指令(或在某些情况下转换为另一种语言,然后再次进行编译),以便可以由某种逻辑设备执行。
例如,C++编译为汇编语言(我猜这样?), 或者C#编译为IL 或者Java编译为字节码
JIT是在执行过程中发生的过程。执行代码的机器分析代码以查看是否可以改进它。Java和C#都能够同时使用编译器为虚拟机准备命令,然后虚拟机至少有机会尝试优化它。
有些程序不是编译的,而是解释的,这意味着运行它们的机器会读取您编写的确切代码。这些机器有机会进行一些JIT,但要记住它们也可以静态编译,潜在地成为原始语言设计者意料之外的第三方供应商。
所以,回答你的问题,我不认为JIT会取代静态编译器。我认为至少在编程存在的时间内,总会有一种将程序的表示转换为某种类型的机器指令集的地方(在此过程中可能进行优化)。
然而,我认为JIT可能会成为故事的更大一部分,随着Java运行时和.net运行时的发展,我相信JIT会变得更好,而且考虑到像Dynamo项目这样的东西,我想硬件也有可能采用JIT,这样你处理器所做的一切都是基于运行时环境重新优化的。
JIT编译器比静态编译器更了解系统。在机器上动态添加多线程可以大大提高速度,一旦它们开始工作。
通常情况下,JIT编译器具有一定的启动延迟,程序/代码的第一次运行可能比预编译代码慢得多。这是冷启动的劣势。
JIT编译的另一个重要优点是,在构建程序后,编译器可以进行更新并获得新的编译器技巧,而无需完全部署新程序。
请注意,16字节的RAM并不像听起来那么弱,因为具有如此小RAM的芯片不会将代码存储在RAM中 - 有一个单独的非易失性内存区域来保存代码(384字节是我所知道的最小值)。当然,这并不算多,但足以使$0.25的处理器执行需要$1.00离散元件才能完成的功能。
//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能够正确预测。当然,离线编译器可以以一种避免mispredict的方式构造代码,但如果您需要查看汇编代码,您可能不喜欢它跳来跳去...
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
方法的代码是不可能的,但幸运的是这不是必要的。在使用之前不需要生成类型。如果action<Blah<...50 levels deep...>>.Act()
返回false,则Blah<Blah<...50 levels deep...>>.ActUpon
不会调用Blah<Blah<...51 levels deep...>>.ActUpon
,后一种类型也不需要被创建。