runif的表现

Eri*_*rik 3 c++ memory r rcpp

我正在为特定问题开发自定义引导算法,因为我想要大量的重复,我确实关心性能.在这方面,我对如何正确使用runif有一些疑问.我知道我自己可以运行基准测试,但C++优化往往很难,我也想了解任何差异的原因.

第一个问题:

第一个代码块是否比第二个更快?

for (int i = 0; i < n_boot; i++) {
  new_random = runif(n);  //new_random is pre-allocated in class
  // do something with the random numbers
}
Run Code Online (Sandbox Code Playgroud)
for (int i = 0; i < n_boot; i++) {
  NumericVector new_random = runif(n);
  // do something with the random numbers
}
Run Code Online (Sandbox Code Playgroud)

它可能归结为runif是填充左侧还是分配并传递新的NumericVector.

第二个问题:

如果两个版本都分配了一个新的向量,我可以通过在标量模式下一次生成一个随机数来改进吗?

如果您想知道,内存分配占用了我处理时间的相当大一部分.通过优化其他不必要的内存分配,我将运行时间减少了30%,因此它很重要.

nru*_*ell 6

我设置了以下内容struct以尝试准确地表示您的场景并促进基准测试:

#include <Rcpp.h>
// [[Rcpp::plugins(cpp11)]]

struct runif_test {

  size_t runs;
  size_t each;

  runif_test(size_t runs, size_t each)
  : runs(runs), each(each)
  {}
  // Your first code block
  void pre_init() {
    Rcpp::NumericVector v = no_init();
    for (size_t i = 0; i < runs; i++) {
      v = Rcpp::runif(each);
    }
  }
  // Your second code block
  void post_init() {
    for (size_t i = 0; i < runs; i++) {
      Rcpp::NumericVector v = Rcpp::runif(each);
    }
  }
  // Generate 1 draw at a time  
  void gen_runif() {
    Rcpp::NumericVector v = no_init();
    for (size_t i = 0; i < runs; i++) {
      std::generate_n(v.begin(), each, []() -> double {
        return Rcpp::as<double>(Rcpp::runif(1));
      });
    }
  }
  // Reduce overhead of pre-allocated vector
  inline Rcpp::NumericVector no_init() {
    return Rcpp::NumericVector(Rcpp::no_init_vector(each));
  } 
};
Run Code Online (Sandbox Code Playgroud)

我在哪里对以下导出函数进行基准测试:

// [[Rcpp::export]]
void do_pre(size_t runs, size_t each) {
  runif_test obj(runs, each);
  obj.pre_init();
}

// [[Rcpp::export]]
void do_post(size_t runs, size_t each) {
  runif_test obj(runs, each);
  obj.post_init();
}

// [[Rcpp::export]]
void do_gen(size_t runs, size_t each) {
  runif_test obj(runs, each);
  obj.gen_runif();
}
Run Code Online (Sandbox Code Playgroud)

以下是我得到的结果:

R>  microbenchmark::microbenchmark(
    do_pre(100, 10e4)
    ,do_post(100, 10e4)
    ,do_gen(100, 10e4)
    ,times=100L)
Unit: milliseconds
                 expr      min       lq      mean   median        uq       max neval
  do_pre(100, 100000) 109.9187 125.0477  145.9918 136.3749  152.9609  337.6143   100
 do_post(100, 100000) 103.1705 117.1109  132.9389 130.4482  142.7319  204.0951   100
  do_gen(100, 100000) 810.5234 911.3586 1005.9438 986.8348 1062.7715 1501.2933   100
Run Code Online (Sandbox Code Playgroud)
R>  microbenchmark::microbenchmark(
    do_pre(100, 10e5)
    ,do_post(100, 10e5)
    ,times=100L)
Unit: seconds
                  expr      min       lq     mean   median       uq      max neval
  do_pre(100, 1000000) 1.355160 1.614972 1.740807 1.723704 1.815953 2.408465   100
 do_post(100, 1000000) 1.198667 1.342794 1.443391 1.429150 1.519976 2.042511   100
Run Code Online (Sandbox Code Playgroud)

所以,假设我解释/准确地表达了你的第二个问题,

如果两个版本都分配了一个新的向量,我可以通过在标量模式下一次生成一个随机数来改进吗?

使用我的gen_runif()成员函数,我认为我们可以自信地说这不是最佳方法 - 比其他两个函数慢7.5倍.

更重要的是,为了解决你的第一个问题,似乎只是初始化并NumericVector为输出分配一个新的快一点Rcpp::runif(n).我当然不是C++专家,但我相信第二种方法(分配给新的本地对象)比第一种方法更快,因为复制省略.在第二种情况下,它看起来好象正在创建两个对象-上的左侧的对象=,v以及(临时右值?)右侧的对象=,这是的结果Rcpp::runif().实际上,编译器很可能会优化这个不必要的步骤 - 我认为这一点在我链接的文章的这篇文章中有所解释:

当无名的临时(未绑定到任何引用)将被移动或复制到相同类型的对象中时......将省略复制/移动.构造临时值时,它将直接构建在存储器中,否则将被移动或复制到该存储器中.

这至少是我如何解释结果.希望能够精通语言的人能够确认/否认/纠正这个结论.