如何有效地反转整数向量?

时间:2014-04-15 01:42:35

标签: r reverse rcpp

更新

BUG rev2中存在错误,现在已修复。

rev1Rcpprev1已删除。

我还包含了不同的功能,并根据评论和答案进行了修改。

结论:rev4最适合我的目的,RcppRev3对于长矢量更好。


我有一个程序需要多次反转整数向量(固定长度)。我分析了代码,rev函数占用了总时间的30%左右。我对R中函数rev的缓慢性能感到非常惊讶。所以我决定将基数R rev与其他一些选择进行比较

library(Rcpp)
library(microbenchmark)
rev2 = function(x){
    nx = length(x)
    y = vector("numeric", nx)
    for(i in 1:nx) y[i] = x[nx-i+1]
    y
}
rev3 = function(x){
    x[length(x):1]
}
rev4 = function(x){
    .subset(x, length(x):1)
}

Rcpp等价:

sourceCpp(code='
#include <Rcpp.h>

using namespace Rcpp;


// [[Rcpp::export]]
IntegerVector Rcpprev2(IntegerVector x){
    int nx = x.size();
    IntegerVector y(nx);
    for(int i = 0; i<nx; i++){
        y[i] = x[nx-i-1];
    }
    return(y);
}

// [[Rcpp::export]]
IntegerVector RcppRev3(IntegerVector x) {
  return rev(x);
}

// [[Rcpp::export]]
std::vector<int> CppRev(std::vector<int> & x) {
  std::reverse(x.begin(), x.end());
  return x;
}

')

微基准:

> x = c(1L,2L,3L,4L)
> microbenchmark(rev(x), rev2(x), rev3(x), rev4(x), Rcpprev2(x),  RcppRev3(x), CppRev(x))
Unit: nanoseconds
        expr  min     lq median     uq   max neval
      rev(x) 4614 5501.0 6086.5 8208.0 32860   100
     rev2(x) 5765 6965.0 7803.0 8784.5 28637   100
     rev3(x) 1013 1351.0 1521.5 1985.5  9977   100
     rev4(x)  961 1317.0 1485.5 1728.5 16492   100
 Rcpprev2(x) 2008 2360.0 2558.5 3075.5 17342   100
 RcppRev3(x) 2045 2377.5 2607.5 3718.0  9184   100
   CppRev(x) 2279 2668.0 2947.0 3381.0 35614   100

事实证明,最快的方法是纯R方法rev4,它甚至比Rcpp函数更快。

让我们考虑更长的向量。

> x = rep(c(1L,2L,3L,4L), 1000)
> microbenchmark(rev(x), rev2(x), rev3(x), rev4(x), Rcpprev2(x),  RcppRev3(x), CppRev(x))
Unit: microseconds
        expr      min        lq    median        uq      max neval
      rev(x)   26.887   29.9425   39.2165   50.4080   71.614   100
     rev2(x) 3899.577 4195.3530 4370.5410 4743.3585 6117.387   100
     rev3(x)   22.092   24.4405   25.8340   27.8230   51.602   100
     rev4(x)   22.346   24.3150   25.8800   28.9425   90.666   100
 Rcpprev2(x)    6.039    7.6405    8.4530   11.6720   31.054   100
 RcppRev3(x)    5.384    6.1425    6.7395    8.3295   47.981   100
   CppRev(x)   10.462   12.0375   13.4130   17.6725   58.012   100

对于长向量,Rcpp函数要好得多。

3 个答案:

答案 0 :(得分:3)

你正在混合用于在副本中分配新记忆的方法,以及那些不分配新记忆的方法。

我会尝试从对象指针实例化std::vector<int>,并调用C ++向量的相应反向方法。当然还有Rcpp糖方法......

另外两个补充:

R> microbenchmark(rev(x), rev1(x), rev2(x), rev3(x), Rcpprev1(x), Rcpprev2(x), 
+                 RcppRev3(x), CppRev(x))
Unit: microseconds
        expr   min     lq median      uq      max neval
      rev(x) 7.676 9.1070 9.9870 11.1445   71.559   100
     rev1(x) 1.446 1.9565 2.1580  2.4280   23.310   100
     rev2(x) 4.333 5.2875 5.9305  6.4335   17.458   100
     rev3(x) 1.229 1.6875 1.9310  2.1115   12.841   100
 Rcpprev1(x) 1.949 2.5440 2.8875  3.3655 7313.028   100
 Rcpprev2(x) 1.686 2.0840 2.6260  3.1960 2632.929   100
 RcppRev3(x) 1.544 2.0770 2.6130  2.9330   15.722   100
   CppRev(x) 1.986 2.6100 3.0530  3.6035   14.091   100
R> 

但我怀疑n=4你在这里几乎无法衡量。

编辑:这是一个更新版本,反映了Kevin关于()[]的优点,并添加了Randy的新{{1} - 并在有意义的向量上运行它。 编辑2:还有一个rev4,我需要()[]接近Rcpp糖的表现,但并不完全存在。

[]

糖版本对我来说仍然很好。

为了完整起见,我的[更新,两次]文件位于下方。

R> microbenchmark(rev(x), rev2(x), rev3(x), rev4(x), Rcpprev2r(x),
+                 Rcpprev2s(x), RcppRev3(x), CppRev(x))
 Unit: microseconds
          expr      min       lq   median       uq      max neval
        rev(x) 3505.205 3603.892 4307.269 4581.047 45761.91   100
       rev2(x)  273.258  294.350  341.571 1258.093 42992.72   100
       rev3(x) 3489.653 3573.588 4462.155 4545.285 46253.93   100
       rev4(x) 3481.903 3575.505 4409.817 4567.506 48873.70   100
  Rcpprev2r(x) 3433.918 3482.274 3507.504 3554.779  4519.62   100
  Rcpprev2s(x)  364.481  379.853  403.149  484.366  1684.71   100
   RcppRev3(x)  269.145  283.750  290.870  346.462 42867.46   100
     CppRev(x)  907.719  963.175  991.787 1126.783  2313.97   100
 R> 

R部分如下:

#include <Rcpp.h>

using namespace Rcpp;

// [[Rcpp::export]]
IntegerVector Rcpprev2r(IntegerVector x){
    int nx = x.size();
    IntegerVector y(nx);
    for(int i = 0; i<nx; i++){
        y(i) = x(nx-i-1);
    }
    return(y);
}

// [[Rcpp::export]]
IntegerVector Rcpprev2s(IntegerVector x){
    int nx = x.size();
    IntegerVector y(nx);
    for(int i = 0; i<nx; i++){
        y[i] = x[nx-i-1];
    }
    return(y);
}

// [[Rcpp::export]]
IntegerVector RcppRev3(IntegerVector x) { 
  return rev(x);
} 

// [[Rcpp::export]]
std::vector<int> CppRev(std::vector<int> & x) { 
  std::reverse(x.begin(), x.end());
  return x;
} 

答案 1 :(得分:3)

仅供参考:在索引operator()向量时,使用operator[]代替Rcpp需要付费。这是因为operator()使用offset函数进行边界检查,与operator[]不同。参见:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
IntegerVector RcppRev(IntegerVector x) {
  int n = x.size();
  IntegerVector output = no_init(n);
  for (int i=0; i < n; ++i) {
    output[i] = x[n - i - 1];
  }
  return output;
}

// [[Rcpp::export]]
IntegerVector RcppRev2(IntegerVector x){
    int nx = x.size();
    IntegerVector y(nx);
    for(int i = 0; i<nx; i++){
        y(i) = x(nx-i-1);
    }
    return(y);
}

/*** R
library(microbenchmark)
x <- as.integer(rnorm(1E7))
identical( rev(x), RcppRev(x) )
identical( rev(x), RcppRev2(x) )
microbenchmark( times=5, 
  rev(x),
  RcppRev(x),
  RcppRev2(x)
)
*/

sourceCpp就此给了我:

> Rcpp::sourceCpp('~/Desktop/rev.cpp')

> library(microbenchmark)

> x <- as.integer(rnorm(1E7))

> identical( rev(x), RcppRev(x) )
[1] TRUE

> identical( rev(x), RcppRev2(x) )
[1] TRUE

> microbenchmark( times=5, 
+   rev(x),
+   RcppRev(x),
+   RcppRev2(x)
+ )
Unit: milliseconds
        expr       min        lq   median        uq      max neval
      rev(x) 54.987311 55.261393 55.48561 56.083582 70.88035     5
  RcppRev(x)  8.375551  8.458457  8.70446  8.800677 63.12635     5
 RcppRev2(x) 75.398164 75.733779 75.74356 76.027000 76.59727     5

答案 2 :(得分:2)

你的结论是错误的。对于大型向量rev1,R和RCpp的版本都是不切实际的。 rev2速度很快。

x = rep(c(1L, 2L, 3L, 4L), 1e+05)
microbenchmark(rev(x), rev2(x), rev3(x), Rcpprev1(x), Rcpprev2(x))
## Unit: microseconds
##         expr      min        lq    median        uq       max neval
##       rev(x) 2159.888 2202.9270 2235.3715 2515.2895 32350.315   100
##      rev2(x)  201.621  221.3185  229.9265  255.9155  1577.871   100
##      rev3(x) 2139.362 2191.5050 2212.6935 2907.7710 32202.328   100
##  Rcpprev1(x)    1.655    3.8075    9.6010   13.5740    18.871   100
##  Rcpprev2(x) 4784.596 4857.7615 4892.3580 4957.4130  5777.800   100

更新

以下是使用Dirk&n CppRevRcpprev3函数的更新基准。

RcppRev3最好。

microbenchmark(rev(x), rev2(x), rev3(x), Rcpprev2(x), RcppRev3(x), CppRev(x))
## Unit: microseconds
##         expr      min        lq   median       uq       max neval
##       rev(x) 2157.902 2227.2605 2502.874 2975.143 32623.777   100
##      rev2(x)  202.945  231.7475  250.453  829.822  1042.865   100
##      rev3(x) 2127.112 2205.9070 2321.946 2947.996 32239.076   100
##  Rcpprev2(x) 4805.453 4878.6190 4924.637 5018.495  5846.661   100
##  RcppRev3(x)  189.040  206.0900  222.478  797.874  1135.232   100
##    CppRev(x) 1368.304 1410.6810 1432.035 1489.972 31546.151   100