了解差异:传统解释器,JIT编译器,JIT解释器和AOT编译器


130

我试图了解传统解释器,JIT编译器,JIT解释器和AOT编译器之间的区别。

解释器只是一台以某种计算机语言执行指令的机器(虚拟或物理)。从这个意义上讲,JVM是解释器,物理CPU是解释器。

提前编译只是意味着在执行(解释)代码之前将代码编译为某种语言。

但是我不确定JIT编译器和JIT解释器的确切定义。

根据我所读的定义,JIT编译只是在解释代码之前就对其进行编译。

因此,基本上,JIT编译是AOT编译,是在执行(解释)之前完成的吗?

还有一个JIT解释器,是一个既包含JIT编译器又包含解释器并在解释它之前对其进行编译(JIT)的程序?

请说明差异。


4
为什么使您相信“ JIT编译器”和“ JIT解释器”之间有区别?对于同一件事,它们本质上是两个不同的词。JIT的总体概念是相同的,但是有各种各样的实现技术,不能简单地分为“编译器”与“解释器”。
Greg Hewgill 2014年

2
阅读有关准时编译AOT编译器编译器解释器字节码的维基百科页面,以及Queinnec的《Lisp in Small Pieces
Basile Starynkevitch》,2014年

Answers:


198

总览

语言X解释器是一个程序(或机器,或一般来说,只是某种某种机制),它执行用语言X编写的任何程序p,从而执行效果并评估X规范所规定的结果。CPU通常是各自指令集的解释器,尽管现代高性能工作站CPU实际上比这要复杂得多。它们实际上可能具有基础专有私有指令集,并且可以转换(编译)或解释外部可见的公共指令集。

编译器Xý是一个程序(或一台机器,或只是某种一般机制的),该转换的任何程序p部分语言X成语义等同程序P'在一些语言ÿ在这样的方式的语义保留该程序的,即用Y的解释器解释p'会产生与用X的解释器解释p相同的结果,并具有相同的效果。(请注意,XY可能是相同的语言。)

术语提前(AOT)即时(JIT)指的是何时进行编译:这些术语中所指的“时间”是“运行时”,即,JIT编译器按原样编译程序。在运行时,AOT编译器会在程序运行之前对其进行编译。请注意,这要求从语言X到语言Y的JIT编译器必须以某种方式与语言Y的解释器一起工作,否则将无法运行该程序。(因此,例如,没有x86 CPU的将JIT编译器编译为x86机器代码的JIT编译器是没有意义的;它在运行时编译该程序,但没有x86 CPU则该程序将无法运行。)

注意,这种区别对解释器没有意义:解释器运行程序。在程序运行之前运行程序的AOT解释器或在程序运行时运行程序的JIT解释器的想法是荒谬的。

因此,我们有:

  • AOT编译器:在运行之前进行编译
  • JIT编译器:在运行时进行编译
  • 解释器:运行

JIT编译器

在JIT编译器系列中,关于它们的确切编译时间,频率和粒度,仍然存在许多差异。

例如,Microsoft的CLR中的JIT编译器只编译一次代码(加载时),并且一次编译整个程序集。其他编译器可能会在程序运行时收集信息,并在新信息可用时对其进行多次重新编译,以便更好地对其进行优化。一些JIT编译器甚至可以对代码进行优化。现在,您可能会问自己,为什么有人会想要这样做?取消优化允许您执行非常激进的优化,这些操作实际上可能是不安全的:如果事实证明您过于激进,则可以再次退出,而对于无法进行优化的JIT编译器,则无法运行首先进行积极的优化。

JIT编译器可以一次性编译某些静态代码单元(一个模块,一个类,一个函数,一个方法,…;例如,通常称为一次一次 JIT 方法),或者它们可以跟踪动态代码。执行代码以查找动态跟踪(通常是循环),然后将其编译(称为跟踪 JIT)。

解释器和编译器的组合

解释器和编译器可以合并为一种语言执行引擎。有两种典型的方案可以做到这一点。

