MRI 优于 JRuby 的典型工作负载示例有哪些?

What are examples of typical workloads where MRI outperforms JRuby?

我有一个 Ruby 网络服务,最近我检查了使用 JRuby(9.1.17.0,OpenJDK 1.8)是否会相对于当前使用 MRI(2.5.0)提高性能).我预计可能是这种情况,因为性能瓶颈是为计算响应数据而执行的大量 'basic arithmetic',而 JRuby 在计算量大的基准测试中往往优于 MRI。

然而,事实并非如此:我尝试了很多 JRuby/JVM 选项的组合,但是 'steady state' 比 MRI 慢 2 倍。在重复请求约 100 次后达到稳定状态,此时 JVM 显然正在发挥其 JIT 魔力,因为相对于初始请求,性能提高了 2.5 倍。

我想了解这是预期的还是意外的行为。所以我想知道:预计 JRuby 比 MRI 慢的典型工作负载是什么? 'basic arithmetic on floats' 确实在其中吗?

(性能瓶颈在 MRI 和 JRuby 的同一个地方,使用适当的分析器确定。最初这个 post 说 JRuby 只慢了 20%,但是从那以后,我引入了一项优化,将 MRI 性能提高了近 2 倍,但几乎没有改变 JRuby 性能。我怀疑 JVM 自动执行了相同的优化,因为它基本上等于 'constant folding')

如果您在 Integers 上进行计算,并且 Integers 适合 native_word_size - 1 位,那么 YARV 将在 Fixnum 上使用本机算法。如果您在 Floats 上进行计算,在 64 位平台上,并且您的计算适合 62 位,YARV 将在 flonums 上使用本机 FPU 算法。在任何一种情况下,它都不会比这快得多,除非你的操作是如此微不足道以至于 JVM JIT(或 JRuby 编译器)可以完全优化它们,不断折叠它们,或类似的东西。

最佳点是大于 63 位但小于 64 位的 Integers,JRuby 而不是 YARV 将其视为本地机器整数,Floats 大于 62 但小于 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 extensions" 始终需要通过安全屏障。它们只能破坏调用它们的 Java 代码显式传递给它们的内存,它们永远不会破坏其他内存,更不用说 JVM 本身了。但是,这是以性能成本为代价的:从 Java 调用 C 或从后者调用 C 通常 比从 YARV 调用 C 慢,反之亦然。

YARV C 扩展甚至比它慢,因为 JRuby 本质上必须提供一个完整的复杂仿真层,仿真内部数据结构、函数和YARV 的内存布局,以便至少获得 运行 的一些 YARV C 扩展。这太慢了。期间.

请注意,这不适用于使用 Ruby FFI API 的 C 库的 Ruby 包装器。这些不依赖于 YARV 内部结构,因此不需要仿真层,并且 JRuby 有一个非常快速和优化的 Ruby FFI API 实现。不过,JVM ↔ C 桥接的成本仍然适用。

这是 YARV 更快的两大方面:代码 运行 太短而无法利用 JVM 对 long-运行ning 进程的优化,以及大量使用的代码来往于 C 的调用,尤其是 YARV C 扩展。

如果您可以在 TruffleRuby 上将您的代码发送到 运行,那将是一个有趣的实验。 TruffleRuby 可以做的优化确实令人惊叹(例如,使用大量动态元编程、反射和 Hash 查找将整个 Ruby 库折叠成一个常量)并且它可以接近和甚至打败了手动优化的 C。此外,TruffleRuby 除了 Ruby 解释器之外还包含一个 C 解释器,因此可以分析和优化 Ruby 调用 C 扩展的代码,反之亦然,甚至执行跨语言内联,这意味着在某些基准测试中,它可以比 YARV 更快地执行 Ruby 大量使用 YARV 扩展的代码!