如果您在Integer
s 上进行计算,并且Integer
s 适合native_word_size - 1位,那么 YARV 将在Fixnum
s 上使用本机机器算法。如果您在Float
s 上进行计算,在 64 位平台上,并且您的计算适合 62 位,YARV 将在flonums上使用本机 FPU 算法。在任何一种情况下,它都不会比这更快,除非您的操作非常简单,以至于 JVM JIT(或 JRuby 编译器)可以完全优化它们,不断折叠它们或类似的东西。
最佳点是Integer
大于 63 位但小于 64 位的 s,JRuby 将其视为本机机器整数,但 YARV 将其视为本机机器整数,Float
大于 62 但小于 64 位的 s 也是如此。在此范围内,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 调用(尤其是YARV C 扩展)的代码。
如果您可以让您的代码在 TruffleRuby 上运行,那将是一个有趣的实验。TruffleRuby 可以做的优化确实令人惊叹(例如,使用大量动态元编程、反射和Hash
查找将整个 Ruby 库折叠到一个常量中),它可以接近甚至击败手动优化的 C。此外,TruffleRuby 包含一个 C 解释器除了 Ruby 解释器,因此可以分析和优化 Ruby 代码调用 C 扩展,反之亦然,甚至执行跨语言内联,这意味着在某些基准测试中,它可以更快地执行大量使用 YARV 扩展的 Ruby 代码比 YARV!