我有一个ruby web服务,最近我检查了使用jruby(9.1.17.0,openjdk 1.8)是否会比当前使用的mri(2.5.0)提高性能。我预计情况可能会是这样,因为性能瓶颈是计算响应数据的大量“基本算法”,而jruby在计算量大的基准上往往优于mri。
然而,事实并非如此:我尝试过许多jruby/jvm选项的组合,但“稳定状态”比mri慢2倍。稳定状态是在重复请求大约100次之后实现的,jvm显然在执行jit魔术,因为相对于初始请求,性能提高了2.5倍。
我想知道这是预料中的行为还是意料之外的行为。所以我想知道:jruby在哪些典型的工作负载上可能比mri慢?“浮点数基本算法”真的在其中吗?
(性能瓶颈在mri和jruby中处于相同的位置,使用适当的分析器确定。最初这篇文章说jruby的速度只有20%,但我后来引入了一个优化,它将mri性能提高了近2倍,但几乎没有改变jruby的性能。我怀疑jvm会自动执行相同的优化,因为它基本上相当于“不断折叠”(constant folding))。
最佳答案
如果您在Integer
s上进行计算,并且Integer
s适合本机字大小-1位,那么yarv将在Fixnum
s上使用本机算法。如果您在Float
s上进行计算,并且是在64位平台上,并且您的计算适合62位,那么yarv将在flonum上使用本机fpu算法。无论是哪种情况,它都不会比这快得多,除非您的操作非常简单,以至于jvm jit(或jruby编译器)可以完全优化它们,不断地折叠它们,或类似的东西。
最佳点是大于63位小于64位的Integer
s,jruby将其视为本机整数,而不是yarv,大于62位小于64位的Float
s也是如此。在这个范围内,jruby将使用本机操作,但yarv不会,这使jruby具有性能优势。
一般来说,yarv在延迟方面优于jruby,特别是在启动时间方面。不过,这在很大程度上取决于使用的jvm和环境。有些jvm被设计成启动非常快(例如ibm j9,imo应该是默认的桌面jvm,而不是oracle热点)或avian(实际上不是jvm,因为它只实现jvm和jre规范的一个子集,但是,尽管如此,仍然可以运行许多不使用任何未实现功能的非平凡程序,jruby就是其中之一。)此外,还有一些环境和配置允许您在内存中保留和重用jvm和jruby实例,从而减少了大部分启动时间。
第二个大的是yarv c扩展。yarv有一个非常开放和广泛的c扩展api。实际上,yarv c扩展可以访问yarv的几乎所有私有内部实现细节。另一方面,jvm“c扩展”总是需要通过一个安全屏障。它们只能破坏由调用它们的Java代码显式传递给它们的内存,它们不能破坏其他内存,更不用说JVM本身了。然而,这是一个性能成本:从Java调用C,反之亦然,通常比从YARV调用C慢,反之亦然。
yarv c扩展甚至慢于此,因为jruby本质上必须提供一个完整的复杂仿真层,模拟yarv的内部数据结构、函数和内存布局,以便至少运行一些yarvc扩展。这太慢了。时期。
注意,这不适用于使用ruby ffi api的c库的ruby包装器。它们不依赖于yarv内部,因此不需要仿真层,jruby有一个非常快速和优化的ruby ffi api实现。不过,jvm与c桥接的成本仍然适用。
这是yarv速度更快的两件大事:运行时间太短而无法利用jvm对长时间运行的进程的优化的代码,以及大量使用c调用(尤其是yarvc扩展)的代码。
如果你能让你的代码在truffleruby上运行,那将是一个有趣的实验。truffleruby可以做的优化是非常惊人的(例如,使用大量的动态元编程、反射和Hash
查找将整个ruby库折叠成一个常量),它可以接近甚至击败手动优化的c。Truffleruby除了Ruby解释器之外还包含一个C解释器,因此可以分析和优化对C扩展的Ruby代码调用,反之亦然,甚至可以执行跨语言内联,这意味着在一些基准测试中,它可以比yarv更快地执行大量使用yarv扩展的ruby代码!