使用Rcpp :: List输入的我的C++函数非常慢

Sam*_*Sam 7 r rcpp

虽然C++,特别是Rcpp包对我加速代码非常有帮助,但我注意到我的C++函数有一个列表或数据框输入参数(Rcpp :: DataFrame和Rcpp :: List形式的参数)与我的其他C++函数相比,速度非常慢.我写了一个示例代码,我想问一些能让我的代码更快的技巧:

首先,让我们模拟R中包含两个列表的List.将myList视为包含两个列表的列表 - measure1和measure2.measure1和measure2是列表本身,每个列表包括对象的测量矢量.这是R代码:

lappend <- function(lst, ...){
  lst <- c(lst, list(...))
return(lst)
}

nSub <- 30
meas1 <- list()
meas2 <- list()
for (i in 1:nSub){
  meas1 <- lappend(meas1, rnorm(10))
  meas2 <- lappend(meas2, rnorm(10))
}
myList <- list(meas1 = meas1, meas2 = meas2)
Run Code Online (Sandbox Code Playgroud)

现在,假设我想要一个C++函数,对于每个主题,找到measure1的总和和度量2的总和,然后基于这两个求和创建两个新的度量.最后,该函数应将这些新测量值作为列表返回.

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

// [[Rcpp::export]]
Rcpp::List mySlowListFn(Rcpp::List myList, int nSub){
   arma::vec myMult(nSub);
   arma::vec myDiv(nSub);
   for (int i = 0; i < nSub; i++){
     arma::vec meas1_i = Rcpp::as<arma::vec>(Rcpp::as<Rcpp::List>(myList["meas1"])[i]);
     arma::vec meas2_i = Rcpp::as<arma::vec>(Rcpp::as<Rcpp::List>(myList["meas2"])[i]);
     myMult[i] = arma::sum(meas1_i)*arma::sum(meas2_i);
     myDiv[i] = arma::sum(meas1_i)/arma::sum(meas2_i);
   }
   return Rcpp::List::create(Rcpp::Named("myMult") = myMult, 
                             Rcpp::Named("myDiv") = myDiv);
}
Run Code Online (Sandbox Code Playgroud)

如何使上述功能更快?我特别想找到将输入和输出列表保存在代码中的想法(因为在我自己的程序中处理列表是不可避免的),但有一些技巧可以减少一些开销时间.我想到的一件事是:

 Rcpp::List mySlowListFn(const Rcpp::List& myList, int nSub)
Run Code Online (Sandbox Code Playgroud)

非常感谢您的帮助.

Kev*_*hey 5

首先,请注意,在最近的R版本中,列表的复制语义已经发生了变化(肯定是在最新的R-devel中,不确定它是否进入了R 3.1.0),从而制作了列表的浅表副本,并且后来的元素被复制了如果他们被修改.很有可能如果你运行的是旧版本的R,那么它更昂贵的列表复制语义就会受到阻碍.

也就是说,这是我用基准测试重新编写你的函数以获得额外速度的方法.sourceCpp它可以在你自己的机器上进行比较.

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

// [[Rcpp::export]]
Rcpp::List mySlowListFn(Rcpp::List myList, int nSub){
   arma::vec myMult(nSub);
   arma::vec myDiv(nSub);
   for (int i = 0; i < nSub; i++){
     arma::vec meas1_i = Rcpp::as<arma::vec>(Rcpp::as<Rcpp::List>(myList["meas1"])[i]);
     arma::vec meas2_i = Rcpp::as<arma::vec>(Rcpp::as<Rcpp::List>(myList["meas2"])[i]);
     myMult[i] = arma::sum(meas1_i)*arma::sum(meas2_i);
     myDiv[i] = arma::sum(meas1_i)/arma::sum(meas2_i);
   }
   return Rcpp::List::create(Rcpp::Named("myMult") = myMult, 
                             Rcpp::Named("myDiv") = myDiv);
}

// [[Rcpp::export]]
Rcpp::List myFasterListFn(Rcpp::List myList, int nSub) {

  Rcpp::NumericVector myMult = Rcpp::no_init(nSub);
  Rcpp::NumericVector myDiv = Rcpp::no_init(nSub);

  Rcpp::List meas1 = myList["meas1"];
  Rcpp::List meas2 = myList["meas2"];

  for (int i = 0; i < nSub; i++) {

    arma::vec meas1_i( 
      REAL(VECTOR_ELT(meas1, i)), Rf_length(VECTOR_ELT(meas1, i)), false, true
    );

    arma::vec meas2_i(
      REAL(VECTOR_ELT(meas2, i)), Rf_length(VECTOR_ELT(meas2, i)), false, true
    );

    myMult[i] = arma::sum(meas1_i) * arma::sum(meas2_i);
    myDiv[i] = arma::sum(meas1_i) / arma::sum(meas2_i);
  }

  return Rcpp::List::create(
    Rcpp::Named("myMult") = myMult, 
    Rcpp::Named("myDiv") = myDiv
  );
}

/*** R
library(microbenchmark)
lappend <- function(lst, ...){
  lst <- c(lst, list(...))
  return(lst)
}

nSub <- 30
n <- 10
meas1 <- list()
meas2 <- list()
for (i in 1:nSub){
  meas1 <- lappend(meas1, rnorm(n))
  meas2 <- lappend(meas2, rnorm(n))
}
myList <- list(meas1 = meas1, meas2 = meas2)
x1 <- mySlowListFn(myList, nSub)
x2 <- myFasterListFn(myList, nSub)
microbenchmark(
  mySlowListFn(myList, nSub),
  myFasterListFn(myList, nSub)
)
*/
Run Code Online (Sandbox Code Playgroud)

给我

> library(microbenchmark)

> lappend <- function(lst, ...){
+   lst <- c(lst, list(...))
+   return(lst)
+ }

> nSub <- 30

> n <- 10

> meas1 <- list()

> meas2 <- list()

> for (i in 1:nSub){
+   meas1 <- lappend(meas1, rnorm(n))
+   meas2 <- lappend(meas2, rnorm(n))
+ }

> myList <- list(meas1 = meas1, meas2 = meas2)

> x1 <- mySlowListFn(myList, nSub)

> x2 <- myFasterListFn(myList, nSub)

> microbenchmark(
+   mySlowListFn(myList, nSub),
+   myFasterListFn(myList, nSub)
+ )
Unit: microseconds
                         expr    min      lq  median      uq    max neval
   mySlowListFn(myList, nSub) 14.772 15.4570 16.0715 16.7520 42.628   100
 myFasterListFn(myList, nSub)  4.502  5.0675  5.2470  5.8515 18.561   100
Run Code Online (Sandbox Code Playgroud)

未来版本的RcppRcpp11将有一个ListOf<T>类,它将使我们更容易与我们事先知道内部类型的列表进行交互,在正确的语义被解决之后.