【问题标题】:Are "self-contained" functions more efficient in R?“自包含”函数在 R 中是否更有效?
【发布时间】:2019-01-14 12:51:05
【问题描述】:

我正在编写一个函数,它需要调用一个函数g,该函数作为参数传递给列表的每个元素,迭代。

我想知道如何尽可能快地做到这一点。我可以使用Rcpp 和特定类型的g (用Cpp 编写所有内容)达到可接受的速度,但我不知道是否可以通过R 函数作为参数达到类似的速度。

正在做一些测试以找出 R 速度较慢的原因,并发现了一些非常出乎意料的结果:

minus <- function(x) -x
minus_vec <- Vectorize(minus, "x")

使用一些简单的函数来反转符号进行测试。

f0 <- function(x) {
  sapply(x, minus)
}

f1 <- function(x) {
  for(i in seq_along(x)){
    x[i] <- -x[i]
  }
  x
}

f2 <- function(x) {
  for(i in seq_along(x)){
    x[i] <- minus(x[i])
  }
  x
}

我得到了以下结果:

a <- 1:10^5
library(rbenchmark)
benchmark(f0(a), f1(a), f2(a), minus_vec(a), minus(a))[,c(1,4)]

          test relative
1        f0(a)  454.842
2        f1(a)   25.579
3        f2(a)  178.211
4 minus_vec(a)  523.789
5     minus(a)    1.000

我想对以下几点进行一些解释:

  • 为什么f1f2 的速度不一样?写代码-x[i]和调用函数minus(x[i])做同样的事情真的应该有这么大的区别吗?

  • 为什么f0f2 慢?我一直认为apply 函数比for 循环更有效,但从来没有真正理解为什么,现在我什至找到了一个反例。

  • 我可以使用函数minus 使函数像f1 一样快吗?

  • 为什么矢量化 minus(不必要,因为 - 已经矢量化,但可能并非总是如此)让它变得如此糟糕?

【问题讨论】:

  • apply 并不总是比自写循环更有效,这实际上取决于任务和循环编写的好坏。另外,您可以考虑vapply,它是apply 系列中效率最高的。
  • 对于 f1 和 f2 而-x 是“内置”,minus 是另一个函数,因此每次迭代中都会嵌套另一个函数调用。
  • 不,你可能无法让你的编辑功能和“内置”一样快,但你可以通过使用 JIT 字节码 compiler 包 - homepage.divms.uiowa.edu/~luke/R/compiler/compiler.pdf
  • 如果你试图将一些非常基本的东西(如减号)加速到更复杂的东西上,你会误入歧途。你的函数实际上是做什么的?

标签: r performance function


【解决方案1】:

不是一个完整的答案,但这里有一些注意事项

1 minus(x) vs -x什么都不做比做某事好

您的函数minus 调用`-`,因此添加的步骤会增加计算时间。老实说,我不知道具体是谁、什么以及什么时候,换句话说,我不知道还需要多少计算时间。

这是一个突出显示它的示例:我们有四个函数,都是平方数

fa <- function (n) n^2
fb <- function (n) fa(n)
fc <- function (n) fb(n)
fd <- function (n) fc(n)
Fa <- function (n) {
  for (i in seq_along(n)) n[i] <- fa(i)
  n
}
Fb <- function (n) {
  for (i in seq_along(n)) n[i] <- fb(i)
  n
}
Fc <- function (n) {
  for (i in seq_along(n)) n[i] <- fc(i)
  n
}
Fd <- function (n) {
  for (i in seq_along(n)) n[i] <- fd(i)
  n
}

这是基准测试结果

n <- 1:10^4
b <- benchmark(Fa(n),Fb(n),Fc(n),Fd(n), replications = 1000L)
b
#    test replications elapsed relative user.self sys.self user.child sys.child
# 1 Fa(n)         1000    3.93    1.000      3.85     0.00         NA        NA
# 2 Fb(n)         1000    7.08    1.802      6.94     0.02         NA        NA
# 3 Fc(n)         1000   10.16    2.585      9.94     0.06         NA        NA
# 4 Fd(n)         1000   13.68    3.481     13.56     0.00         NA        NA
# looks rather even
diff(b$elapsed)
# [1] 3.15 3.08 3.52

