七爪源码:使用 FastR 实现更快的 R

R 是一种用于统计计算的编程语言,以其丰富的第三方库生态系统(称为“包”)而闻名。 R 语言通常用于统计、数据挖掘、分析和可视化、机器学习和类似任务。 然而,R 至少在本质上仍然是一种通用语言,例如,可以通过 Shiny 框架用于响应式 Web 应用程序开发。

FastR 是基于 GraalVM 的 R 语言实现,它在 R 代码的性能、将 R 执行引擎嵌入 Java 应用程序以及与其他 GraalVM 和 JVM 语言(包括 Python、Java 和 Scala)交互的能力方面提供了显着改进 . FastR 还能够利用 GraalVM 生态系统提供的工具支持。

要试用 FastR,您可以下载 GraalVM 并使用以下命令安装 FastR:

$GRAALVM_HOME/bin/gu install R

您可以通过运行以下命令启动 R 交互式控制台:

$GRAALVM_HOME/bin/R


与 GNU-R 的兼容性

FastR 的目标是成为 GNU-R 的直接替代品,GNU-R 是 R 语言的参考实现。 到目前为止,FastR 能够运行为 GNU-R 构建的二进制 R 包,只要这些包正确使用 R 扩展 C API。 但是,为了获得最佳效果,建议从源代码安装 R 包。 FastR 通过 grid 包和基于 grid 的包(lattice 和 ggplot2)支持 R 图形。 我们目前正在努力支持基本图形包。

FastR 目前适用于许多流行的 R 包,例如

  • ggplot2
  • jsonlite
  • testthat
  • assertthat
  • knitr
  • Shiny
  • Rcpp
  • rJava
  • quantmod
  • 和更多…

此外,对 dplyr 和 data.table 的支持正在进行中。我们正在积极监控和改进对 CRAN 上发布的最流行包的 FastR 支持,包括所有 tidyverse 包。但是,应该考虑到 FastR 的实验状态,可能存在尚不兼容的包,如果您在复杂的 R 应用程序上尝试它,它可能会偶然发现这些包。

注意:对于一个包是否与 FastR 一起“工作”的最直接的评估似乎是使用 FastR 运行它自己的测试。然而,一些包只有微不足道的测试,而其他包可能有大量的测试,具有防弹代码覆盖,这使得很难将它们全部传递到最后一个奇异的极端情况。本文中列出的包要么通过了所有测试,要么通过了我们根据常见用例创建的测试。


高性能 R 代码执行:我们还需要 Fortran 吗?

R 的性能一直是 R 社区的症结所在,许多 R 开发人员的祸根是当他们被迫用 C、C++ 或 Fortran 重新实现他们的 R 代码时。

“这种方法有效,但对于除小型数据集外的所有数据集,它都非常缓慢。”

在“向制图师光明会投掷阴影:用 R 对华盛顿纪念碑进行光线追踪”一文中,作者 Tyler Morgan-Wall 展示了光线追踪算法的 R 代码片段,并得出结论:“这种方法有效,但对所有人来说都慢得无法使用但小数据集”。如果我们用 FastR 对这个例子进行基准测试呢?下图显示了经过 5 次预热后 FastR 相对于 GNU-R 的加速,即越高越好。

FastR 大约快 3 倍。尽管这看起来可能是一个很好的结果,但与 GNU-R 相比,支持 FastR 高性能执行的 Graal 编译器通常做得更好。我们可以使用内置的 CPU 采样器来找出瓶颈在以下函数调用中:

akima::bilinear(volc$x,volc$y,volc$z,x0=xcoord,y0=ycoord)$z

经过仔细检查,我们确定这是执行实际计算的 Fortran 函数的简单包装器。传统观点表明,我们现在只能通过在更好的 R 引擎上执行 Fortran 函数来改进对 Fortran 函数的调用,但对于 FastR 来说是否如此?

GraalVM 上构建的每种语言的原则之一是,我们希望允许开发人员使用最适合任务的语言专注于他们的问题域,无论语言的抽象级别如何。编译器的工作,而不是程序员的工作,是负责删除抽象并尽可能接近等效的手动优化的 C 代码运行代码。那么是否可以在 R 中重写该 Fortran 算法并获得相同的性能?如果我们这样做,我们最终得到的结果更符合我们对 FastR 的预期,这是因为调用本机代码创建的优化编译器的边界不再存在。同样,下图显示了经过 5 次预热迭代后,FastR 相对于 GNU-R 的加速,即越高越好。

