我有一个Ruby网络服务,我最近检查过使用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自动执行相同的优化,因为它基本上等于'恒定折叠')
答案 0 :(得分:3)
如果您在Integer
上进行计算,并且Integer
适合 native_word_size - 1 位,那么YARV将在{{1}上使用本机机器算术}秒。如果您在Fixnum
上进行计算,在64位平台上进行计算,并且您的计算适合62位,则YARV将在 flonums 上使用本机FPU算法。在任何一种情况下,它都没有那么快,除非你的操作是如此微不足道,以至于JVM JIT(或JRuby编译器)可以完全优化它们,不断折叠它们,或类似的东西
最佳点是Float
s,大于63位但小于64位,被JRuby视为本机整数但不是YARV,大于62的Integer
大小相同但小于64位。在这个范围内,JRuby将使用本机操作,但YARV不会,这为JRuby提供了性能优势。
通常,YARV在延迟上表现优于JRuby,尤其是启动时间。但这很大程度上取决于所使用的JVM和环境。有些JVM设计用于非常快速启动(例如IBM J9,IMO应该是默认的桌面JVM而不是Oracle HotSpot)或Avian(实际上它不是JVM,因为它只实现了JVM和JRE的子集)规范,但仍然可以运行许多不使用任何未实现的功能的非平凡程序,JRuby就是其中之一。)此外,还有一些环境和配置,允许您保留和重用JVM和内存中的JRuby实例,消除了很多启动时间。
第二个重要的是YARV C扩展。 YARV有一个非常开放和广泛的C扩展API。从本质上讲,YARV C扩展可以访问YARV的几乎每个私有内部实现细节。 (这显然意味着它们可以破坏并崩溃YARV。)另一方面,JVM“C扩展”总是需要经历一个安全屏障。它们只能破坏调用它们的Java代码明确传递给它们的内存,它们永远不会破坏其他内存,更不用说JVM本身了。但是,这会带来性能成本:从Java调用C或反之亦然通常比从YARV调用C慢,反之亦然。
YARV C扩展甚至更慢,因为JRuby本质上必须提供整个复杂的仿真层,模拟YARV的内部数据结构,函数和内存布局,以便至少获得一些YARV C扩展运行。这很慢。周期。
请注意,这不适用于使用Ruby FFI API的C库的Ruby包装器。那些不依赖于YARV内部,因此不需要仿真层,JRuby有一个非常快速和优化的Ruby FFI API实现。但是,JVM↔C桥接的成本仍然适用。
这些是YARV速度更快的两件大事:代码运行时间过短,无法利用JVM对长时间运行进程的优化,以及大量使用C调用的代码,尤其< / em> YARV C扩展。
如果你能让你的代码在TruffleRuby上运行,那将是一个有趣的实验。 TruffleRuby可以做的优化真的很棒(例如,使用大量动态元编程,反射和Float
查找将整个Ruby库折叠成一个常量),它可以接近甚至击败手动优化的C.此外,除了Ruby解释器之外,TruffleRuby还包含一个C解释器,因此可以分析和优化调用C扩展的Ruby代码,反之亦然,甚至可以执行跨语言内联,这意味着在某些基准测试中,它可以执行繁重的Ruby代码使用YARV扩展的速度比YARV快!