【问题标题】:Rcpp - generate multiple random observations from custom distributionRcpp - 从自定义分布生成多个随机观察
【发布时间】:2019-02-12 05:44:24
【问题描述】:

这个问题与previous one 有关在 Rcpp 中的函数内调用函数的问题。

我需要以类似于 rnorm() 或 rbinom() 的方式从自定义分布中生成大量随机抽取,但我的函数会产生矢量输出。

作为一种解决方案,我考虑定义一个从自定义分布生成观察结果的函数,然后定义一个通过 for 循环从生成函数中提取 n 次的 main 函数。下面是代码的一个非常简化的工作版本:

#include <Rcpp.h>
using namespace Rcpp;

// generating function
NumericVector gen(NumericVector A, NumericVector B){
  NumericVector out = no_init_vector(2); 
  out[0] = R::runif(A[0],A[1]) + R::runif(B[0],B[1]);
  out[1] = R::runif(A[0],A[1]) - R::runif(B[0],B[1]);
  return out;
}

// [[Rcpp::export]]
// draw n observations
NumericVector rdraw(int n, NumericVector A, NumericVector B){
  NumericMatrix out = no_init_matrix(n, 2);
  for (int i = 0; i < n; ++i) {
    out(i,_) = gen(A, B); 
  }
  return out;
}

我正在寻找加快抽签速度的方法。我的问题是:有没有比 for 循环更有效的替代方法?在这种情况下并行化会有所帮助吗?

感谢您的帮助!

【问题讨论】:

  • 应该NumericVector rdrawNumericMatrix rdraw
  • @SymbolixAU 我认为这无关紧要,因为矩阵是一个向量。但明确一点也很好。
  • @Jinglestar 不确定是否可以加快抽签(也许使用 RcppArmadillo?)。但是,如果直接在gen 中分配,您可以分配更少的内存。为什么不直接在 R 中执行此操作?
  • @F.Privé 感谢您的 cmets!可能由于缺乏经验,我没有关注您:您所说的“分配”功能是什么意思?
  • @Jinglestar 在gen 中,您每次都在创建一个新向量,填充它,然后使用该向量填充您的矩阵。你可以直接填充你的矩阵。

标签: r random rcpp


【解决方案1】:

有不同的方法可以加快速度:

  1. gen() 上使用inline,减少函数调用次数。
  2. 使用Rcpp::runif 而不是带有R::runif 的循环来删除更多的函数调用。
  3. 使用允许并行执行的更快的 RNG。

这里是第 1 点和第 2 点:

#include <Rcpp.h>
using namespace Rcpp;

// generating function
inline NumericVector gen(NumericVector A, NumericVector B){
  NumericVector out = no_init_vector(2); 
  out[0] = R::runif(A[0],A[1]) + R::runif(B[0],B[1]);
  out[1] = R::runif(A[0],A[1]) - R::runif(B[0],B[1]);
  return out;
}

// [[Rcpp::export]]
// draw n observations
NumericVector rdraw(int n, NumericVector A, NumericVector B){
  NumericMatrix out = no_init_matrix(n, 2);
  for (int i = 0; i < n; ++i) {
    out(i,_) = gen(A, B); 
  }
  return out;
}

// [[Rcpp::export]]
// draw n observations
NumericVector rdraw2(int n, NumericVector A, NumericVector B){
  NumericMatrix out = no_init_matrix(n, 2);
  out(_, 0) = Rcpp::runif(n, A[0],A[1]) + Rcpp::runif(n, B[0],B[1]);
  out(_, 1) = Rcpp::runif(n, A[0],A[1]) - Rcpp::runif(n, B[0],B[1]);
  return out;
}

/*** R
set.seed(42)
system.time(rdraw(1e7, c(0,2), c(1,3)))
system.time(rdraw2(1e7, c(0,2), c(1,3)))
*/

结果:

> set.seed(42)

> system.time(rdraw(1e7, c(0,2), c(1,3)))
   user  system elapsed 
  1.576   0.034   1.610 

> system.time(rdraw2(1e7, c(0,2), c(1,3)))
   user  system elapsed 
  0.458   0.139   0.598 

作为比较,您的原始代码在 10^7 次绘制中花费了大约 1.8 秒。对于第 3 点。我正在修改我的 dqrng 包中的 parallel vignette 代码:

#include <Rcpp.h>
// [[Rcpp::depends(dqrng)]]
#include <xoshiro.h>
#include <dqrng_distribution.h>
// [[Rcpp::plugins(openmp)]]
#include <omp.h>
// [[Rcpp::depends(RcppParallel)]]
#include <RcppParallel.h>
// [[Rcpp::plugins(cpp11)]]
// [[Rcpp::export]]
Rcpp::NumericMatrix rdraw3(int n, Rcpp::NumericVector A, Rcpp::NumericVector B, int seed, int ncores) {
  dqrng::uniform_distribution distA(A(0), A(1));
  dqrng::uniform_distribution distB(B(0), B(1));
  dqrng::xoshiro256plus rng(seed);
  Rcpp::NumericMatrix res = Rcpp::no_init_matrix(n, 2);
  RcppParallel::RMatrix<double> output(res);

  #pragma omp parallel num_threads(ncores)
  {
  dqrng::xoshiro256plus lrng(rng);      // make thread local copy of rng 
  lrng.jump(omp_get_thread_num() + 1);  // advance rng by 1 ... ncores jumps 
  auto genA = std::bind(distA, std::ref(lrng));
  auto genB = std::bind(distB, std::ref(lrng));      

  #pragma omp for
  for (int i = 0; i < n; ++i) {
    output(i, 0) = genA() + genB();
    output(i, 1) = genA() - genB();
  }
  }
  return res;
}

/*** R
system.time(rdraw3(1e7, c(0,2), c(1,3), 42, 2))
*/

结果:

> system.time(rdraw3(1e7, c(0,2), c(1,3), 42, 2))
   user  system elapsed 
  0.276   0.025   0.151 

因此,通过更快的 RNG 和适度的并行性,我们可以将执行时间提高一个数量级。当然,结果会有所不同,但汇总统计数据应该相同。

【讨论】:

  • 太棒了!谢谢你的解释!不幸的是,这个例子比真正的代码简单得多:我的生成函数不能向量化,所以不能应用第 2 点。在这种情况下,你认为 for 循环有什么更快的替代方法吗?
  • @Jinglestar 为什么你认为for 循环可能有问题?使用快速 RNG 和尽可能少的内存分配确实有意义,c.f.第 3 点,如果看起来很吓人,也可以不使用并行化。
猜你喜欢
  • 2017-05-10
  • 2011-05-27
  • 1970-01-01
  • 1970-01-01
  • 2017-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-23
相关资源
最近更新 更多