2

Oracle 声称其对 R 的 graalvm 实现(称为“FastR”)比普通 R (https://www.graalvm.org/r/)快 40 倍。然而,我运行了这个超级简单(但很现实)的 4 行测试程序,GraalVM/FastR 不仅没有快 40 倍,实际上还慢了 10 倍!

x <- 1:300000/300000
mu <- exp(-400*(x-0.6)^2)+
  5*exp(-500*(x-0.75)^2)/3+2*exp(-500*(x-0.9)^2)
y <- mu+0.5*rnorm(300000)
t1 <- system.time(fit1 <- smooth.spline(x,y,spar=0.6))
t1

在 FASTR 中,t1 返回此值:

 user  system elapsed 
  0.870   0.012   0.901

在原来的正常 R 中,我得到了这个结果:

 user  system elapsed 
  0.112   0.000   0.113

如您所见,即使对于这个简单的(即 4 行代码,没有额外/特殊库导入等) ,FAST R 也非常慢。我在 Google Cloud 上的 16 核 VM 上对此进行了测试。想法?(仅供参考:我快速浏览了 smooth.spline 代码,它确实调用了 Fortran,但根据 Oracle 营销网站,GraalVM/FastR 甚至比 Fortran-R 代码还要快。)

=====================================

编辑:根据下面 Ben Bolker 和 user438383 的评论,我修改了代码以包含一个 for 循环,以便代码运行更长时间,并且我有时间监控 CPU 使用情况。修改后的代码如下:

x <- 1:300000/300000
mu <- exp(-400*(x-0.6)^2)+
  5*exp(-500*(x-0.75)^2)/3+2*exp(-500*(x-0.9)^2)
y <- mu+0.5*rnorm(300000)

forloopfunction <- function(xTrain, yTrain) {
  for (x in 1:100) {
  smooth.spline(xTrain, yTrain, spar=0.6)
  }
}

t1 <- system.time(fit1 <-forloopfunction(x,y))
t1

现在,正常的 R 为 t1 返回这个:

   user  system elapsed 
 19.665   0.008  19.667 

而 FastR 返回:

 user  system elapsed 
 76.570   0.210  77.918 

所以,现在,FastR 只慢了 4 倍,但这仍然相当慢。(我可以接受 5% 甚至 10% 的差异,但这是 400% 的差异。)此外,我检查了 cpu 的使用情况。正常 R 在整个 19 秒内仅使用 1 个内核(100%)。然而,令人惊讶的是,FastR 在大约 78 秒内使用了 100% 到 300% 的 CPU 使用率(即 1 个全核和 3 个全核)。因此,我认为可以相当合理地得出结论,至少对于这个测试(这恰好是我非常简单的场景的实际测试),FastR 至少慢 4 倍,同时消耗约 1 到 3 倍的 CPU 内核。特别是考虑到我没有导入任何 FASTR 团队可能没有时间正确分析的特殊库(即我只使用 R 附带的 vanilla R 代码),我认为有 至少在速度方面,FASTR 实现并不完全正确。(我还没有测试过准确性,但我认为这现在没有实际意义。)有没有其他人经历过类似的事情,或者是否有人知道需要对 FASTR 进行任何“神奇”配置才能获得其声称的速度(或至少类似,即 +- 5% 速度到正常 R)?(或者也许我可以解决一些已知的 FASTR 限制,即不使用普通的 fortran 二进制文件等,但使用这些特殊的等)即 +- 5% 速度到正常 R)?(或者也许我可以解决一些已知的 FASTR 限制,即不使用普通的 fortran 二进制文件等,但使用这些特殊的等)即 +- 5% 速度到正常 R)?(或者也许我可以解决一些已知的 FASTR 限制,即不使用普通的 fortran 二进制文件等,但使用这些特殊的等)

4

1 回答 1

0

TL;DR:您的示例确实不是 FastR 的最佳用例,因为它大部分时间都花在了 R 内置程序和 Fortran 代码上。不过,没有理由让它在 FastR 上变慢,我们将努力解决这个问题。FastR 可能对您的整个应用程序仍然有用,或者仅适用于在 GNU-R 上运行缓慢的某些选定算法,但非常适合 FastR(循环,“标量”代码,请参阅FastRCluster 包)。


正如其他人所提到的,当涉及到微基准测试时,需要多次重复基准测试以使系统预热。这在任何情况下都很重要,但对于依赖动态编译的系统来说更是如此,比如 FastR。

动态即时编译的工作原理是首先解释程序,同时记录执行概况,即了解程序如何执行,然后才使用这些知识编译程序以更好地优化它(*)。对于像 R 这样的动态语言,这可能是非常有益的,因为我们可以观察到类型和其他动态行为,如果不实际运行程序,即使不是不可能静态确定也很难。

