即时编译与提前编译相比有什么优势?


69

我最近一直在考虑它,在我看来,赋予JIT编译的大多数优势或多或少应该归因于中间格式,而自动拼合本身并不是生成代码的好方法。

因此,这些是我通常会听到的主要的准JIT编译参数:

  1. 即时编译可提供更大的可移植性。那不是中间格式吗?我的意思是,一旦在计算机上安装了虚拟字节码,就没有什么可以阻止您将虚拟字节码编译为本地字节码了。可移植性是“分发”阶段的问题,而不是“运行”阶段的问题。
  2. 好吧,那在运行时生成代码呢?好吧,同样适用。没有什么可以阻止您将真正实时需求的实时编译器集成到本机程序中。
  3. 但是运行时无论如何只能将其编译为本地代码一次,并将生成的可执行文件存储在硬盘驱动器上的某种缓存中。当然可以。但是它在时间限制下优化了您的程序,并且从那以后并没有使它变得更好。参见下一段。

这也不是说提前编译也没有优势。即时编译具有时间限制:程序启动时,您不能让最终用户永远等待,因此需要在某处进行权衡。大多数情况下,它们的优化程度较低。我的一个朋友提供了剖析证据,表明内联函数和“手动”展开循环(混淆过程中的源代码)对其C#数字运算程序的性能产生了积极影响。在我这边做同样的事情,我的C程序执行相同的任务,没有产生积极的结果,我相信这是由于允许编译器进行的广泛转换。

但是,我们仍然被固定程序所包围。C#Java无处不在,Python脚本可以编译为某种字节码,而且我敢肯定,还有很多其他编程语言都可以做到这一点。我想必一定有一个很好的理由。那么,什么使即时编译比提前编译好呢?


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

大多数运行时(或与此相关的编译器)宁愿及时或提前编译它们。由于提前编译对于我来说似乎是一个更好的选择,因为编译器有更多时间执行优化,所以我想知道为什么Microsoft,Sun和所有其他公司都朝着相反的方向发展。我对与概要分析相关的优化有些怀疑,因为我对即时编译程序的经验显示出较差的基本优化。

我用了一个例子与C语言代码,只是因为我需要的例子名列前茅的时间编制与刚刚在时间编译。C代码没有发出到中间表示的事实与情况无关,因为我只需要证明提前编译可以产生更好的即时结果。


我不确定你在吵什么。您是说归因于JITted代码的好处实际上是中间格式的结果,然后想知道为什么该中间格式如此流行?
Anon。

不,我不是在反对中间格式的争论。我在争论为什么在安装阶段必须及时编译该中间格式而不是提前编译。
zneak 2010年

2
如此有趣的问题。我阅读了所有帖子,没有提出任何论据使我信服。我仍然不明白为什么像Java这样的语言不会事先将字节码编译为本机代码(无论如何都将使用JIT来完成,所以为什么不在执行之前就编译所有代码?)。我从未见过在本地编译的代码运行速度比使用JIT编译的代码慢(即使后来运行了几周),所以支持JIT的“更好的性能”对我来说毫无意义。
Tiago

@TiagoT,现在有了更多的经验(这个问题已经3年了,您相信吗?),我将更加强调这样一个事实,即当外部类中的类不再需要重新编译字节码程序时,库更改。对于Java和CLR等面向对象的系统来说,这是一个巨大的优势。
zneak

@zneak我现在正在重读您的答案。确实,这是优势,但不是性能优势。当我说我看不到JIT优于AOT编译的优势时,我主要是在谈论性能。有趣的是,使用KitKat 4.4的Google现在也正在测试适用于Android的AOT编译器...因此,我猜它最终将取代当前的JIT编译器,我认为这种迟早会发生。我真的无法确定为什么这么多系统首先迁移到JIT(而不是坚持使用AOT)...
Tiago 2014年

Answers:


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

  2. 同时,更多特定于平台:由于JIT编译发生在代码运行的同一系统上,因此可以针对该特定系统进行非常非常精细的调整。如果您要进行提前编译(仍然希望将相同的程序包交付给所有人),则必须妥协。

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

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

没错,JIT增加了启动成本,因此存在时间限制,而提前编译可能会花费所有时间。这使得它更适合于服务器类型的应用程序,因为它们的启动时间不是很重要,并且在代码变得非常快之前可以进行“预热阶段”。

我想可以将JIT编译的结果存储在某个地方,以便下次可以重新使用它。这样可以为第二个程序运行提供“提前”编译。也许Sun和Microsoft的聪明人认为新的JIT已经足够好了,而额外的复杂性也不值得为此烦恼。


