即时编译与提前编译有什么区别?

最近我一直在思考这个问题,而且在我看来,给JIT编译带来的好处多多lessless可以归因于中间格式,而且这样做本身并不是生成代码的好方法。

所以这些是我通常听到的主要的JIT编译参数:

  1. 即时编译允许更好的可移植性。 这是不是归因于中间格式? 我的意思是,一旦你的机器上已经有了虚拟字节码,就没有什么能够把你的虚拟字节码编译成本地字节码了。 可移植性在“分发”阶段是一个问题,而不是在“运行”阶段。
  2. 好的,那么在运行时生成代码呢? 那么,这同样适用。 没有什么能够让您将适时的编译器集成到您的本地程序中。
  3. 但是,运行库只是一次将其编译为本地代码,并将生成的可执行文件存储在硬盘驱动器某处的caching中。 好,当然。 但是它在时间限制下优化了你的程序,而且从那里开始并不是很好。 见下一段。

这不像提前编译也没有好处。 即时编译有时间限制:当程序启动时,不能让最终用户永远等待,所以它有一个折中的地方。 大多数时候,他们只是优化less。 我的一个朋友有一些证据表明 ,内联函数和“手动”展开循环(在这个过程中混淆了源代码)对他的C#编程计划的性能产生了积极的影响。 在我这边做同样的事情,我的C程序完成了相同的任务,没有取得任何积极的结果,我相信这是由于我的编译器允许进行的广泛的转换。

然而,我们被激烈的计划所包围。 C#Java是无处不在的,Python脚本可以编译成某种字节码,我相信其他一些编程语言也是一样的。 一定有一个很好的理由,我错过了。 那么什么使得即时编译如此优于提前编译呢?


编辑为了清除一些混淆,也许这是很重要的,说我都是可执行文件的中间表示。 这有很多优点(实际上, 即时编译的大多数论据实际上是中间表示的参数)。 我的问题是关于如何将它们编译为本地代码。

大多数运行时(或者编译器)都会select及时编译或提前编译。 由于编译器有更多的时间来执行优化,因此提前编译看起来更适合我,因为我想知道为什么微软,Sun和其他所有人都会相反。 我对性能分析相关的优化有些怀疑,因为我对即时编译程序的经验显示很差的基本优化。

我只用了一个C代码的例子,因为我需要一个提前编译与即时编译的例子。 C代码不是发送到中间表示的事实与情况无关,因为我只需要显示提前编译可以产生更好的立即结果。

  1. 可移植性更高:可交付成果(字节码)保持可移植性

  2. 同时,更多平台特定的:因为JIT编译发生在代码运行的同一个系统上,所以可以非常非常精细地调整这个特定的系统。 如果您提前编译(并且仍然想将相同的软件包发送给所有人),则必须妥协。

  3. 编译器技术的改进会对现有的程序产生影响。 一个更好的C编译器完全不能帮助你使用已经部署的程序。 更好的JIT编译器将改善现有程序的性能。 你十年前写的Java代码今天会运行得更快。

  4. 适应运行时指标。 JIT编译器不仅可以查看代码和目标系统,还可以查看代码的使用方式。 它可以testing运行代码,并根据例如通常具有的方法参数的值来决定如何进行优化。

JIT增加了启动成本,这是正确的,所以它有一个时间限制,而提前编译可以花费所有它想要的时间。 这对服务器types的应用程序来说更为合适,因为启动时间并不那么重要,在代码变得非常快之前的“预热阶段”是可以接受的。

我猜想可能会将JIT编译的结果存储在某个地方,以便下一次重新使用它。 这会给你第二个程序运行的“提前”编译。 Sun和微软的聪明人也许认为新的JIT已经够好了,额外的复杂性并不值得这样做。

ngen工具页面溢出了bean(或者至less提供了本地图像与JIT编译图像的良好比较)。 以下是可以提前编译的可执行文件的优点列表:

  1. 原生图像加载速度更快,因为它们没有太多的启动活动,并且需要静态数量更less的内存(JIT编译器所需的内存);
  2. 原生图像可以共享库代码,而JIT编译的图像则不能。