我们很高兴地介绍一个世界,R 代码在其自己的应用程序中不再被视为二等公民。 R 代码与等效的 C、C++ 或 Fortran 代码一样快(甚至更快)的世界。

FastR 的长期目标之一是通过 GraalVM LLVM 支持执行本机代码。这不仅会消除 R 和 Fortran 代码之间的优化障碍,而且还可以提供额外的安全保证以及对 R 包进行完全沙盒执行的能力。

我们是否提到本文中的所有图都是使用 ggplot2 从 FastR 生成的?在 Medium 上嵌入 SVG 图像没有简单的方法,但 FastR 还包含一个内置的 SVG 设备,这与 GNU-R 的内置 SVG 设备不同,因为它更接近 svglite,因为它可以生成更小、更多的 web - 友好的 SVG。

多语言编程:想要 Python 中的 R 包?

作为 GraalVM 生态系统的公民,会带来许多有用的功能,例如与其他基于 GraalVM 的语言进行互操作的能力。在 R 的上下文中自然会想到的一种语言是 Python。关于这两种语言中哪一种是更好的数据科学语言、哪一种具有更好的调试器、哪一种具有更好的库等等的讨论越来越多。在 GraalVM 的上下文中,所有这些讨论都将成为一个有争议的问题,因为我们可以简单地使用 R 中的 Python 库,反之亦然,或者双向使用,也许只是为了好玩而添加一些 JavaScript!让我们看一个示例,我们将在 GraalVM 的 Python 3 实现中使用 R 包。

以下用 Python 编写的算法是一种从指数分布中生成随机数的简单方法。

from random import random
from math import log
rate = 3
result = []
for i in range(1000):
    result += [-1/rate * log(random())]

现在,如果我们想测试我们生成的样本来自这个分布,我们可以使用 R 基础库中的 Kolmogorov-Smirnov 测试。 以下代码段是 Python 代码,它使用内置的互操作性功能调用 R 包内的函数:

import polyglot
# Create an R function and save it to python variable
rcode = "function(x, l) ks.test(as.vector(x), 'pexp', l)$p.value"
kstest = polyglot.eval(string=rcode, language="R")
# invoke the R function with arguments that are Python objects
pvalue = kstest(result, rate)
print("The p-value of the test is ", pvalue)

最后,让我们用 R 的 lattice 包来可视化数据:

# load the R package lattice and open a window for plotting
polyglot.eval(string="library(lattice); awt()", language="R")
# Create R function that draws a histogram
plot = polyglot.eval(string="function(y) {print(histogram(~as.vector(y), main='Hello from Python to R', xlab='Python List')) }", language="R")
# invoke the R function from Python
plot(result)

注意:awt 函数会打开一个带有绘图的窗口,而不是绘图到图像文件中。使用以下代码结构获取绘图的 SVG 代码或将其保存为 PNG 图像。

svg()
print(histogram(...))
svgCode <- svg.string()

png('/path/to/image.png')
print(histogram(...))
dev.off()

如果您想自己运行 Python 示例,请确保为 GraalVM 安装 Python 支持,并且不要忘记为 graalpython 命令启用多语言,如下所示:

$GRAALVM_HOME/bin/gu install python
$GRAALVM_HOME/bin/graalpython --jvm --polyglot ...

此时,一个经常被问到的问题是:做如此复杂的语言互操作性的性能开销是多少?还记得我们在上一节中如何将 Fortran 函数重写为 R 函数吗?这消除了 GraalVM 和本机代码之间的边界,但是如果我们留在 GraalVM 中,我们可以用任何语言实现该功能,而无需为优化编译器引入任何边界。因此,例如,我们可以将函数重写为 Python。下图显示,重写为 Python 确实会产生少量开销,但我们正在努力消除这种开销,理论上,可以完全消除它。尽管如此,R+Python 版本仍然比 GNU-R 上的纯 R 版本快一个数量级!一旦 FastR 开始使用 GraalVM LLVM 支持来运行 R 包的本机代码,我们甚至会移除 Fortran 和 C 代码的边界。

为了完成对该基准的分析,这里是一个包含所有变体的预热曲线和绝对时间的图,即越低越好。

