2015-06-11 24 views
5

Lấy cảm hứng từ nghệ thuật http://gallery.rcpp.org/articles/parallel-distance-matrix/, tôi cố gắng sử dụng RcppParallel để chạy tìm kiếm brute-force trong không gian tham số chiều cao để backtesting bằng cách sử dụng multithreads. Tôi bị kẹt trong cách gọi một chức năng tự xác định trong phần struct. Ý tưởng là như thế này:Cách gọi hàm do người dùng định nghĩa trong RcppParallel?

Đầu tiên, tạo một ma trận tham số NumericMatrix params_mat trong R đầu tiên, và sử dụng dữ liệu backtesting với List, NumericVector, CharacterVector datatype, chẳng hạn như List Data_1, NumericVector Data_2, CharacterVector Data_3, ..., mà là tĩnh cho mỗi kịch bản tham số params_vec (Lưu ý rằng nó là hàng của params_mat).

Tiếp theo, xác định hàm backtesting xuất ra một vectơ bao gồm 3 biến quan trọng để đánh giá hiệu suất chiến lược.

Dưới đây là ví dụ về số params_matBacktesting_Fun có thể chạy tương ứng với R và 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; 
} 

Chắc chắn chúng ta cần phải viết lại/sửa đổi bản gốc Rcpp Backtesting_Fun với các loại RVector/RMatrix, và sau đó sử dụng RcppParallel mã sau để gọi Backtesting_Fun trong struct Backtest_parallel:

// [[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; 
} 

Dưới đây là những câu hỏi của tôi:

  1. Có thể RVector chứa List kiểu dữ liệu hoặc có bất kỳ thùng chứa cụ thể nào trong RcppParallel để chứa List;

  2. Trong Backtesting_Fun, đầu vào nên RVector/RMatrix loại, điều đó có nghĩa là chúng ta thực sự cần phải chuyển đổi ban đầu mã chính Rcpp với NumericVector vào RVector?

Hoặc có cách nào tốt hơn để thực hiện tính toán song song cho trường hợp của tôi trong RcppParallel không? Cảm ơn trước.

EDIT:

  1. tôi nhìn vào các ví dụ khác về RcppPararrel trong http://gallery.rcpp.org/articles/parallel-matrix-transform/, http://gallery.rcpp.org/articles/parallel-inner-product/, ý tưởng phổ biến ở struct operator() là sử dụng con trỏ để thao tác các dữ liệu đầu vào cho operator(), như vậy là có cách nào thế nào để xây dựng một hàm do người dùng định nghĩa trong trường hợp của tôi với các đầu vào con trỏ?

  2. Nếu cách trên không hoạt động, nó là khả thi để sử dụng wrap để chuyển đổi RVector/RMatrix trở lại vào Rcpp datatype, nghĩa là, NumericVector.. trong operator() để các loại đầu vào của chức năng người dùng định nghĩa Backtesting_Fun có thể vẫn không thay đổi.

+0

Có thể bạn sẽ có nhiều khả năng nhận được câu trả lời nếu bạn cung cấp ví dụ nhỏ hơn, đầy đủ (không có '...' s trong chức năng của bạn). – nrussell

+1

Cảm ơn bạn đã đề xuất @nrussell, tôi sẽ sớm cập nhật câu hỏi với ví dụ đơn giản và chính xác – Alvin

Trả lời

3

Tôi nghĩ rằng tôi có thể tìm thấy một cách khác để giải quyết câu hỏi này: Các phím được sử dụng một sợi accessors an toàn để chứa các biến trong struct, và vẫn RVector/RMatrix trong hàm chính bên ngoài để các parallelFor có thể làm việc tốt, đó là phần quan trọng nhất trong bản ngã song song này.Dưới đây là cách của tôi:

  1. Loại bỏ List datatype: Thay vào đó, chúng ta có thể chuyển đổi các biến List bằng cách sử dụng NumericVector/NumericMatrix container và ghi chỉ số tương ứng của nó để các subvector/submatrix sẽ chỉ các yếu tố tương tự vì nó là phần tử của danh sách.

  2. Chuyển đổi RVector/RMatrix vào arma::vec/arma::mat: Như đã đề cập trong RcppParallel Github, C++ Armadillo là thread-an toàn trong khai thác struct của. Ở đây, tôi sửa đổi ví dụ được đưa ra trong Parallel Distance Matrix Calculation with RcppParallel bằng cách sử dụng ý tưởng này, mà gần như vẫn giữ nguyên tốc độ thử nghiệm tương tự.

    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 
    

Như chúng ta có thể thấy, các kiểu dữ liệu trong vòng operator sẽ C++ arma, và bây giờ chúng tôi một cách an toàn và nhanh chóng tiện lợi có thể gọi hàm do người dùng định nghĩa của chúng tôi bằng cách trực tiếp bằng cách sử dụng đối tượng thay vì chỉ trỏ, mà có thể không chung hoặc dễ thiết kế.

Cấu trúc parallelFor này sẽ chia sẻ cùng một nguồn dữ liệu mà không cần sao chép thêm trong tính toán song song.

Các vấn đề liên quan