结合从AOT编译器XŸ与一位翻译ÿ。这里,X通常是为人类可读性而优化的一些高级语言,而Y是是一种针对机器的可解释性进行优化的紧凑型语言(通常为某种字节码)。例如,CPython Python执行引擎具有将Python源代码编译为CPython字节码的AOT编译器,以及解释CPython字节码的解释器。同样,YARV Ruby执行引擎具有一个AOT编译器,该编译器将Ruby源代码编译为YARV字节码,以及一个解释器,用于解释YARV字节码。你为什么想这么做?Ruby和Python都是非常高级且有点复杂的语言,因此我们首先将它们编译成易于解析和易于解释的语言,然后再解释语言。

组合解释器和编译器的另一种方法是混合模式执行引擎。在这里,我们将实现同一语言的两种“模式”“混合” 在一起,即X的解释器和XY的JIT编译器。(因此,这里的区别在于,在上述情况下,我们有多个“阶段”,编译器对程序进行编译,然后将结果馈送到解释器中,在这里,我们使两个语言在同一语言中并行工作。 )由编译器编译的代码往往比解释器执行的代码运行速度更快,但是实际上首先编译代码需要时间(特别是如果您要大量优化代码以使其运行)速度非常快,这需要很多时间)。因此,为了在JIT编译器正在忙于编译代码的这段时间之间架起桥梁,解释器可以开始运行代码,并且JIT完成编译后,我们可以将执行切换到已编译的代码上。这意味着我们可以同时获得编译后的代码的最佳性能,但是我们不必等待编译完成,并且我们的应用程序可以立即开始运行(尽管速度可能不快)。

实际上,这只是混合模式执行引擎的最简单的应用。例如,更有趣的可能性是不立即开始编译,而是让解释器运行一会儿,并收集统计信息,配置文件信息,类型信息,有关采用特定条件分支的可能性的信息,称为哪些方法的信息等等),然后将此动态信息提供给编译器,以便它可以生成更多优化的代码。这也是实现我上面所讨论的去优化的一种方式:如果事实证明您在优化方面过于积极,则可以丢弃(一部分)代码,然后返回解释。例如,HotSpot JVM就是这样做的。它既包含JVM字节码的解释器,又包含JVM字节码的编译器。(事实上​​,两个编译器!)

结合这两种方法也是可能的,并且实际上很常见:两个阶段,第一阶段是将X编译为Y的AOT编译器,第二阶段是既解释Y又将Y编译为Z的混合模式引擎。例如,Rubinius Ruby执行引擎就是这样工作的:它有一个AOT编译器,将Ruby源代码编译为Rubinius字节码,还有一个混合模式引擎,该引擎首先解释Rubinius字节码,一旦它收集了一些信息,便将最常用的方法编译为本地方法。机器代码。

注意,在混合模式执行引擎的情况下,解释器所扮演的角色,即提供快速启动,还可能收集信息并提供回退功能,也可以由第二个JIT编译器扮演。例如,这就是V8的工作方式。V8从不解释,而是始终进行编译。第一个编译器是一个非常快速,非常苗条的编译器,它的启动速度非常快。不过,它产生的代码不是很快。该编译器还将配置文件代码注入其生成的代码中。另一个编译器速度较慢,使用的内存更多,但生成的代码快得多,并且它可以使用通过运行第一个编译器编译的代码收集的性能分析信息。


1
Python和Ruby字节码编译器是否真的算作AOT?鉴于两种语言都允许动态加载模块,这些模块在加载时进行编译,因此它们确实在程序运行时运行。
塞巴斯蒂安·雷德尔

1
@SebastianRedl,使用CPython,您可以运行python -m compileall .或加载一次模块。即使在后一种情况下,由于文件仍然保留并在第一次运行后被重用,因此看起来确实像是AOT。
Paul Draper

您有参考资料供进一步阅读吗?我想进一步了解V8。
文斯Panuccio

@VincePanuccio帖子引用了Full-Codegen和Crankshaft编译器,此后已被替换。您可以在线找到有关它们的信息。
eush77

CLR Jit通过方法而不是整个程序集编译方法
Grigory
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.