如何在RcppParallel中调用用户定义的函数?

时间:2015-06-11 21:03:54

标签: c++ r parallel-processing rcpp

受到艺术http://gallery.rcpp.org/articles/parallel-distance-matrix/的启发,我尝试使用RcppParallel在高维参数空间中进行强力搜索,以便使用多线程进行回测。我陷入了如何在struct部分调用自定义函数的问题。这个想法是这样的:

首先,首先在R中创建一个参数矩阵NumericMatrix params_mat,然后使用List, NumericVector, CharacterVector数据类型的回测数据,例如List Data_1, NumericVector Data_2, CharacterVector Data_3, ...,这些数据类型对于每个参数场景{{1}都是静态的(注意它是params_vec)的行。

接下来,定义返回测试函数,该函数输出包含3个关键变量的向量以评估策略性能。

以下是我可以分别在R和Rcpp中运行的params_matparams_mat的示例。

Backtesting_Fun

当然我们需要使用RVector / RMatrix类型重写/修改原始Rcpp //[[Rcpp::export]] NumericMatrix data_frame_rcpp(const Rcpp::List& list_params) { NumericMatrix res = list_params[0]; return res; } # R codes to generate params_mat params <- expand.grid (x_1=seq(1,100,1), x_2=seq(3,100,2), ..., x_n=seq(4,200,1)); list_params = list(ts(params)); tmp_params_data = data_frame_rcpp(list_params); params_mat = matrix(tmp_params_data, ncol = ncol(tmp_params_data), dimnames = NULL); params_vec = params_mat[ii,]; # User-defined Rcpp codes for backtesting NumericVector Backtesting_Fun (List Data_1, NumericVector Data_2, CharacterVector Data_3, ..., NumericVector params_vec) { // Main function parts to run backtesting for each params_vec scenario. ... etc // save 3 key result variables together with each params_vec (just a simple illustration). NumericVector res = NumericVector::create(params_vec[0],...,params_vec[size-1], key_1, key_2, key_3); return res; } ,然后使用以下Backtesting_Fun代码在RcppParallel中调用Backtesting_Fun:< / p>

struct Backtest_parallel

以下是我的问题:

  1. // [[Rcpp::depends(RcppParallel)]] #include <RcppParallel.h> using namespace RcppParallel; RVector<double> Backtesting_Fun (const RVector<double> Data_1, const RVector<double> Data_2, const RVector<string> Data_3,..., const RVector<double> params_vec) { // Main function parts to run backtesting for each params_vec scenario. ... etc; // save 3 key result variables together with each params_vec ... etc; return res; } struct Backtest_parallel : public Worker { // input matrix to read from const RVector<List> Data_1; const RVector<double> Data_2; const RVector<string> Data_3; ... const RMatrix<double> params_mat; // output matrix to write to RMatrix<double> rmat; // initialize from Rcpp input and output matrixes (the RMatrix class // can be automatically converted to from the Rcpp matrix type) Backtest_parallel(const List Data_1, const NumericVector Data_2, const CharacterVector Data_3, ..., const NumericMatrix params_mat) : Data_1(Data_1), Data_2(Data_2), Data_3(Data_3), ..., params_mat(params_mat) {} // function call operator that work for the specified range (begin/end) void operator()(std::size_t begin, std::size_t end) { for (std::size_t ii = begin; ii < end; i++) { // params rows that we will operate on RMatrix<double>::Row params_row = params_mat.row(ii); // Run the backtesting function defined above RVector<double> res = Backtesting_Fun(Data_1, Data_2, ..., params_row) for (std::size_t jj = 0; jj < res.length(); jj++) { // write to output matrix rmat(ii,jj) = res[jj]; } } } }; // [[Rcpp::export]] NumericMatrix rcpp_parallel_backtest(List Data_1, NumericVector Data_2, CharacterVector Data_3, ..., NumericMatrix params_mat) { // allocate the matrix we will return NumericMatrix rmat(params_mat.nrow(), params_mat.nrow()+3); // create the worker Backtest_parallel backtest_parallel(Data_1, Date_2, ..., params_mat); // call it with parallelFor parallelFor(0, rmat.nrow(), backtest_parallel); return rmat; } 可以包含RVector数据类型,还是List中有任何特定容器包含RcppParallel;

  2. List中,输入应为Backtesting_Fun类型,这是否意味着我们确实需要将带有RVector/RMatrix的原始Rcpp主代码转换为NumericVector

  3. 或者有没有更好的方法在RcppParallel中为我的案例进行并行计算?提前谢谢。

    修改

    1. 我在http://gallery.rcpp.org/articles/parallel-matrix-transform/http://gallery.rcpp.org/articles/parallel-inner-product/中查看有关RcppPararrel的其他示例,RVector中的常见想法是使用指针来操纵{{1}的数据输入那么有没有办法在我的情况下使用指针输入构建用户定义的函数?

    2. 如果上述方法不起作用,是否可以使用struct operator()operator()转换回Rcpp数据类型,即wrap中的RVector/RMatrix这样用户定义函数NumericVector..的输入类型可以保持不变。

1 个答案:

答案 0 :(得分:5)

我想我可能会找到另一种方法来解决这个问题:关键是使用线程安全访问器来包含struct中的变量,并保持RVector / RMatrix外部主要功能使parallelFor可以正常工作,这是这个平行算法中最重要的部分。以下是我的方法:

  1. 摆脱List数据类型:相反,我们可以使用List / NumericVector容器转换NumericMatrix变量,记录其相应的索引,以便子向量/子矩阵将指向与列表元素相同的元素。

  2. RVector / RMatrix转换为arma::vec / arma::mat :如RcppParallel Github中所述,{{1在struct的运算符中是线程安全的。在这里,我使用这个想法来修改Parallel Distance Matrix Calculation with RcppParallel中给出的示例,该概念几乎保持相同的测试速度。

    C++ Armadillo
  3. 正如我们所看到的,struct JsDistance : public Worker { const RMatrix<double> tmp_MAT; // input matrix to read from RMatrix<double> tmp_rmat; // output matrix to write to std::size_t row_size, col_size; // Convert global input/output into RMatrix/RVector type JsDistance(const NumericMatrix& matrix_input, NumericMatrix& matrix_output, std::size_t row_size, std::size_t col_size) : tmp_MAT(matrix_input), tmp_rmat(matrix_output), row_size(row_size), col_size(col_size) {} // convert RVector/RMatrix into arma type for Rcpp function // and the follwing arma data will be shared in parallel computing arma::mat convert() { RMatrix<double> tmp_mat = tmp_MAT; arma::mat MAT(tmp_mat.begin(), row_size, col_size, false); return MAT; } void operator()(std::size_t begin, std::size_t end) { for (std::size_t i = begin; i < end; i++) { for (std::size_t j = 0; j < i; j++) { // rows we will operate on arma::mat MAT = convert(); arma::rowvec row1 = MAT.row(i); // get the row of arma matrix arma::rowvec row2 = MAT.row(j); // compute the average using std::tranform from the STL std::vector<double> avg(row1.n_elem); std::transform(row1.begin(), row1.end(), // input range 1 row2.begin(), // input range 2 avg.begin(), // output range average); // function to apply // calculate divergences double d1 = kl_divergence(row1.begin(), row1.end(), avg.begin()); double d2 = kl_divergence(row2.begin(), row2.end(), avg.begin()); // write to output matrix tmp_rmat(i,j) = sqrt(.5 * (d1 + d2)); } } } }; // [[Rcpp::export]] NumericMatrix rcpp_parallel_js_distance_modify(const Rcpp::NumericMatrix& matrix_input, int N_cores) { // allocate the matrix we will return NumericMatrix matrix_output(matrix_input.nrow(), matrix_input.nrow()); std::size_t row_size = matrix_input.nrow(); std::size_t col_size = matrix_input.ncol(); // create the worker JsDistance jsDistance(matrix_input, matrix_output, row_size, col_size); // call it with parallelFor parallelFor(0, matrix_input.nrow(), jsDistance, matrix_input.nrow()/N_cores); // parallelFor with grain size setting return matrix_output; } // Example compare: n_row = 1E3; n_col = 1E2; m = matrix(runif(n_row*n_col), nrow = n_row, ncol = n_col); m = m/rowSums(m); res <- benchmark(rcpp_parallel_js_distance(m, 6), rcpp_parallel_js_distance_orignal(m, 6), order="relative") res[,1:4]; #test #elapsed #relative rcpp_parallel_js_distance_orignal(m, 6) 128.069 1.000 rcpp_parallel_js_distance(m, 6) 129.210 1.009 中的数据类型将是operator,现在我们可以通过直接使用对象而不是指针来安全快速地调用我们的用户定义函数,这可能不是通用或易于设计。

    现在,这个C++ arma结构将在并行计算中共享相同的数据源而无需额外的副本,然后我们可以通过使用上述问题中提到的想法对回测进行一些略微更改。