现在应该清楚为什么 FastR 需要很少的迭代来显示它可以实现的最佳性能。的确,FastR 的解释模式并没有优化很多,所以前几次迭代实际上比 GNU-R 慢。这不是 FastR 所基于的技术的固有限制,而是我们将资源放在哪里的权衡。我们在 FastR 中的优先级一直是峰值性能,即在为微基准测试或运行足够长时间的应用程序进行充分预热之后。

以你的具体例子。我还可以重现该问题,并通过使用内置 CPU 采样器运行程序来分析它:

$GRAALVM_HOME/bin/Rscript --cpusampler --cpusampler.Delay=20000 --engine.TraceCompilation example.R
...
-----------------------------------------------------------------------------------------------------------
Thread[main,5,main]
 Name                    ||             Total Time    ||              Self Time    || Location             
-----------------------------------------------------------------------------------------------------------
 order                   ||             2190ms  81.4% ||             2190ms  81.4% || order.r~1-42:0-1567
 which                   ||               70ms   2.6% ||               70ms   2.6% || which.r~1-6:0-194
 ifelse                  ||              140ms   5.2% ||               70ms   2.6% || ifelse.r~1-34:0-1109
...

  • --cpusampler.Delay=20000将采样开始延迟 20 秒
  • --engine.TraceCompilation打印有关 JIT 编译的基本信息
  • 当程序完成时,它会从 CPU 采样器打印表格
  • (example.R 循环运行微基准测试)

一个观察结果是,调用 from 的 Fotran 例程smooth.spline不应归咎于此。这是有道理的,因为 FastR 运行与 GNU-R 完全相同的本机 Fortran 代码。FastR 确实必须将数据转换为本机内存,但与计算本身相比,这可能是很小的成本。此外,本机代码和 R 代码之间的转换通常在 FastR 上更昂贵,但在这里它不起作用。

所以这里的问题似乎是一个内置函数order。在 GNU-R 中,内置函数是用 C 实现的,它们基本上对输入的类型(整数/实数/...)进行了很大的切换,然后只执行高度优化的 C 代码,在纯 C 整数/双精度/ ... 大批。这已经是最有效的事情了,FastR 无法超越它,但没有理由不那么快。事实上,事实证明 FastR 中存在性能错误,并且正在修复中。感谢您引起我们的注意。

提出的其他观点:

但根据 Oracle 营销网站,GraalVM/FastR 甚至比 Fortran-R 代码还要快

YMMV。我们网站上提供的具体基准确实在 R 代码中花费了大量时间,因此 R<->native 转换的开销不会使结果产生太大偏差。最好的结果是在将 Fortran 代码翻译成 R 时,使整个事情只是一个纯 R 程序。这表明 FastR 可以在 R 中以与 Fortran 一样快或非常接近的速度运行相同的算法,也就是说,在性能方面,FastR 的主要优势。天下没有免费的午餐。预热时间和 R<->native 转换的成本是目前要付出的代价。

FastR 使用了 100% 到 300% 的 CPU 使用率

这是由于后台线程上正在进行 JIT 编译。再说一次,没有免费的午餐。

总结一下:

  • FastR 可以通过使用动态即时编译和优化 R 代码块(内联到一个编译单元中的函数或可能的多个函数)更快地运行 R 代码,使其可以接近甚至匹配等效的本机代码,即明显快于 GNU-R。这对“标量”R 代码很重要,即带有循环的代码。对于将大部分时间花在内置 R 函数中的代码,例如,sum((x - mean(x))^2)对于 large x,这不会获得太多,因为即使在 GNU-R 上,该代码也已经将大部分时间花在优化的本机代码上。
  • FastR 不能做的是在执行单个 R 内置函数时击败 GNU-R,这很可能已经是 GNU-R 中高度优化的 C 代码。对于单个内置函数,我们可能会击败 GNU-R,因为我们碰巧选择了稍微更好的算法,或者 GNU-R 在某个地方存在一些性能错误,或者在这种情况下可能是相反的情况。
  • FastR 也不能做的是加速本机代码,例如某些 R 代码可能调用的 Fortran 例程。FastR 运行相同的本机代码。最重要的是,在 FastR 中,本机代码和 R 代码之间的转换成本更高,因此过于频繁地进行这种转换的程序最终可能会在 FastR 上变慢。
  • 注意:FastR 可以做并且正在进行中的工作是运行 LLVM 位码而不是本机代码。GraalVM 支持 LLVM 位码的执行,并且可以与其他语言一起优化它,这消除了 R<->native 转换的成本,甚至为编译器提供了更多的能力来跨这个边界进行优化。
  • 注意:您可以通过集群包接口使用 FastR来仅执行部分应用程序。

(*) 也可以编译第一个分析层,这给出了不同的权衡

于 2022-01-24T22:59:00.010 回答