10
对于您的第一点和第二点,如果我只是在安装过程中将字节码提前针对最终用户的特定系统在最终用户的计算机上进行编译,该怎么办?这就是为什么我说这些优点是相对于中间格式而不是即时编译的。关于您的第三点,是的,我想是真的。但是,如果我的本机代码今天运行的速度是现在运行的固定代码的两倍,那么我对10年后的性能并不十分感兴趣。
zneak 2010年

然后,您必须将提前编译器与程序捆绑在一起。这会破坏可移植性,因为您的编译器需要使用本机代码。
Anon。

9
@Anon:应该指出的是,即时编译和提前编译的要求是相同的。没有.NET框架,您将无法运行.NET程序,因此虚拟机已经破坏了其中的可移植性。
zneak 2010年

1
@Anon:不,它将使用与JVM或CLR现在使用的相同(或非常相似)的编译器。
Thilo 2010年

5
对不起,坏了。查看JIT为.NET生成(优化)的机器代码,我不得不说您的第2、3和4点均无效。.NET JIT几乎没有进行任何明显的微调,并且生成的机器代码明显不如(与未优化的C ++机器代码相比)。即使在今天,十年前用C语言生成的代码仍然会更快。最后,没有任何运行时指标的影响。即使在运行或“启动”功能100,000次之后,.NET JIT仍不会更改代码。再次,对您的坏死表示歉意,但我认为这必须加以评论。
Jorma Rebane 2014年

15

NGEN工具页面泄露了天机(或至少提供本机映像与JIT编译的图像比较不错)。提前编译的可执行文件通常具有以下优点:

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

在以下情况下,即时编译的可执行文件通常具有优势:

  1. 本机映像大于其对应的字节码;
  2. 每当修改原始程序集或其依赖项之一时,都必须重新生成本机映像。

对于本机映像,每次生成一个组件时都需要重新生成预先编译的映像是一个巨大的缺点。另一方面,JIT编译的图像无法共享库代码这一事实可能导致严重的内存不足。操作系统可以在一个物理位置加载任何本机库,并与要使用它的每个进程共享它的不变部分,从而可以节省大量内存,尤其是实际上每个程序都使用的系统框架。(我想这可以被JIT编译的程序仅编译它们实际使用的事实所抵消。)

微软对此的普遍考虑是,大型应用程序通常可以从提前编译中受益,而小型应用程序通常则不能。


如果应用程序需要性能,那么AOT绝对是必经之路。在开发过程中也不需要重新编译也不是问题,因为AOT应该在部署期间完成,在此期间接口不再更改。字节码图像在函数调用之间生成更多代码,因此具有一条额外跳转指令的AOT仍比常规JIT代码快。但是,如果您的应用程序很小并且性能不是关键,那么它根本就不重要。
Jorma Rebane 2014年

6

简单的逻辑告诉我们,即使从字节码编译巨大的MS Office大小的程序也将花费太多时间。您将获得大量的启动时间,这将使任何人都不敢购买您的产品。当然,您可以在安装过程中进行预编译,但这也会带来后果。

另一个原因是,不会使用应用程序的所有部分。JIT将仅编译用户关心的那些部分,从而使80%的代码保持不变,从而节省时间和内存。

最后,JIT编译可以应用普通编译器无法实现的优化。就像用跟踪树内联虚拟方法或方法的一部分。从理论上讲,这可以使它们更快。


2
我没有你的第一个论点。是应该针对即时编译还是针对提前编译?由于节省了内存并节省了时间,在编译未修改的程序部件上浪费的时间仅浪费了一次,因此这与IMO无关。无论如何,如果整个虚拟字节码都已加载到内存中,则不会节省太多内存。
zneak 2010年

6
+1。有一种说法是:“如果不一次又一次地运行子例程,为什么您需要它很快(并花时间使它变得更快)?”。当然,这是假设,如果您经常运行JIT,它将使JIT变得非常快。在第一轮汇编中可能不会发生这种情况,但最终...
Thilo 2010年

1
当然,您可以在安装过程中进行预编译,但这也会带来后果。有什么后果?是安装缓慢吗?所有安装都很慢,使更快的应用程序变得有点慢,这似乎对我来说没有后果。
kirie 2014年

4
  1. 更好的反射支持。原则上,这可以在提前编译的程序中完成,但实际上似乎从未发生过。

  2. 通常只能通过动态观察程序来找出优化。例如,内联虚拟函数,转义分析以将堆栈分配转换为堆分配并锁定粗化。


