我有一个澄清问题。 据我所知,sourceCpp自动传递RNG状态,因此set.seed(123)在调用Rcpp代码时给出了可重现的随机数。编译包时,我必须添加一组RNG语句。 现在,如何在sourceCpp或包中使用openMP?
考虑以下Rcpp代码
#include <Rcpp.h>
#include <omp.h>
// [[Rcpp::depends("RcppArmadillo")]]
// [[Rcpp::export]]
Rcpp::NumericVector rnormrcpp1(int n, double mu, double sigma ){
Rcpp::NumericVector out(n);
for (int i=0; i < n; i++) {
out(i) =R::rnorm(mu,sigma);
}
return(out);
}
// [[Rcpp::export]]
Rcpp::NumericVector rnormrcpp2(int n, double mu, double sigma, int cores=1 ){
omp_set_num_threads(cores);
Rcpp::NumericVector out(n);
#pragma omp parallel for schedule(dynamic)
for (int i=0; i < n; i++) {
out(i) =R::rnorm(mu,sigma);
}
return(out);
}
然后运行
set.seed(123)
a1=rnormrcpp1(100,2,3,2)
set.seed(123)
a2=rnormrcpp1(100,2,3,2)
set.seed(123)
a3=rnormrcpp2(100,2,3,2)
set.seed(123)
a4=rnormrcpp2(100,2,3,2)
all.equal(a1,a2)
all.equal(a3,a4)
虽然a1和a2相同,但a3和a4不相同。如何使用openMP循环调整RNG状态?我可以吗?
答案 0 :(得分:2)
为了扩展Dirk Eddelbuettel已经说过的内容,几乎不可能同时生成相同的PRN序列并且具有所需的加速。其根源在于PRN序列的生成本质上是一个顺序过程,其中每个状态依赖于前一个状态,这会创建一个向后依赖链,该链向后延伸到初始播种状态。
此问题有两种基本解决方案。其中一个需要大量内存,另一个需要大量CPU时间,实际上两者都比真正的解决方案更像是变通方法:
预生成的PRN序列:一个线程按顺序生成大量PRN,然后所有线程以与顺序情况一致的方式访问此数组。此方法需要大量内存才能存储序列。另一种选择是将序列存储到稍后存储器映射的磁盘文件中。后一种方法的优点是节省了一些计算时间,但通常I / O操作很慢,因此只有处理能力有限或RAM少的机器才有意义。
prewound PRNGs:这种方法适用于在线程之间静态分配工作的情况,例如:与schedule(static)
。每个线程都有自己的PRNG,所有PRNG都使用相同的初始种子播种。然后每个线程绘制与其开始迭代一样多的虚拟PRN,基本上将其PRNG预绕到正确的位置。例如:
out(0:99)
out(100:199)
out(200:299)
等等。除了绘制PRN之外,当每个线程进行大量计算时,此方法也能正常工作,因为在某些情况下(例如,多次迭代),PRNG前置的时间可能很长。(
除了绘制PRN之外还有大量数据处理的情况下存在第三种选择。这个使用OpenMP有序循环(注意迭代块大小设置为1):
#pragma omp parallel for ordered schedule(static,1)
for (int i=0; i < n; i++) {
#pragma omp ordered
{
rnum = R::rnorm(mu,sigma);
}
out(i) = lots of processing on rnum
}
虽然循环排序本质上是序列化计算,但它仍然允许lots of processing on rnum
并行执行,因此可以观察到并行加速。有关原因,请参阅this answer以获得更好的解释。
答案 1 :(得分:1)
是,sourceCpp()
等以及RNGScope
的实例化,以便RNG保持适当的状态。
是的,可以做OpenMP。但是在OpenMP段内部,您无法控制线程执行的顺序 - 因此您需要更长的相同序列。我正在开发一个包有同样的问题,我希望有可重复的绘图,但仍然使用OpenMP。但似乎你不能。