我有一个Rcpp函数,该函数输出一个大的矩阵,我想另存为R对象。我的想法是与包foreach并行使用Rcpp函数来加快处理速度。
对于相同的矩阵大小,在我的Windows机器上使用foreach所花费的时间大约是不使用foreach来运行该功能(不包括工作程序的设置)的时间的五倍以上。 我知道与并行执行非常小的任务有关的问题(例如Why is the parallel package slower than just using apply?)。我也愿意抛弃并行运行随机数生成器的理论问题,因为结果可能不再是真正的随机性。
由于我的子任务应该足够大,因此显然我编写的Rcpp函数不能很好地并行工作,但我不知道为什么。在Rcpp函数中使用RNG仅仅是一项无法并行化的任务吗?除此之外:foreach中我的子矩阵是否存在最优i以及最优icol(在此为n_bootstrap)?非常感谢任何帮助。另外,如果您愿意,也可以随意对代码进行评论。
澄清:我编译了一个程序包,并在foreach中使用mypackage :: funC
这是R中的示例代码:
y <- funC(n_bootstrap = 250, n_obs_censusdata = 300000,
locationeffects = as.numeric(1:200),
residuals = as.numeric(1:20000),
X = matrix(as.numeric(1:3000000), ncol = 10),
beta_sample = matrix(as.numeric(1:2500), ncol = 250))
并行:
no_cores <- parallel::detectCores() - 2
cl <- parallel::makeCluster(no_cores)
doParallel::registerDoParallel(cl)
y <- foreach(i=1:5, .combine = "cbind") %dopar% {
funC(n_bootstrap = 50,
n_obs_censusdata = 300000, locationeffects = as.numeric(1:200),
residuals = as.numeric(1:20000),
X = matrix(as.numeric(1:3000000), ncol = 10),
beta_sample = matrix(as.numeric(1:2500), ncol = 250))
}
parallel::stopCluster(cl)
添加:带有bigstatsr
y <- bigstatsr::FBM(nrow = 300000, ncol = 250, type = "double")
bigstatsr::big_apply(y, a.FUN = function(y, ind, fun) {
y[, ind] <- fun(n_bootstrap = length(ind),
n_obs_censusdata = 300000,
locationeffects = as.numeric(1:200),
residuals = as.numeric(1:20000),
X = matrix(as.numeric(1:3000000), ncol = 10),
beta_sample = matrix(as.numeric(1:2500), ncol = 250))
NULL
}, a.combine = 'c', ncores = bigstatsr::nb_cores(), fun = funC)+
这是Rcpp代码:
// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; indent-tabs-mode: nil; -*-
#include <RcppEigen.h>
#include <random>
using namespace Rcpp;
// [[Rcpp::depends(RcppEigen)]]
// [[Rcpp::plugins(cpp11)]]
// [[Rcpp::export]]
SEXP funC(const int n_bootstrap,
const int n_obs_censusdata,
const Eigen::Map<Eigen::VectorXd> locationeffects,
const Eigen::Map<Eigen::VectorXd> residuals,
const Eigen::Map<Eigen::MatrixXd> X,
const Eigen::Map<Eigen::MatrixXd> beta_sample)
{
// --------- create random sample of locations and of residuals --------- //
// initialise random seeds
std::random_device rd; // used to obtain a seed for the number engine
std::mt19937 gen(rd()); // Mersenne Twister engine
// initialize distributions for randam locations and residuals
const int upperlocation = locationeffects.size();
const int upperresiduals = residuals.size();
std::uniform_int_distribution<> distrloc(1, upperlocation);
std::uniform_int_distribution<> distrres(1, upperresiduals);
// initialize and fill matrix for randam locations and residuals
Eigen::MatrixXd LocationEffectResiduals(n_obs_censusdata, n_bootstrap);
for (int i=0; i<n_obs_censusdata; ++i)
for (int j=0; j<n_bootstrap; j++)
LocationEffectResiduals(i,j) = locationeffects[distrloc(gen)-1] + residuals[distrres(gen)-1]; // subtract 1 because in C++ indices start with 0
// ----- create Xbeta ------- //
Eigen::MatrixXd Xbeta = X * beta_sample;
// ----- combine results ------- //
Eigen::MatrixXd returnmatrix = Xbeta + LocationEffectResiduals;
return Rcpp::wrap(returnmatrix);
}
答案 0 :(得分:3)
在这里您要创建一个大矩阵。原则上可以将其分布到多个过程中,但是要承担最终组合结果的成本。我建议在这里使用“共享内存并行性”。我使用OpenMP code from here作为并行算法的起点:
// [[Rcpp::depends(RcppEigen)]]
#include <RcppEigen.h>
// [[Rcpp::depends(dqrng)]]
#include <xoshiro.h>
// [[Rcpp::plugins(openmp)]]
#include <omp.h>
// [[Rcpp::plugins(cpp11)]]
#include <random>
// [[Rcpp::export]]
Eigen::MatrixXd funD(const int n_bootstrap,
const int n_obs_censusdata,
const Eigen::Map<Eigen::VectorXd> locationeffects,
const Eigen::Map<Eigen::VectorXd> residuals,
const Eigen::Map<Eigen::MatrixXd> X,
const Eigen::Map<Eigen::MatrixXd> beta_sample,
int ncores) {
// --------- create random sample of locations and of residuals --------- //
// initialise random seeds
std::random_device rd; // used to obtain a seed for the number engine
dqrng::xoshiro256plus gen(rd());
// initialize distributions for randam locations and residuals
const int upperlocation = locationeffects.size();
const int upperresiduals = residuals.size();
// subtract 1 because in C++ indices start with 0
std::uniform_int_distribution<> distrloc(0, upperlocation - 1);
std::uniform_int_distribution<> distrres(0, upperresiduals - 1);
// initialize and fill matrix for randam locations and residuals
Eigen::MatrixXd LocationEffectResiduals(n_obs_censusdata, n_bootstrap);
#pragma omp parallel num_threads(ncores)
{
dqrng::xoshiro256plus lgen(gen); // make thread local copy of rng
lgen.jump(omp_get_thread_num() + 1); // advance rng by 1 ... ncores jumps
#pragma omp for
for (int i=0; i<n_obs_censusdata; ++i)
for (int j=0; j<n_bootstrap; j++)
LocationEffectResiduals(i,j) = locationeffects[distrloc(lgen)] + residuals[distrres(lgen)];
}
// ----- create Xbeta ------- //
Eigen::MatrixXd Xbeta = X * beta_sample;
// ----- combine results ------- //
Eigen::MatrixXd returnmatrix = Xbeta + LocationEffectResiduals;
return returnmatrix;
}
在我的双核Linux系统上,我的funD
和ncores = 1
比funC
快一点,可能是因为使用的RNG更快。使用ncores = 2
可获得30-40%的收益。鉴于并非所有代码都并行执行,这还不错。我不知道这些天在Windows上OpenMP的性能如何。改为使用RcppParallel
可能有意义。但这需要对代码进行更多更改。
上面的代码应与Rcpp::sourceCpp()
一起使用。将其放入包装中时,应使用
CXX_STD = CXX11
PKG_CXXFLAGS = $(SHLIB_OPENMP_CXXFLAGS)
PKG_LIBS = $(SHLIB_OPENMP_CXXFLAGS)
在Makevars(.win)
中。请注意,根据WRE,如果用于C ++ 11的编译器与用于C ++ 98的编译器不同,则可能无法按预期工作。在默认配置中,IIRC Solaris是唯一的平台。因此,对于内部软件包,您应该没事。