无缝和高效的互操作性开辟了许多有趣的可能性。 Galaaz 是一种编程语言,或者您可以认为是嵌入式 DSL,它允许您使用 Ruby 语法访问 R 生态系统的丰富性,包括 R 图形。在底层,Galaaz 使用了 TruffleRuby 的互操作特性。例如,使用 Galaaz,您可以使用熟悉的类 Ruby 语法在 Ruby 中创建 ggplot2 可视化。

将 R 嵌入到您的 JVM 应用程序中

GraalVM 生态系统 R 的另一个用例是将 R 无缝嵌入到 Java 应用程序中。 FastR 或任何基于 GraalVM 的语言都可以使用 Graal SDK 嵌入。从 Java 调用 R 函数就像这样简单:

Context ctx = Context.newBuilder("R").allowAllAccess(true).build();
ctx.eval("R", "sum").execute(new int[] {1,2,3});

如何将更复杂的数据从 Java 传递到 R?想象一个现有的 Java 应用程序,其中包含一个保存用户数据的类 User。我们有一个这样的对象的集合,我们想要运行一个 R 脚本来对这些数据进行一些分析。使用 FastR,我们唯一需要做的就是实现一些 Java 代理类来转换数据,使其具有 R 数据框的预期形状,这是一种“按列”数据结构,类似于关系数据库表。然后我们可以将 Java 对象直接传递给 R 脚本。这个过程消除了在两个截然不同的平台之间复制数据的需要。 Java 类可以按如下方式实现:

public static final class User {
    public final int id;
    public final String name;
    // ...usual constructor
}
public static class NameColumn implements ProxyArray {
    private final User[] users;
    public NameColumn(User[] users) {
        this.users = users;
    }
    public Object get(long index) {
        return users[(int) index].name;
    }
    // ...
}
public static class IdColumn implements ProxyArray {
    // similar to NameColumn...
}
public static class UsersTable {
    public final IdColumn id;
    public final NameColumn name;
    // ...usual constructor
}

在下面的代码中,我们执行一个 R 脚本,从 R 数据帧中过滤掉 id 大于 2 的用户。 数据框实际上是由上面代码片段中的 Java 类支持的,并且在 Java 和 FastR 之间没有不必要的复制或编组。

Context ctx = Context.newBuilder("R").allowAllAccess(true).build();
Value rFunction = context.eval("R",
        "function(table) { " +
        "  table <- as.data.frame(table);" +
        "  cat('The whole data frame printed in R:
');" +
        "  print(table);" +
        "  cat('---------

');" +
        "  cat('Filter out users with ID>2:
');" +
        "  print(table[table$id > 2,]);" +
        "}");
User[] data = getUsers();
rFunction.execute(new UsersTable(data));

FastR 还能够将 R 的图形输出重定向到给定的 Java Graphics2D 对象。 本文顶部的屏幕截图取自演示 K-means 算法的示例应用程序。 结果在 R 中计算和可视化,但显示在 Java Swing 桌面应用程序中。

说到 Java,从 R 与 Java 交互的传统方式是 rJava 包。 FastR 包括它自己的 rJava API 实现,它比 GNU-R 上的 rJava 快一个数量级,如果你使用 FastR 的原生 Java 互操作性,它的语法几乎与 rJava 相同,那么代码可以是 rJava 的数量级 数量级比 GNU-R 和 rJava 快。

public class RJavaBench {
  public int intField;
  public double doubleField;
  
  public int intFunction(int a, int b) { return a - b; }
  
  public RJavaBench objectFunction(RJavaBench a) {
    RJavaBench result = new RJavaBench();
    // ...
    return result;
  }
}

# the R code:
benchmark <- function(obj) {
    result <- 0L
    for (j in 1:100) {
       obj2 <- obj$objectFunction(obj)
       obj$intField <- as.integer(obj2$doubleField)
       for (i in 1:250) {
           result <- obj$intFunction(i, obj$intField)
       }
    }
    result
}
benchmark(.jnew("RJavaBench"))

下图显示了 rJava 在 GNU-R、FastR 和 FastR 中的原生 Java 互操作性的预热曲线,即越低越好。


结论

在本文中,我们介绍了 FastR,这是一种 R 编程语言的实验性实现,旨在与 GNU-R 完全兼容,与 R 的参考实现相比,它可以提供显着的性能改进,并且是 GraalVM 生态系统的成员。我们很高兴为 R 社区带来许多有趣的新功能,包括但不限于与其他语言的互操作性、开发工具和 Java 嵌入。

关注七爪网,获取更多APP/小程序/网站源码资源!

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章