现在回到你的minusfunction

a <- 1:10^5
b <- benchmark(f0(a), f1(a), f2(a), minus_vec(a), minus(a))          
b$elapsed[b$test == 'f2(a)'] - b$elapsed[b$test == 'f1(a)']    
# [1] 3.39   

2 apply vs for vs Vectorize

@NavyCheng 提供了一些关于该主题的好材料。现在我的理解是,apply 系列(就像Vectorize)在R 中循环(如果我没有误认为`-` 的循环是在C 中完成的)。

同样,我不知道确切的细节,但如果 apply/Vectorize 使用 R 循环,那么理论上(在实践中经常),可以编写一个适当的for 循环将执行得一样好或更好。


3 一个函数快 f1:

特设,我想出的关闭是通过使用Rcpp作弊。 (作弊因为一个人先在c++写函数)

C++

#include <RcppArmadillo.h>
//[[Rcpp::depends(RcppArmadillo)]]
using namespace Rcpp;

// [[Rcpp::export]]
NumericVector minusCpp(NumericVector x) {
  for (int k = 0; k < x.length(); ++k) {
    x[k] = -x[k];
  }
  return x;
}

现在看看R中的基准

a <- 1:10^5
b <- benchmark(f0(a), f1(a), f2(a), minus_vec(a), minus(a), minusCpp(a))          
b
#           test replications elapsed relative user.self sys.self user.child sys.child
# 1        f0(a)          100    9.47       NA      9.22     0.01         NA        NA
# 2        f1(a)          100    0.53       NA      0.54     0.00         NA        NA
# 3        f2(a)          100    4.23       NA      4.24     0.00         NA        NA
# 5     minus(a)          100    0.00       NA      0.00     0.00         NA        NA
# 4 minus_vec(a)          100   10.42       NA     10.39     0.02         NA        NA
# 6  minusCpp(a)          100    0.05       NA      0.04     0.00         NA        NA

【讨论】:

  • 我认为那是没有办法的。听起来在 R 中循环并多次调用 Rcpp 函数很慢,在 Rcpp 中循环并从 Rcpp 中多次调用 R 函数也不起作用。要么全是 Rcpp,要么很慢。
  • @Freguglia 在Rcpp 中调用R 函数是可能的,请参见此处gallery.rcpp.org/articles/r-function-from-c++
  • 是的,但正如我提到的以及该链接中的警告状态,从Rcpp 调用R 函数效率不高。 “调用函数既简单又诱人。它也很慢,因为涉及开销。从 C++ 代码中重复调用它,可能隐藏在几个循环中,是完全愚蠢的。”
  • @Freguglia 是的,这是真的。我从字面上解释了你的“不起作用”。
  • R 中的 @nate forloopsample 情况下的 apply family 快,我发现 forloop 性能不好是因为data is not modified in place。我希望这将有助于完成这篇文章。
【解决方案2】:

忽略-x[i]minus(-x[i]),我把四个问题总结为两个:

  • 为什么 apply 系列比 forloop 慢?
  • 为什么 Vectorizeapply 系列慢?

第一个问题:

apply 功能旨在方便和清晰阅读, 不一定很快。

apply家族会比forloop做更多的事情,

此外,sapply 函数首先使用 as.vector(unlist(...)) 将任何内容转换为向量,最后尝试将答案简化为合适的形式。

您无法阅读herehere 了解更多详情。

对于第二个问题,这是因为 Vectorizemapply 的包装器,如果您在 Rstudio 中键入 Vectorize,您将看到详细代码。您可以阅读this 以获得更多帮助。

【讨论】:

  • 我认为如果您能总结在这些链接中找到的论点,这将是一个更好的答案。据我所知Vectorizemapply 的包装。
猜你喜欢
  • 2022-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-28
相关资源
最近更新 更多