激励 JIT 编译器生成¶
这是一个关于 RPython 的 Just-In-Time 编译器生成方法的非技术性介绍和动机。
动机¶
概述¶
为像 Python 这样复杂的动态语言编写解释器并非易事,尤其是在为了性能目标,我们也想要编写一个 Just-in-Time (JIT) 编译器的情况下。
好消息是,我们并没有这么做。我们确实为 Python 编写了一个解释器,但我们从未为 PyPy 中的 Python 编写任何 JIT 编译器。相反,我们利用了这样一个事实:我们为 Python 编写的解释器是用 RPython 编写的,RPython 是一种很棒的高级语言——并且我们将其自动转换为 Python 的 JIT 编译器。
当然,这种转换对用户(即编写 Python 程序的程序员)来说是完全透明的。目标(我们已经实现了)是支持所有 Python 功能——例如,包括随机帧访问和调试器。但它对语言实现者(即 Python 解释器的源代码)也大多是透明的。它只需要一点指导:我们必须在解释器的源代码中添加少量提示。基于这些提示,JIT 编译器生成器会生成一个 JIT 编译器,该编译器在构造上具有与原始解释器相同的语言语义。这个 JIT 编译器本身在运行时生成机器代码,积极优化用户的程序并带来巨大的性能提升,同时保持语义不变。当然,有趣的部分在于,我们的 Python 语言解释器可以随着时间的推移而发展,而不会与 JIT 编译器失去同步。
我们所遵循的路径¶
我们之前版本的 PyPy 的 JIT 生成器是基于部分求值的。这是一个众所周知且经过大量研究的主题,被认为非常有前景。已经有很多尝试使用它将解释器自动转换为编译器。但是,它们都没有为现实世界的语言带来实质性的加速。我们认为,缺失的关键见解是使用部分求值来生成 Just-in-Time 编译器,而不是传统的 Ahead-of-Time 编译器。如果事实证明这一点是正确的,那么动态语言的实际速度可能会得到极大的提高。
所有这些以前的 JIT 编译器生成器都生成类似于手工编写的 Psyco 的 JIT 编译器。但是今天,从 2009 年开始,我们的原型不再使用部分求值——至少不是以能说服论文评审员的方式。它反而基于跟踪 JIT的概念,最近在 Java 和 JavaScript 中得到了研究。然而,与迄今为止所有现有的跟踪 JIT 相比,部分求值为我们提供了一些我们之前在 JIT 生成器中已经拥有的额外技术,特别是如何通过消除分配来优化结构。
与我们当前 JIT 最接近的比较是 Tamarin 的 TraceMonkey。但是,这个 JIT 编译器是手动编写的,这需要相当大的工作量。相反,我们在 RPython 的级别上编写了一个 JIT 生成器,这意味着我们的最终 JIT 不必——实际上,不能——被编写成编码完整 Python 语言的所有细节。这些细节是由这样一个事实自动提供的:我们有一个完整的 Python 解释器。
实际结果¶
我们生成的 JIT 编译器使用了一些迄今为止尚未广泛使用但也不完全是新技术的技术。我们想在这里说明的重点不是我们正在推动给定动态语言运行速度的理论极限。我们的观点是:我们正在使为所有动态语言(无论多么复杂或不流行)拥有相当好的 Just-In-Time 编译器变得实用,例如没有大型行业或学术支持的开源动态语言,或内部领域特定语言。实用是指这应该
- 简单:只需要比最初编写解释器多一点的努力。
- 可维护:我们生成的 JIT 编译器不是单独的项目(我们不生成单独的源代码,而只是生成会被编译成生成的 VM 的一次性 C 代码)。换句话说,每次修改高级解释器时,整个 JIT 编译器都会重新生成,这样无论语言发展速度多么快,它们都不会失去同步。
- 足够快:我们可以从生成的 JIT 编译器中获得相当不错的性能。当然,这才是重点。
提高速度的替代方法¶
|
有很多方法可以提高动态编程语言的执行速度,其中大多数只能带来少量改进,而且没有一种方法能提供我们方法所提供的灵活性和可定制性。在过去 6 年的调整中,CPython 的速度仅提高了 1.3 或 1.4 倍(取决于基准测试)。许多调整也适用于 PyPy。事实上,一些 CPython 的调整最初是作为 PyPy 的调整而产生的。
IronPython 最初实现了大约是 CPython 1.8 倍的速度,方法是省略了一些语言细节并利用微软在使 .NET 平台快速运行方面的大量投资;当前更完整的实现与 CPython 的速度大致相同。总的来说,现有的方法在速度方面已经走到了尽头。微软的动态语言运行时 (DLR) 通常在此上下文中被提及,它本质上只是一个 API,用于使 IronPython 中首创的技术正式化。充其量,它只会带来另一次小幅改进。
另一种经常被提及的技术是向语言中添加类型以加快速度:要么是显式的可选类型,要么是软类型(即推断出的“可能”类型)。对于 Python 来说,该领域的所有项目都从语言的简化子集开始;没有一个项目扩展到接近完整语言的程度。这将是一项重大工作,并且特定于平台和语言。此外,维护将是一件麻烦事:我们认为,在 CPython 中很容易实现的许多更改,都可能使之前精心调整的优化失效。
为了实现速度的重大改进,JIT 技术是必要的。对于 Python 来说,Psyco 通常可以提高 2 到 4 倍的速度——在算法示例中甚至可以提高 100 倍。它已经走到了尽头,因为开发和维护它存在困难且成本巨大。它对语言语义的编码相对较差——关于 Python 行为的知识需要手动编码并保持最新。至少,Psyco 即使在遇到它不支持的众多 Python 结构之一时也能正常工作,因为它会回退到 CPython。
另一种类型的现有技术是像 Jikes 这样的自托管 JIT 编译器。Jikes 是用 Java 编写的 Java JIT 编译器。它对语言语义的编码很差;要将类似 Python 语言的所有细节直接编码到 JIT 编译器中,需要付出巨大的工作量。它也具有有限的可移植性,这对 Python 来说是一个问题;为了在与预期的低级环境不同的环境中运行,JIT 编译器的大部分内容可能都需要重新定位。
简单地重用现有的经过良好调整的 JIT(例如 JVM 的 JIT)实际上并不可行,因为实现者语言和主机 VM 语言之间存在概念上的不匹配:前者需要以一种方式编译到目标环境中,以便 JIT 能够显着加快其速度——这种方法在 Python 中基本上已经失败了:即使 CPython 是一个简单的解释器,它的 Java 和 .NET 重新实现的速度也没有显著提高。
最近,JIT 领域启动了几个更大的项目。例如,Sun Microsystems 正在投资 JRuby,该项目旨在使用 Java Hotspot JIT 来提高 Ruby 的性能。但是,这需要大量的手工操作,并且只会为一个平台上的一个语言提供加速。一些问题很微妙,例如,如何在动态语言中消除持续装箱和拆箱的开销。与 PyPy 相比,一个优势是可以执行一些不适合元编程方法的手动优化。但是元编程使 PyPy JIT 可重用于许多不同语言的许多不同执行平台。也可以组合这些方法——我们可以使用我们的 JIT 获得实质性的加速,然后将结果提供给 Java 的 Hotspot JIT 以进一步改进。我们中的一员甚至还是JSR 292 专家组的成员,以定义对 JVM 的补充以更好地支持动态语言,并且正在贡献我们 JIT 研究的见解,这些见解也将使 PyPy 受益。
最后,跟踪 JIT 现在正在为像 JavaScript 这样的动态语言(使用 TraceMonkey)出现。PyPy 生成的代码与跟踪 JIT 的概念非常相似(但不是手写的)。