Stripe开源可增加Ruby程序性能的Sorbet编译器

线上支付服务Stripe在GitHub上,开源其生产环境正在使用的Sorbet编译器,该编译器使用Sorbet和LLVM,根据Ruby源码文件产生符合Ruby的C扩展组件API。目前这个编译器仍处于实验阶段,虽然Sorbet编译器与Sorbet在同一个存储库中,但是并不影响Sorbet的内容,Sorbet也不依赖Sorbet编译器,官方目前并没有计划要发布预构建的Sorbet编译器二进制文件。

Stripe在2019年的时候,开源了Ruby类型检查工具Sorbet,Sorbet能够静态分析项目程序代码,以找出类型不一致的错误,并且在程序执行时动态检测类型,以发现Ruby程序中所存在的安全性风险。由于Stripe中绝大多数的程序代码都是由Ruby撰写而成,因此Stripe进行了多项能够提高Ruby性能与安全性的项目,Sorbet编译器便是其中一项,过去Sorbet编译器的程序代码放在私有存储库,只与少数对象共享,但因为更新项目手续麻烦,因此Stripe决定将编译器开源在Sorbet存储库中,以方便与Sorbet共享同一个内部数据结构。

Stripe的主要产品是API,他们必须压低延迟来提供更好的服务,Stripe采取两个策略降低延迟,除了减少I/O时间,他们也着手加速运算时间,而构建Sorbet编译器,是他们加速Ruby程序的解决方案之一。之所以将Sorbet编译器设计为AOT(Ahead-Of-Time)而非JIT编译器,官方提到,是因为不需要将整个语言Runtime都部署到生产环境,使用AOT编译器,就只要在CI阶段中使用,经过编译的构件,也可以刚好适用于现有的构建工作管线,即便发生问题,可以停止加载有问题的编译构件,让Ruby虚拟机执行原始程序代码就可以减轻问题带来的影响。

AOT编译器在概念上更简单,Stripe认为,实例一个能够改进性能的AOT编译器,比起JIT编译器所花费的时间更少。而且使用Sorbet对项目进行类型检查,AOT编译器就可以使用Sorbet所产生的静态类型信息,虽然JIT在执行时也能观察类型,来得知编码的方式,但是以Stripe的用例来说,Ruby程序代码库大量使用Sorbet,因此使用静态类型信息有其优势。

Sorbet编译器产生Ruby原生扩展组件,这些组件能够与其他Ruby程序代码互通,因此使用Sorbet编译器不需要放弃Ruby虚拟机,并且可以继续使用现有的Gems和扩展组件。

Sorbet编译器建基在LLVM和现有的Ruby虚拟机之上,当编译程序代码的时候,起点从普通的*.rb程序代码文件开始,这些文件会进入到Sorbet中进行类型检查,类型检查的输出为自定义中间语言(Intermediate Representation,IR),Sorbet编译器使用这些IR来生成LLVM IR,LLVM接着使用LLVM IR来产生原生共享对象*.so文件,这些共享对象就是Ruby原生扩展组件,符合且使用与Gems相同的API,也就是说,编译后的构件,使用与其他Ruby程序代码相同的对象模型和Runtime表示。

目前Stripe已经在内部的生产环境,使用Sorbet编译器约1年的时间,程序代码增加的性能,根据不同工作负载而有所差异,Stripe并没有分享更多性能信息,但是在程序代码库中,提供了一些综合基准检查,但官方提到,这些基准测试并没有代表性,只能用来调试和减少性能问题。