1
事实上,您的程序在运行之前已被编译为本机代码。因此,即使对于本机程序,反射显然也是可能的,这更多的是运行时问题,而不是编译方式。对于转义分析,我认为将其基于运行时观察而非静态代码分析将是一个糟糕的主意。
zneak 2010年

2

也许与现代编程方法有关。您知道,很多年前,您将程序写在一张纸上,其他一些人则将其转换为一叠打孔的卡,然后输入计算机,明天早晨,您将在一卷纸上得到一个故障转储。半磅。所有这些迫使您在编写第一行代码之前多加考虑。

那些日子已经一去不回。使用PHP或JavaScript之类的脚本语言时,您可以立即测试任何更改。尽管应用服务器为您提供了热部署,但Java并非如此。因此,可以快速编译Java程序非常方便,因为字节码编译器非常简单。

但是,不存在仅JIT语言。预先编译器已经在Java上使用了很长一段时间,最近,Mono将其引入到CLR中。实际上,由于AOT编译,MonoTouch完全可以实现,因为Apple的应用商店中禁止使用非本机应用。


1
是的,当然,大多数固定语言也都具有提前编译器。实际上,Mono并没有做任何新的事情,因为Microsoft有一个名为ngen的实用程序(msdn.microsoft.com/en-us/library/6t9t5wcf(VS.80).aspx),它可以从CLR可执行文件生成本机代码,一段时间 但是事实是,大多数语言都会在可能的情况下使用即时变体,这就是我试图理解的行为。
zneak 2010年

2

我也一直试图理解这一点,因为我看到Google正在将其Dalvik虚拟机(本质上是另一个Java虚拟机,例如HotSpot)替换为Android Run Time(ART),后者是AOT编译器,但是Java通常使用HotSpot ,这是一个JIT编译器。显然,ARM比Dalvik快2倍……所以我对自己说:“为什么Java也不使用AOT?”。无论如何,据我所知,主要区别在于JIT在运行时使用了自适应优化,例如,它仅允许将经常执行的字节码部分编译为本机代码。而AOT会将整个源代码编译成本机代码,而数量较少的代码则比数量较大的代码运行得更快。
我必须想象大多数Android应用程序都是由少量代码组成的,因此平均而言,将整个源代码编译为本机代码AOT更有意义,并且可以避免因解释/优化而产生的开销。


在这种情况下,Java字节码的主要优点是可移植。该应用程序可以Java代码的形式分发,然后编译为电话所使用的任何基础体系结构。Android支持MIPS,ARM和x86,因此分发Java应用程序然后进行本机编译是很有意义的。在这种情况下,Java字节码用作美化的中间表示形式。
zneak

我了解Java /字节码具有可移植性的目的。我试图帮助阐明使用JIT相对于AOT的优势...我认为这取决于最终用户的角度,即取决于应用程序源代码的大小(即,较小-> AOT,较大-> JIT) 。它们都可以移植给任何最终用户。否则,为什么所有的客户端编译器都不是AOT?
Scott Ferrell 2014年

2

我未在此处列出的JIT的优势之一是能够在单独的程序集/ dll / jar中进行内联/优化(为简单起见,我将从这里开始使用“程序集”)。

如果您的应用程序引用了在安装后可能会更改的程序集(例如,预安装的库,框架库,插件),则“安装时编译”模型必须避免跨程序集边界内联方法。否则,在更新引用的程序集时,我们将不得不在系统上引用程序集的过程中找到所有此类内联代码,并将其替换为更新的代码。

在JIT模型中,我们可以自由地跨程序集内联,因为我们只关心在不更改基础代码的情况下为单个运行生成有效的机器代码。


1
这甚至可能取决于运行时行为,而不是安装。今天连接到不同的数据库,使用不同的驱动程序jar,处理文件而不是http url,并通过完全不同的代码路径运行。等等
Holger


0

platform-b​​rowser-dynamic和platform-b​​rowser之间的区别是角度应用程序的编译方式。通过使用动态平台,可以将“及时”编译器发送到前端以及您的应用程序。这意味着您的应用程序正在客户端编译。另一方面,使用平台浏览器会导致将应用程序的提前预编译版本发送到浏览器。通常,这意味着发送给浏览器的软件包要小得多。https://angular.io/docs/ts/latest/guide/ngmodule.html#!#bootstrap上用于引导的angular2-文档进行了详细说明。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.