YJIT为CRuby建立一个新的JIT编译器

223 阅读7分钟

YJIT:为CRuby构建一个新的JIT编译器

20世纪80年代和90年代,Perl、Ruby、Python、PHP和JavaScript诞生了:这些解释型、动态类型的编程语言更倾向于易用性和灵活性而不是性能。在许多方面,这些编程语言是周围环境的产物。90年代是网络公司炒作的高峰期,CPU时钟速度仍然大约每18个月翻一番。看起来这种增长永远不会结束。你不必真的尝试让你的软件快速运行,因为计算机只会越来越快,问题会自己解决的。今天,情况有些不同。我们正在达到当前硅制造技术的极限,我们不能依靠单核性能的提高来解决我们的性能问题。由于移动设备和环境问题,我们开始意识到能源效率的重要性。

去年,在大流行期间,我在Shopify找了一份工作,这家公司运行着一个由Ruby on Rails驱动的大规模服务器基础设施。我加入了一个由多名软件工程师组成的团队,致力于以各种方式提高Ruby代码的性能,从优化CRuby解释器及其垃圾收集器到实现TruffleRuby,一种替代Ruby的实现。从那时起,我一直在与来自Shopify和GitHub的熟练工程师团队合作开发YJIT,这是一个在CRuby内部构建的新的及时编译器(JIT)。

这个项目对Shopify和全世界的Ruby开发者都很重要,因为速度是一个被低估的功能。CRuby内部已经有一个JIT编译器,被称为MJIT,它已经工作了三年。虽然它在较小的基准上实现了速度提升,但到目前为止,它在广泛使用的Ruby应用程序(如Ruby on Rails)上实现实际速度提升方面并不成功。通过YJIT,我们采取了一种数据驱动的方法,并特别关注大型应用程序的性能热点,如Rails和Shopify Core(Shopify的主要Rails单体)。

什么是YJIT?

Tobi Lütke在推特上介绍YJIT

YJIT是一个在CRuby内部逐步建立JIT编译器的项目,这样越来越多的代码由JIT执行,最终将取代解释器进行大部分的执行。这个编译器很快就会正式成为CRuby的一部分,它基于Basic Block Versioning(BBV),是我在读博士期间开始开发的JIT编译器架构。我在今年的MoreVMs 2021研讨会上发表了关于YJIT的演讲,如果你想了解更多关于我们所采取的方法,可以在RubyKaigi 2021上发表另一篇演讲。

目前的成果

目前,我们的YJIT项目已经进行了一年,到目前为止,我们对结果很满意,自从MoreVMs讲座后,结果有了明显的改善。根据我们的基准测试,与CRuby解释器相比,我们在railsbench上的速度提高了20%,在液体模板渲染上提高了39%,在activerecord上提高了37%。YJIT的预热速度也非常快。它在任何基准的单次迭代后就达到了接近峰值的性能,并且在每个基准上的表现至少与解释器一样好,甚至在第一次迭代时也是如此。 基准测试速度(迭代/秒)与解释器的性能成比例(越高越好)

在CRuby中构建YJIT有一些限制。这意味着我们的JIT编译器必须用C语言编写,而且我们必须与CRuby代码库中的设计决定一起工作,而这些设计决定并没有考虑到高性能JIT编译器的问题。然而,它有一个关键的优势,即YJIT能够与现有的Ruby代码和软件包保持几乎100%的兼容性。我们通过了CRuby测试套件,包括大约3万个测试,我们也能够通过Shopify核心CI的所有测试,这个代码库包含超过300万行的代码,并且(直接和间接)依赖于500多个Ruby gem,以及GitHub后台CI的所有测试。我们也有一个工作部署到Shopify的一小部分生产服务器上。

我们相信YJIT的BBV架构在编译动态类型的代码时提供了一些关键优势。对整个代码生成管道的端到端控制将使我们能够比基于GCC的MJIT的现有架构走得更远。值得注意的是,YJIT可以根据类型信息快速地对代码进行专业化处理,并根据程序的运行时行为在运行时修补代码。在编译速度和预热时间方面的优势也是难以比拟的。

接下来的步骤

Ruby核心开发者已经邀请YJIT团队将编译器合并到Ruby 3.1中。对于我和我的同事来说,能够让我们的工作正式成为Ruby的一部分,这是一个巨大的荣誉。这意味着,在几个月后,每个Ruby开发者都将有机会通过简单地将一个命令行选项传递给Ruby二进制文件来尝试YJIT。然而,我们的旅程并没有到此为止,我们已经有计划让YJIT和CRuby变得更快。

目前,railsbench中只有大约79%的指令是由YJIT执行的,其余的都在解释器中运行,这意味着我们仍有很多事情可以做,以改善我们目前的结果。我们有一条清晰的前进道路,我们相信YJIT可以提供比现在更好的性能。然而,作为构建YJIT的一部分,我们不得不通过CRuby的实现来详细了解它。在这样做的过程中,我们已经确定了其架构中的一些关键元素,我们相信这些元素可以被改进以释放出更高的性能。这些改进不只是对YJIT有帮助,对MJIT也有帮助,其中一些甚至会使解释器更快。因此,我们可能会尝试将其中的一些工作从YJIT中分离出来,推向上游。

我可能会在未来的博文中对其中的一些内容进行扩展,但这里是我们想解决的CRuby潜在改进的暂定清单:

  • 将CRuby转向基于对象形状的对象模型。
  • 改变CRuby的类型标记方案,以减少类型检查的成本。
  • 实现一个更精细的常量缓存机制。
  • 一个更快、更轻的调用约定。
  • 在Ruby中重写C语言的运行时方法,以便JIT编译器可以内联通过它们。

Matz(Yukihiro Matsumoto)在他最近在Euruko 2021的演讲中表示,在不久的将来,Ruby将在语言添加方面保持保守。我们相信这是一个明智的决定,因为语言的快速变化会使JIT实现难以落地并保持更新。在我们看来,Ruby专注于内部变化是有一定意义的,这将使语言更加强大,并在未来提供非常有竞争力的性能。

我希望你和我们一样对YJIT和Ruby的未来感到兴奋。如果你有兴趣尝试YJIT,它可以在GitHub上以与CRuby相同的开源许可提供。如果你遇到了BUG,我们会很感激,如果你能打开一个问题,帮助我们找到一个简单的重现方法。敬请关注,关于YJIT的另外两篇博文即将发表,其中有关于如何试用YJIT的细节,以及我们为speed.yjit.org建立的性能跟踪系统。