简体   繁体   English

Rcpp sample v. C++ shuffle 的效率

[英]Efficiency of Rcpp sample v. C++ shuffle

I'm trying to optimize an algorithm for R. Initially, I wrote the algorithm using Rcpp (and Rcpp vectors, etc.) but subsequently rewrote it using standard C++ vectors and only translating it to Rcpp at the final stage.我正在尝试为 R 优化算法。最初,我使用 Rcpp(和 Rcpp 向量等)编写了算法,但随后使用标准 C++ 向量重写了它,并且仅在最后阶段将其转换为 Rcpp。 However, the component of the C++ algorithm that involves shuffle seems quite slow.但是,涉及shuffle的 C++ 算法组件似乎很慢。 In fact, translating back and forth to an Rcpp vector so that I can use the Rcpp/R sample function is much faster.事实上,来回转换为 Rcpp 向量以便我可以使用 Rcpp/R sample函数要快得多。 This surprises me.这让我很惊讶。

Here's a minimally reproducible example:这是一个最小可重现的示例:

#include <Rcpp.h>
#include <random>
#include <algorithm>

// [[Rcpp::export]]

List test_cpp(int n, int x)  {

  List return_list(n);

  std::vector<int> v;
  v.reserve(x);

  for(int i = 0; i < x; ++i) {
    v.push_back(i);
  }

  std::random_device rd;
  std::mt19937 g(rd());

  for(int i = 0; i < n; ++i)  {
    std::shuffle(v.begin(), v.end(), g);
    return_list(i) = v;
  }

  return return_list;
}


// [[Rcpp::export]]

List test_r(int n,
            int x)  {

  List return_list(n);

  std::vector<int> v;
  v.reserve(x);

  for(int i = 0; i < x; ++i){
      v.push_back(i);
    }

  IntegerVector vs = wrap(v);

  for(int i = 0; i < n; ++i)  {
    IntegerVector s_v = sample(vs, v.size());
    std::vector<int> s_v_c = as<std::vector<int>>(s_v);
    return_list(i) = s_v_c;
  }

  return return_list;
}

The first function using C++ shuffle is significantly slower than the version using Rcpp sample until you're shuffling a vector of ~50,000 elements.使用 C++ shuffle的第一个函数比使用 Rcpp sample的版本慢得多,直到您对约 50,000 个元素的向量进行混洗。 For an example closer to most of my use cases, the following produces median times of ~13 ms for the Rcpp sample v. ~20 ms for C++ shuffle .对于一个更接近我的大多数用例的示例,以下为 Rcpp sample产生约 13 毫秒的中值时间。对于 C++ shuffle约 20 毫秒。

n <- 1000
x <- 999

speed <- bench::mark(min_iterations = 100, 
                       check = FALSE,
                       cpp = test_cpp(n, x),
                       rcpp = test_r(n, x)
                       )

  ggplot2::autoplot(speed) +
    ggplot2::theme_minimal() +
    ggplot2::xlab(NULL) +
    ggplot2::ylab(NULL) 

在此处输入图像描述

It's likely that I've mucked up the C++ code.很可能我搞砸了 C++ 代码。 If so could someone show me my mistake?如果是这样,有人可以告诉我我的错误吗? Or is it that shuffle is just slow and I should use a different C++ algorithm?还是shuffle很慢,我应该使用不同的 C++ 算法? Or is there some penalty in calling an algorithm/random number generator outside of R/Rcpp that explains this difference in performance?或者在 R/Rcpp 之外调用算法/随机数生成器是否有一些惩罚来解释这种性能差异? Thankful for any suggestions.感谢您的任何建议。

Edit To illustrate that the inefficiency for the C++ versions doesn't come from having to convert standard vectors to IntegerVectors, I've modified the Rcpp version so that after sampling IntegerVectors are superfluously converted to standard vectors (and then back to IntegerVectors).编辑为了说明 C++ 版本的低效率并非来自必须将标准向量转换为 IntegerVectors,我修改了 Rcpp 版本,以便在采样后 IntegerVectors 被多余地转换为标准向量(然后返回 IntegerVectors)。

Update更新

