在C ++中使用Rcpp的NumericMatrix循环是一个很好的理想吗?

时间:2016-04-17 16:57:14

标签: c++ r rcpp

我想使用Rcpp::NumericMatrix作为循环它的C ++函数的参数类型。我现在应该将Rcpp::NumericMatrix的全部内容复制到C风格的数组中以获得良好的性能,还是在C ++中快速使用Rcpp::NumericMatrix的[] - 操作符 - 循环许多次?有没有比使用随机访问更快的方式来循环Rcpp::NumericMatrix实例?

1 个答案:

答案 0 :(得分:2)

由于这是一个相当悠闲的日子,我无法快速找到这个基准(Rcpp Sugar第4节是TBD而第6节是TBD)......而且我的一般好奇心......我们试试吧!

首先,要访问NumericMatrix c风格的数组,我们首先需要将NumericMatrix转换为std::vector。从std::vector开始,我们可以提取由指针控制的 c风格的数组。在这种结构下,我们完全复制了数据。尽管如此,我们可以调查提出的问题,然后进行一些调查。

下面是一个快速测试功能套件,主要关注四个不同的组件:

  1. c-style 数组
  2. std::accumulate
  3. 循环std::vector
  4. 使用NumericMatrix
  5. 以元素方式访问

    现在,我在纯 c风格的函数中做了“作弊”,因为我没有尝试通过sizeof()计算来计算数组的大小。 (这可能会导致一些问题,因为指针大小已经给出了......)

    让我们看看测试功能套件。

    #include <Rcpp.h>
    
    // [[Rcpp::export]]
    double c_for_access(const Rcpp::NumericMatrix& x){
    
      // Cast to std vector
      std::vector<double> v = Rcpp::as<std::vector<double> >(x);
    
      // Convert to c-style pointer
      double* pv = &v[0];
    
      // Sum using a pointer
      double sum = 0;
      for(unsigned int i = 0; i < v.size(); i++){
        sum += *(pv+i);  
      }
    
      return sum;
    }
    
    // [[Rcpp::export]]
    double stl_for_access(const Rcpp::NumericMatrix& x){
    
      // Cast to std vector
      std::vector<double> v = Rcpp::as<std::vector<double> >(x);
    
      // Summing Operation
      double sum = 0;
      for(unsigned int i = 0; i < v.size(); i++){
        sum +=  v[i];
      }
    
      return sum;
    }
    
    // [[Rcpp::export]]
    double stl_access(const Rcpp::NumericMatrix& x){
      // Cast to STL Vector
      std::vector<double> v = Rcpp::as<std::vector<double> >(x);
    
      // Use STL to return sum
      return std::accumulate(v.begin(), v.end(), 0.0); // Important to specify 0.0 instead of 0. 
    }
    
    
    // [[Rcpp::export]]
    double matrix_access(const Rcpp::NumericMatrix& x) {
      // Define matrix information and looping variables.
      unsigned int r = x.nrow(), c = x.ncol(), i, j;
    
      // Sum elements
      double sum = 0;
      for(i = 0; i < r; i++){
        for(j = 0; j < c; j++){
          sum += x(i,j);
        }
      }
    
      return sum;
    }
    

    现在,让我们生成一些数据:

    # Set seed for reproducibility
    set.seed(1337)
    
    # Create a 100x100 matrix
    x = matrix(rnorm(10000),nrow=100,ncol=100)
    

    接下来,我们计算并检查每个对象的总和,以确保它们都相等:

    # Calculate each object
    oracle = sum(x)     # Oracle is the correct answer given by R
    
    c.out = c_for_access(x)
    
    stl.loop = stl_for_access(x)
    
    stl.lib = stl_access(x)
    
    rcpp.pure = matrix_access(x)
    
    # Check all equal
    all.equal(oracle, c.out)
    
    all.equal(oracle, stl.loop)
    
    all.equal(oracle, stl.lib)
    
    all.equal(oracle, rcpp.pure)
    

    最后,我们在每个函数上运行 microbenchmark

    # install.packages("microbenchmark")
    
    microbenchmark::microbenchmark(oracle = sum(x),
    
                   c.out = c_for_access(x),
    
                   stl.loop = stl_for_access(x),
    
                   stl.lib = stl_access(x),
    
                   rcpp.pure = matrix_access(x)
    )
    

    从微基准测试中,我们有:

    Unit: microseconds
          expr    min     lq     mean  median      uq    max neval
        oracle  8.105  8.705  9.11406  8.7060  9.0060 24.016   100
         c.out 30.319 31.220 31.75767 31.2210 31.5210 54.636   100
      stl.loop 30.320 30.921 32.56819 31.2210 31.5210 55.836   100
       stl.lib 30.319 30.920 31.64063 31.2205 31.6705 50.133   100
     rcpp.pure  9.907 10.807 10.95122 10.8070 11.1070 12.909   100
    

    因此,通过Rcpp的矩阵求和比R慢约2微秒,但它比std::vector c-style 数组设置快得多。

    Q.E.D?