在R中并行化非平凡Gibbs采样器:RcppThread与RcppParallel
概述: 我感兴趣的是通过Rcpp/RcppEigen对一个非平凡回归问题并行化(跨链)Gibbs采样器。我已经阅读了和的文档,我想知道我对并行化此代码所涉及的挑战的理解是否准确,以及我建议的使用在R中并行化非平凡Gibbs采样器:RcppThread与RcppParallel,r,parallel-processing,statistics,rcpp,rcppparallel,R,Parallel Processing,Statistics,Rcpp,Rcppparallel,概述: 我感兴趣的是通过Rcpp/RcppEigen对一个非平凡回归问题并行化(跨链)Gibbs采样器。我已经阅读了和的文档,我想知道我对并行化此代码所涉及的挑战的理解是否准确,以及我建议的使用RcppThread的伪代码是否可行 编程挑战: 这个回归问题需要在吉布斯取样器的每次迭代中反转更新的设计矩阵。因此,任何新矩阵(每条链一个)都需要“线程安全”。也就是说,不存在一个线程写入另一个线程可能也尝试访问的内存的危险。如果这样做了,我就可以通过给Rcpp::parallelFor一个唯一的索引来
RcppThread
的伪代码是否可行
编程挑战:
这个回归问题需要在吉布斯取样器的每次迭代中反转更新的设计矩阵。因此,任何新矩阵(每条链一个)都需要“线程安全”。也就是说,不存在一个线程写入另一个线程可能也尝试访问的内存的危险。如果这样做了,我就可以通过给Rcpp::parallelFor
一个唯一的索引来分配样本,从而绘制并存储回归系数样本(beta)。我想知道在哪里/如何最好地初始化这些特定于线程的矩阵?。下面是我的总体概念理解,以及我如何基本上使用并行分配样本的样本原则来并行分配X的初步猜测。注意,这是假设Eigen对象可以进行并发索引访问,就像我在RcppThread
文档中看到的std::vector的内存访问方式一样
#include "RcppEigen.h>
// [[Rcpp::plugins(cpp11)]]
// [[Rcpp::depends(RcppThread)]]
// [[Rcpp::depends(RcppEigen)]]
// Sampler class definition
#include "Sampler.h"
#include "RcppThread.h"
// [[Rcpp::export]]
Eigen::ArrayXXd fancyregression(const Eigen::VectorXd &y, // outcome vector
const Eigen::MatrixXd &Z, // static sub-matrix of X
const int &num_iterations,
const int &num_chains_less_one,
const int &seed,
...)
{
std::mt19937 rng;
rng(seed);
const int dim_X = get_dim_X(Z,...);
const int n = y.rows();
const int num_chains = num_chains_less_one + 1;
Eigen::ArrayXXd beta_samples;
beta_samples.setZero(num_iterations,num_chains*dim_X);
Eigen::MatrixXd shared_X(n,dim_X*num_chains);
// sampler object only has read access to its arguments
SamplerClass sampler(y,Z,...);
//chain for loop
RcppThread::parallelFor(0, num_chains_less_one,[&beta, &shared_X, &n,&sampler, &dim_X, &rng](unsigned int chain){
// chain specific iteration for loop
for(unsigned int iter_ix = 0; iter_ix < num_iterations ; iter_ix ++){
X.block(0,dim_X*chain,n,dim_X) = sampler.create_X(rng);
beta_samples(iter_ix,dim_X*chain) = sampler.get_beta_sample(X,rng);
}
});
return(beta_samples);
}
#包括“RcppEigen.h>
//[[Rcpp::插件(cpp11)]]
//[[Rcpp::Dependes(RcppThread)]]
//[[Rcpp::depends(RcppEigen)]]
//采样器类定义
#包括“Sampler.h”
#包括“RcppThread.h”
//[[Rcpp::导出]]
特征::Arrayxd fancyregression(常数特征::向量xd&y,//结果向量
常数本征::矩阵xxd&Z,//X的静态子矩阵
常量int和num_迭代,
常量整数和数量链数减去一个,
康斯特国际种子公司,
...)
{
标准:mt19937 rng;
rng(种子);
const int dim_X=获取dim_X(Z,…);
常量int n=y.行();
const int num_chains=num_chains_less_one+1;
本征::Arrayxdβ_样本;
beta_samples.setZero(num_迭代,num_链*dim_X);
本征::矩阵X×d共享X(n,dim×num×链);
//采样器对象只有对其参数的读取权限
采样器类采样器(y,Z,…);
//循环链
RcppThread::parallelFor(0,num_chains_less_one,[&beta,&shared_X,&n,&sampler,&dim_X,&rng](无符号整数链){
//循环的特定链迭代
for(无符号整数iter_ix=0;iter_ix
“初始化这些线程特定矩阵的最佳位置/方式是什么?”
您正在寻找特定于线程的资源。下面是一个简单的示例:
#include <Rcpp.h>
#include <RcppParallel.h>
using namespace Rcpp;
using namespace RcppParallel;
// [[Rcpp::depends(RcppParallel)]]
// [[Rcpp::plugins(cpp11)]]
struct Test : public Worker {
tbb::enumerable_thread_specific<bool> printonce;
Test() : printonce(false) {}
void operator()(std::size_t begin, std::size_t end) {
tbb::enumerable_thread_specific<bool>::reference p = printonce.local();
if(!p) { // print once per thread
std::cout << 1;
p= true;
}
}
};
// [[Rcpp::export(rng = false)]]
void test() {
Test x{};
parallelFor(0, 10000, x);
}
#包括
#包括
使用名称空间Rcpp;
使用名称空间RcppParallel;
//[[Rcpp::dependens(RcppParallel)]]
//[[Rcpp::插件(cpp11)]]
结构测试:公共工作者{
tbb::可枚举线程特定打印一次;
Test():printonce(false){}
void运算符()(std::size\u t begin,std::size\u t end){
tbb::可枚举线程特定::引用p=printonce.local();
如果(!p){//每个线程打印一次
Std::很好。我认为,给每个线程并发只读访问采样器对象的内部成员不会是问题吗?是的,对大多数事物来说,并发阅读通常不是问题,尤其是当你直接通过指针访问时。C++读取的访问是根据C++标准定义的。e:@thc嗨,你能帮我看看这个问题吗?谢谢。