I've experimented a bit with alternative pseudo random number generators.我已经尝试了一些替代的伪随机数生成器。 This post suggest that the Mersenne Twister pseudo random number generator I use above is relatively slow compared to some alternatives. 这篇文章表明我在上面使用的 Mersenne Twister 伪随机数生成器与某些替代方案相比相对较慢。 I tried the pseudo random number generators coded in this post and they are indeed faster but they don't massively improve performance.我尝试了这篇文章中编码的伪随机数生成器,它们确实更快,但它们并没有大幅提高性能。 Here are my simplified test functions.这是我的简化测试功能。

// [[Rcpp::export]]

void test_pcg(int x)  {
  std::vector<int> v;   
  v.reserve(x);
  for(int i = 0; i < x; ++i) {
    v.push_back(i);
  }
  std::random_device rd;   
  pcg g(rd);
  std::shuffle(v.begin(), v.end(), g);
}


  // [[Rcpp::export]]

  void test_mt(int x)  {
    std::vector<int> v;
    v.reserve(x);
    for(int i = 0; i < x; ++i) {
      v.push_back(i);
    }
    std::random_device rd;
    std::mt19937 g(rd());
    std::shuffle(v.begin(), v.end(), g);
  }


// [[Rcpp::export]]

void test_splitmix(int x)  {
  std::vector<int> v;   
  v.reserve(x);
  for(int i = 0; i < x; ++i) {
    v.push_back(i);
  }
  std::random_device rd;   
  splitmix g(rd);   
  std::shuffle(v.begin(), v.end(), g);
}



// [[Rcpp::export]]

void test_xorshift(int x)  {
  std::vector<int> v;   
  v.reserve(x);
  for(int i = 0; i < x; ++i) {
    v.push_back(i);
  }
  std::random_device rd;   
  xorshift g(rd);
  std::shuffle(v.begin(), v.end(), g);
}


// [[Rcpp::export]]

void test_rcpp(int x)  {
  IntegerVector v = seq(0, x);   
  IntegerVector s_v = sample(v, x);
}

For a vector of 1,000, the Rcpp version is still massively faster, ~13 ms compared to 20 ms for the fastest RNG's with C++ shuffle.对于 1,000 的向量,Rcpp 版本仍然要快得多,约为 13 毫秒,而使用 C++ shuffle 的最快 RNG 版本为 20 毫秒。

From what I understand, C++ shuffle implements the Fisher - Yates (Knuth) shuffle.据我了解,C++ shuffle 实现了 Fisher - Yates (Knuth) shuffle。 My conjecture now is that the Rcpp sample function doesn't implement the Fisher-Yates shuffle when all elements are sampled without replacement but instead utilizes a sorting algorithm?我现在的猜想是,当所有元素都在没有替换的情况下被采样而是使用排序算法时,Rcpp 示例函数没有实现 Fisher-Yates 洗牌? Perhaps there's a similar algorithm in C++ that would be faster than shuffle for my application?也许 C++ 中有一个类似的算法比我的应用程序的 shuffle 更快?

As alluded to in my comment, your functions may 'do too much'.正如我在评论中提到的那样,您的功能可能“做得太多”。 Here is simplified example (which is also nonsensical as we likely alter the input vector each time) but it distills your question down to 'is sample faster than shuffle from the standard library'.这是简化的示例(这也是荒谬的,因为我们可能每次都更改输入向量),但它将您的问题提炼为“比标准库中的随机播放更快”。 And it is not.事实并非如此。

在此处输入图像描述

My modified code follows below.我修改后的代码如下。

Code代码

#include <Rcpp.h>
#include <random>
#include <algorithm>

// [[Rcpp::export]]
Rcpp::IntegerVector shuffle_cpp(Rcpp::IntegerVector x)  {
    std::random_device rd;
    std::mt19937 g(rd());
    std::shuffle(x.begin(), x.end(), g);
    return x;
}

// [[Rcpp::export]]
Rcpp::IntegerVector sample_rcpp(Rcpp::IntegerVector x)  {
    return sample(x, x.size());
}

/*** R
v <- seq(1, 1e6)
res <- bench::mark(min_iterations = 100, check = FALSE, shuffle_cpp(v), sample_rcpp(v))
res
ggplot2::autoplot(res) + ggplot2::theme_minimal() + ggplot2::ylab(NULL)
*/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM