将R函数作为参数传递给RCpp函数

5
我正在尝试运行类似于

R

的东西。
my_r_function <- function(input_a) {return(input_a**3)}
RunFunction(c(1,2,3), my_r_function)

C++

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
NumericVector RunFunction(NumericVector a, Function func)
{
  NumericVector b = NumericVector(a.size());
  for(int i=0; i<a.size(); i++)
    b[i] = func(a[i]);
  return b;
}

我该如何在Rcpp中让"Function func"函数正常工作?
注:我知道有一些方法可以在不使用Rcpp的情况下实现(例如,对于这个例子,可以使用apply),但我只是举个例子来说明我的需求。

2
像这样吗?http://gallery.rcpp.org/articles/r-function-from-c++/ - nrussell
1
额外加分链接到Rcpp Gallery文章。 - Dirk Eddelbuettel
2个回答

9
你应该能够使用我上面提供的链接中的示例使你的代码正常工作;但你也应该注意Dirk的警告,
调用函数虽然简单而且诱人,但它也会因为涉及开销而变慢。在你的C++代码内重复调用该函数,尤其是可能被埋在多个循环内部时,就显得非常愚蠢。
可以通过稍微修改你上面的代码并对两个版本进行基准测试来证明这一点:
#include <Rcpp.h>

// [[Rcpp::export]]
Rcpp::NumericVector RunFunction(Rcpp::NumericVector a, Rcpp::Function func)
{
  Rcpp::NumericVector b = func(a);
  return b;
}

// [[Rcpp::export]]
Rcpp::NumericVector RunFunction2(Rcpp::NumericVector a, Rcpp::Function func)
{
  Rcpp::NumericVector b(a.size());
  for(int i = 0; i < a.size(); i++){
    b[i] = Rcpp::as<double>(func(a[i]));
  }
  return b;
}

/*** R
my_r_function <- function(input_a) {return(input_a**3)}
x <- 1:10
##
RunFunction(x,my_r_function)

RunFunction2(x,my_r_function)
##
library(microbenchmark)
microbenchmark(
  RunFunction(rep(1:10,10),my_r_function),
  RunFunction2(rep(1:10,10),my_r_function))

Unit: microseconds
                                       expr     min       lq       mean   median       uq      max neval
  RunFunction(rep(1:10, 10), my_r_function)  21.390  22.9985   25.74988  24.0840   26.464   43.722   100
 RunFunction2(rep(1:10, 10), my_r_function) 843.864 903.0025 1048.13175 951.2405 1057.899 2387.550   100

*/

请注意,RunFunctionRunFunction2快约40倍:在前者中,我们只需要在C++代码中调用一次func的开销,而在后者中,我们必须为输入向量的每个元素进行一次交换。如果你尝试在更长的向量上运行此代码,我相信你会看到RunFunction2相对于RunFunction有明显更差的性能表现。因此,如果你要在C++代码内部调用R函数,应尽可能利用R的本机矢量化(如果可能的话),而不是在循环中重复调用R函数,至少对于像x**3这样相对简单的计算。
此外,如果你想知道为什么你的代码无法编译,那是因为这一行的问题:
b[i] = func(a[i]);

您可能会遇到以下错误:

无法将“SEXP”转换为“Rcpp :: traits :: storage_type <14> :: type {aka double}”,无法完成赋值

我通过在上面使用Rcpp :: as <double>()包装func(a[i])的返回值来解决这个问题。然而,这显然不值得麻烦,因为您最终会得到一个更慢的函数。

2
非常好的答案。我正在使用它来处理性能敏感的代码,所以我采纳了您的建议,从Rcpp代码中删除了调用,并选择了矢量化解决方案。感谢您提供的所有有用提示。 - Coat

0
你可以使用 'transform()' 函数来避免使用循环!尝试以下代码:
List RunFunction(List input, Function f) {

    List output(input.size());

    std::transform(input.begin(), input.end(), output.begin(), f);
    output.names() = input.names();
}

1
你说得对,std::transform是一个可行的替代for循环的方法,在典型的C++代码中通常比循环和迭代器更可取。然而,这种方法仍然会产生与在C++中重复调用f导致的性能开销相同的开销。如果你运行一些基准测试,比较for循环方法和std::transform方法,你会发现它们的时间几乎相等。另请注意,OP的函数接受向量作为输入,而不是列表;虽然transform在列表上也可以正常工作。 - nrussell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接