以下是即时编译的可执行文件的优点列表:

  1. 原生图像大于其字节码对应;
  2. 无论何时原始程序集或其中一个依赖关系被修改(必须有意义,因为它可能会搞砸虚拟表和类似的东西),必须重新生成本地图像。

以及微软在这个问题上的一般考虑:

  1. 大型应用程序通常受益于提前编译,小型应用程序通常不会;
  2. 对从dynamic库加载的函数的任何调用都需要一个额外的修正跳转指令的开销。

每次对其中一个组件进行预编译时,都需要重新生成一个图像,这对于本地图像来说是一个巨大的缺点。 这是脆弱的基类问题的根源。 例如,在C ++中,如果您使用本地应用程序的DLL的类的布局发生了变化,那么您就搞砸了。 如果你使用接口进行编程,那么如果界面改变的话,你还是会被搞砸的。 如果你使用更dynamic的语言(比如说Objective-C),那么你很好,但是这样会带来一个性能问题。

另一方面,字节码图像不会受到这个问题的影响,而且不会影响性能。 这本身就是一个很好的理由来devise一个可以很容易地重新生成中间表示的系统。

简单的逻辑告诉我们,甚至从字节码编译巨大的MS Office大小的程序将花费太多的时间。 你会以巨大的开始时间结束,这会吓跑你的产品的任何人。 当然,您可以在安装过程中进行预编译,但这也有其后果。

另一个原因是并不是所有的应用部分都会被使用。 JIT将只编译那些用户关心的部分,留下80%的代码,节省时间和内存。

最后,JIT编译可以应用正常编译器无法做到的优化。 就像使用跟踪树内联虚拟方法或部分方法一样。 从理论上讲,这可以使他们更快。

  1. 更好的反思支持。 这在原则上可以在提前编译的程序中完成,但在实践中几乎不会发生。

  2. 通常只能通过dynamic观察程序来进行优化。 例如,内嵌虚拟函数,转义分析将堆栈分配转换为堆分配,并locking更粗糙。

也许这与现代的编程方法有关。 你知道,很多年前你会把你的程序写在一张纸上,其他人会把它转换成一堆打孔的卡片并送入计算机,明天早上你会在一卷纸上称重半磅。 所有这一切都迫使你在编写第一行代码之前考虑很多。

那些日子已经一去不回。 使用PHP或JavaScript等脚本语言时,可以立即testing任何更改。 Java的情况并非如此,尽pipeappservers给你热门的部署。 所以Java程序可以快速编译 ,这非常方便,因为字节码编译器非常简单。

但是,没有只有JIT的语言。 时间久的编译器已经可用于Java一段时间了,最​​近Mono将它引入CLR。 事实上,由于AOT编译, MonoTouch完全可能,因为苹果app store禁止非本地应用程序。

我一直在试图理解这一点,因为我看到Google正在用Android Run Time(ART)替代他们的Dalvik虚拟机(本质上是另一个Java虚拟机,如HotSpot),而AOT编译器是Android运行时(ART),但是Java通常使用HotSpot ,这是一个JIT编译器。 显然,ARM比Dalvik快了两倍…所以我想我自己“为什么Java不使用AOT?”。 无论如何,从我能收集到的信息来看,主要的区别在于JIT在运行时使用自适应优化,例如只允许那些频繁执行的字节码部分被编译成本地代码; 而AOT将整个源代码编译成本地代码,而较less量的代码运行得比代码量更大。
我不得不想象大多数Android应用程序由less量代码组成,所以平均来说,将整个源代码编译为本地代码AOT并避免解释/优化相关的开销会更有意义。

我没有看到这里列出的JIT的一个优点是能够跨单独的程序集/ dlls /jar子内联/优化(为了简单起见,我只是从这里开始使用“程序集”)。

如果您的应用程序引用了可能在安装后会更改的程序集(例如,预安装的库,框架库,插件),那么“安装时编译”模型必须避免跨程序集边界内联方法。 否则,当引用程序集更新时,我们必须在系统上引用程序集中find所有这些内联代码,并用更新的代码replace它们。

在JIT模型中,我们可以在程序集内自由内联,因为我们只关心为底层代码没有改变的单次运行生成有效的机器代码。