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

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

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

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

根据我阅读的定义,JIT编译只是在解释代码之前编译代码。

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

请先解释它们之间的区别。

评论

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

阅读有关即时编译,AOT编译器,编译器,解释器,字节码的维基页面,以及Queinnec的《 Lisp in Small Pieces》

@BasileStarynkevitch-我总是赞成在小篇幅中对Lisp的任何引用...

@davidbak:我从1988年以前就认识了Christian Queinnec。我目前的宠物项目是RefPerSys。给我发电子邮件至basile@starynkevitch.net

#1 楼

概述
语言X的解释器是一种程序(或机器,或一般来说只是某种某种机制),它执行用语言X编写的任何程序p,从而执行效果并评估由语言X规定的结果X的规范通常是各自指令集的解释器,尽管现代的高性能工作站CPU实际上比这更复杂。它们实际上可能具有基础专有私有指令集,并且可以转换(编译)或解释外部可见的公共指令集。
从X到Y的编译器是程序(或机器,或者仅仅是某种机制)。一般)将某种语言X的任何程序p转换成某种语言Y的语义等效程序p',使得该程序的语义得以保留,即用Y的解释器解释p'将产生相同的结果并且具有与X的解释器解释p相同的效果。(请注意,X和Y可能是同一语言。)
术语“提前”(AOT)和“即时”(JIT)指编译发生的时间:这些术语中的“时间”是“运行时”,即JIT编译器在运行时编译程序,AOT编译器在运行前编译程序。请注意,这要求从语言X到语言Y的JIT编译器必须以某种方式与语言Y的解释器一起工作,否则将无法运行该程序。 (因此,例如,将JIT编译为x86机器代码的JIT编译器在没有x86 CPU的情况下是没有意义的;它在运行时编译该程序,但是在没有x86 CPU的情况下该程序将无法运行。)
请注意,这种区别对解释器没有意义:解释器运行程序。在程序运行之前先运行程序的AOT解释器或在程序运行时运行程序的JIT解释器的想法是荒谬的。
因此,我们有了:

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

JIT编译器
在JIT编译器系列中,它们的确切编译时间,频率和粒度有许多不同。例如,Microsoft CLR中的JIT编译器仅编译一次(加载时),一次编译整个程序集。其他编译器可能会在程序运行时收集信息,并在新信息可用时对其进行多次重新编译,以便更好地对其进行优化。一些JIT编译器甚至可以对代码进行优化。现在,您可能会问自己,为什么有人会想要这样做?取消优化允许您执行非常激进的优化,这些操作实际上可能是不安全的:如果事实证明您过于激进,则可以再次退出,而对于无法进行优化的JIT编译器,程序将崩溃或返回错误的结果换句话说,您根本就不允许自己首先进行积极的优化。
JIT编译器可以一次性编译一些静态代码单元(一个模块,一个类,一个函数,一个方法)。 ,…;例如,它们通常被称为一次方法JIT),或者它们可以跟踪代码的动态执行以找到动态跟踪(通常是循环),然后将其编译(称为跟踪JIT)。 br />组合解释器和编译器
可以将解释器和编译器组合为单个语言执行引擎。有两种典型的方案可以做到这一点。
将X到Y的AOT编译器与Y的解释器结合起来。这里,X通常是为人类可读性而优化的一些高级语言,而Y是为机器可解释性而优化的紧凑型语言(通常是某种字节码)。例如,CPython Python执行引擎具有将Python源代码编译为CPython字节码的AOT编译器,以及解释CPython字节码的解释器。同样,YARV Ruby执行引擎具有将Ruby源代码编译为YARV字节码的AOT编译器,以及解释YARV字节码的解释器。你为什么想这么做? Ruby和Python都是非常高级且有点复杂的语言,因此我们首先将它们编译为易于解析和易于解释的语言,然后再对该语言进行解释。
将解释器与编译器是混合模式执行引擎。在这里,我们将实现同一语言的两种“模式”“混合”在一起,即X的解释器和X到Y的JIT编译器。(因此,这里的区别在于,在上述情况下,我们有多个“阶段”通过编译器编译程序,然后将结果馈入解释器,在这里,我们可以使两种语言在相同的语言中并行工作。)由编译器编译的代码的运行速度通常比由编译器执行的代码快一个解释器,但实际上首先编译代码需要时间(特别是,如果您要大量优化代码以使其运行得非常快,则需要很多时间)。因此,为了在JIT编译器正在忙于编译代码的这段时间之间架起桥梁,解释器可以开始运行代码,并且JIT完成编译后,我们可以将执行切换到已编译的代码上。这意味着我们可以同时获得已编译代码的最佳性能,但是我们不必等待编译完成,并且我们的应用程序可以立即开始运行(尽管速度不快)。
实际上,这只是混合模式执行引擎的最简单的应用。例如,更有趣的可能性是不立即开始编译,而是让解释器运行一点,然后收集统计信息,配置文件信息,类型信息,有关采用特定条件分支的可能性的信息,称为哪些方法的信息等等),然后将此动态信息提供给编译器,以便它可以生成更多优化的代码。这也是实现我上面提到的去优化的一种方法:如果事实证明您在优化方面过于积极,则可以丢弃(部分)代码,然后返回解释。例如,HotSpot JVM就是这样做的。它既包含JVM字节码的解释器,又包含JVM字节码的编译器。 (实际上,它实际上包含两个编译器!)
结合这两种方法也是可能的,而且实际上很常见:两个阶段,第一个阶段是将X编译为Y的AOT编译器,第二个阶段是混合的解释Y并将Y编译为Z的模式引擎。Rubinius Ruby执行引擎以这种方式工作,例如:它有一个AOT编译器,将Ruby源代码编译为Rubinius字节码,还有一个混合模式引擎,该引擎首先解释Rubinius字节码,一次解释
请注意,解释器在混合模式执行引擎的情况下扮演的角色,即提供快速启动,还可能收集信息并提供备用功能的工具也可以由第二个JIT编译器播放。
例如,这就是第二代Google的V8 ECMAScript引擎的工作方式。 V8的这一代人永远不会解释,它总是会编译。第一个编译器是非常快速,非常苗条的编译器,它的启动速度非常快。不过,它产生的代码不是很快。该编译器还将配置文件代码注入其生成的代码中。另一个编译器速度较慢并且使用更多的内存,但是生成的代码快得多,并且它可以使用通过运行第一个编译器编译的代码收集的性能分析信息。

评论


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

–塞巴斯蒂安·雷德尔(Sebastian Redl)
16-09-26在17:13

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

– Paul Draper
17年1月11日,下午5:28

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

–剃刀
17 Mar 22 '17 at 6:32

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

– eush77
17年11月26日在5:36

阅读整个答案后,我可能会发现一个错字。 “将解释器和编译器结合在一起的另一种方法是混合模式执行引擎。在这里,我们将实现同一语言的两种“模式”“混合”在一起,即X的解释器和X到Y的JIT编译器。 ”。我想您本来想为Y口译的,对吧?

–里克
20 Nov 3'8:35