Rcppparallel начальной загрузки - PullRequest
0 голосов
/ 05 июля 2018

Я предполагаю, или, скорее, надеюсь, что у меня есть единственная решаемая проблема или, возможно, множество мелких проблем, и я должен отказаться. В любом случае, я относительно новичок в Rcpp и крайне не осведомлен о параллельных вычислениях и не могу найти решение онлайн.

Проблема, как правило, в том, что 'фатальная ошибка' в R или R застревает в цикле, что-то вроде 5 минут на 10 итераций, когда непараллельная версия будет делать 5 000 итераций в одно и то же время, грубо говоря.

Поскольку этот алгоритм вписывается в гораздо больший проект, я вызываю несколько других функций, все они в Rcpp, и я переписал их только с объектами 'arma', которые, похоже, помогли другим людям здесь. Я также запустил часть оптимизации с помощью оптимизатора «тепловых карт», который я написал в Rcpp, опять же исключительно в «arma» без улучшений - я должен также указать, что это возвращено как «arma :: vec».

// [[Rcpp::depends("RcppArmadillo")]]
// [[Rcpp::depends("RcppParallel")]]
#include <RcppArmadillo.h>
#include <RcppParallel.h>
using namespace Rcpp;
using namespace std;
using namespace arma;
using namespace RcppParallel;

struct Boot_Worker : public Worker {

  //Generate Inputs
  // Source vector to keep track of the number of bootstraps
  const arma::vec Boot_reps;

  // Initial non-linear theta parameter values
  const arma::vec init_val;

  // Decimal date vector
  const arma::colvec T_series;

  // Generate the price series observational vector
  const arma::colvec Y_est;
  const arma::colvec Y_res;

  // Generate the optimization constants
  const arma::mat U;
  const arma::colvec C;

  const int N;

  // Generate Output Matrix
  arma::mat Boots_out;

  // Initialize with the proper input and output
  Boot_Worker( const arma::vec Boot_reps, const arma::vec init_val, const arma::colvec T_series, const arma::colvec Y_est, const arma::colvec Y_res, const arma::mat U, const arma::colvec C, const int N, arma::mat Boots_out)
    : Boot_reps(Boot_reps), init_val(init_val), T_series(T_series), Y_est(Y_est), Y_res(Y_res), U(U), C(C), N(N), Boots_out(Boots_out) {}

  void operator()(std::size_t begin, std::size_t end){
    //load necessary stuffs from around

    Rcpp::Environment stats("package:stats");
    Rcpp::Function constrOptim = stats["constrOptim"];
    Rcpp::Function SDK_pred_mad( "SDK_pred_mad");

    arma::mat fake_data(N,2);
    arma::colvec index(N);

    for(unsigned int i = begin; i < end; i ++){

      // Need a nested loop to create and fill the fake data matrix

      arma::vec pool = arma::regspace(0, N-1) ;
      std::random_shuffle(pool.begin(), pool.end());
      for(int k = 0; k <= N-1; k++){
        fake_data(k, 0) = Y_est[k] + Y_res[ pool[k] ];
        fake_data(k, 1) = T_series[k];
      }

      // Call the optimization
      Rcpp::List opt_results = constrOptim(Rcpp::_["theta"]    = init_val,
                                           Rcpp::_["f"]     = SDK_pred_mad,
                                           Rcpp::_["data_in"] = fake_data,
                                           Rcpp::_["grad"] = "NULL",
                                           Rcpp::_["method"] = "Nelder-Mead",
                                           Rcpp::_["ui"] = U,
                                           Rcpp::_["ci"] = C );

      /// fill the output matrix ///

      // need to create an place holder arma vector for the parameter output
      arma::vec opt_param = Rcpp::as<arma::vec>(opt_results[0]);
      Boots_out(i, 0) = opt_param[0];
      Boots_out(i, 1) = opt_param[1];
      Boots_out(i, 2) = opt_param[2];
      // for the cost function value at optimization
      arma::vec opt_value = Rcpp::as<arma::vec>(opt_results[1]);
      Boots_out(i, 3) = opt_value[0];
      // for the number of function calls (?)
      arma::vec counts = Rcpp::as<arma::vec>(opt_results[2]);
      Boots_out(i, 4) = counts[0];
      // for thhe convergence code
      arma::vec convergence = Rcpp::as<arma::vec>(opt_results[3]);
      Boots_out(i, 5) = convergence[0];

    }

  }

};

// [[Rcpp::export]]
arma::mat SDK_boots_test(arma::vec init_val, arma::mat data_in, int boots_n){

  //First establish theta_sp, estimate and residuals
  const int N = arma::size(data_in)[0];

  // Create the constraints for the constrained optimization
  // Make a boundry boundry condition matrix of the form Ui*theta - ci >= 0
  arma::mat U(6, 3);

  U(0, 0) = 1;
  U(1, 0) = -1;
  U(2, 0) = 0;
  U(3, 0) = 0;
  U(4, 0) = 0;
  U(5, 0) = 0;

  U(0, 1) = 0;
  U(1, 1) = 0;
  U(2, 1) = 1;
  U(3, 1) = -1;
  U(4, 1) = 0;
  U(5, 1) = 0;

  U(0, 2) = 0;
  U(1, 2) = 0;
  U(2, 2) = 0;
  U(3, 2) = 0;
  U(4, 2) = 1;
  U(5, 2) = -1;

  arma::colvec C(6);
  C[0] = 0;
  C[1] =  -data_in(N-1, 9)-0.5;
  C[2] = 0;
  C[3] = -3;
  C[4] = 0;
  C[5] = -50;

  Rcpp::Function SDK_est( "SDK_est");
  Rcpp::Function SDK_res( "SDK_res");

  arma::vec Y_est = as<arma::vec>(SDK_est(init_val, data_in));
  arma::vec Y_res = as<arma::vec>(SDK_res(init_val, data_in));

  // Generate feed items for the Bootstrap Worker
  arma::vec T_series = data_in( span(0, N-1), 9);

  arma::vec Boots_reps(boots_n+1);

  // Allocate the output matrix
  arma::mat Boots_out(boots_n, 6);

  // Pass input and output the Bootstrap Worker
  Boot_Worker Boot_Worker(Boots_reps, init_val, T_series, Y_est, Y_res, U, C, N, Boots_out);

  // Now finnaly call the parallel for loop
  parallelFor(0, Boots_reps.size(), Boot_Worker);

  return Boots_out;
}

Итак, я написал в своем «алгоритме нагрева», чтобы решить оптимизацию, это полностью в Rcpp-armadillo, это значительно упрощает код, так как ограничения записываются в оптимизатор. Кроме того, я удалил рандомизацию, поэтому она просто должна решить ту же оптимизацию; просто чтобы увидеть, была ли это единственная проблема. Безусловно, у меня все та же «фатальная ошибка».

в таком виде код:

// [[Rcpp::depends("RcppArmadillo")]]
// [[Rcpp::depends("RcppParallel")]]
#include <RcppArmadillo.h>
#include <RcppParallel.h>
#include <random>
using namespace Rcpp;
using namespace std;
using namespace arma;
using namespace RcppParallel;

struct Boot_Worker : public Worker {

  //Generate Inputs
  // Source vector to keep track of the number of bootstraps
  const arma::vec Boot_reps;

  // Initial non-linear theta parameter values
  const arma::vec init_val;

  // Decimal date vector
  const arma::colvec T_series;

  // Generate the price series observational vector
  const arma::colvec Y_est;
  const arma::colvec Y_res;

  const int N;

  // Generate Output Matrix
  arma::mat Boots_out;

  // Initialize with the proper input and output
  Boot_Worker( const arma::vec Boot_reps, const arma::vec init_val, const arma::colvec T_series, const arma::colvec Y_est, const arma::colvec Y_res, const int N, arma::mat Boots_out)
    : Boot_reps(Boot_reps), init_val(init_val), T_series(T_series), Y_est(Y_est), Y_res(Y_res), N(N), Boots_out(Boots_out) {}

  void operator()(std::size_t begin, std::size_t end){
    //load necessary stuffs from around

    Rcpp::Function SDK_heat( "SDK_heat");

    arma::mat fake_data(N,2);
    arma::colvec index(N);

    for(unsigned int i = begin; i < end; i ++){

      // Need a nested loop to create and fill the fake data matrix

      //arma::vec pool = arma::shuffle( arma::regspace(0, N-1) );

      for(int k = 0; k <= N-1; k++){
        fake_data(k, 0) = Y_est[k] + Y_res[ k ];
        //fake_data(k, 0) = Y_est[k] + Y_res[ pool[k] ];
        fake_data(k, 1) = T_series[k];
      }

      // Call the optimization

      arma::vec opt_results = Rcpp::as<arma::vec>(  SDK_heat(Rcpp::_["data_in"]    = fake_data, Rcpp::_["tol"]     = 0.1) );


      /// fill the output matrix ///

      // need to create an place holder arma vector for the parameter output
      Boots_out(i, 0) = opt_results[0];
      Boots_out(i, 1) = opt_results[1];
      Boots_out(i, 2) = opt_results[2];
      // for the cost function value at optimization
      Boots_out(i, 3) = opt_results[3];

    }

  }

};

// [[Rcpp::export]]
arma::mat SDK_boots_test(arma::vec init_val, arma::mat data_in, int boots_n){

  //First establish theta_sp, estimate and residuals
  const int N = arma::size(data_in)[0];


  Rcpp::Function SDK_est( "SDK_est");
  Rcpp::Function SDK_res( "SDK_res");

  const arma::vec Y_est = as<arma::vec>(SDK_est(init_val, data_in));
  const arma::vec Y_res = as<arma::vec>(SDK_res(init_val, data_in));

  // Generate feed items for the Bootstrap Worker
  const arma::vec T_series = data_in( span(0, N-1), 9);

  arma::vec Boots_reps(boots_n+1);

  // Allocate the output matrix
  arma::mat Boots_out(boots_n, 4);

  // Pass input and output the Bootstrap Worker
  Boot_Worker Boot_Worker(Boots_reps, init_val, T_series, Y_est, Y_res, N, Boots_out);

  // Now finnaly call the parallel for loop
  parallelFor(0, Boots_reps.size(), Boot_Worker);

  return Boots_out;
}

1 Ответ

0 голосов
/ 05 июля 2018

Глядя на ваш код, я вижу следующее:

struct Boot_Worker : public Worker {
  [...]      
  void operator()(std::size_t begin, std::size_t end){
    //load necessary stuffs from around

    Rcpp::Environment stats("package:stats");
    Rcpp::Function constrOptim = stats["constrOptim"];
    Rcpp::Function SDK_pred_mad( "SDK_pred_mad");

    [...]

      // Call the optimization
      Rcpp::List opt_results = constrOptim(Rcpp::_["theta"]    = init_val,
                                           Rcpp::_["f"]     = SDK_pred_mad,
                                           Rcpp::_["data_in"] = fake_data,
                                           Rcpp::_["grad"] = "NULL",
                                           Rcpp::_["method"] = "Nelder-Mead",
                                           Rcpp::_["ui"] = U,
                                           Rcpp::_["ci"] = C );

Вы вызываете функцию R из многопоточного контекста C ++. Это то, что вы не должны делать . R является однопоточным, так что это приведет к неопределенному поведению или сбоям:

Ограничения API

Код, который вы пишете в параллельных рабочих, не должен вызывать R или Rcpp API каким-либо образом. Это связано с тем, что R является однопоточным, и одновременное взаимодействие с его структурами данных может вызвать сбои и другое неопределенное поведение. Вот официальное руководство от Writing R Extensions :

Вызов любого из API R из многопоточного кода "только для экспертов": им нужно будет прочитать исходный код, чтобы определить, является ли он потокобезопасным. В частности, код, использующий механизм проверки стека, не должен вызываться из многопоточного кода.

Кроме того, обратный вызов R из C ++ даже в однопотоковом контексте - не лучшее, что вы можете сделать для повышения производительности. Должно быть более эффективно использовать библиотеку оптимизации, которая предлагает прямой интерфейс C (++). Одной из возможностей может быть разрабатываемая версия nlopt, c.f. этот вопрос для обсуждения и ссылок на примеры. Кроме того, std::random_shuffle не только устарел в C ++ 14 и удален из C ++ 17, но также не является поточно-ориентированным .

Во втором примере вы говорите, что функция SDK_heat фактически реализована в C ++. В этом случае вы можете позвонить напрямую:

  • Удалить импорт соответствующей функции R, то есть Rcpp::Function SDK_heat( "SDK_heat");
  • Убедитесь, что компилятор знает объявление функции C ++ и что компоновщик имеет фактическую функцию:
    • Быстро и грязно: скопируйте определение функции в файл cpp перед определением BootWorker.
    • Более понятный подход см. В разделе «1.10 Обмен кодом» в виньетка с атрибутами Rcpp
  • Вызовите функцию, как любую другую функцию C ++, т. Е. Используйте позиционные аргументы с типами, совместимыми с объявлением функции.

Все это предполагает, что вы используете sourceCpp, как указано при использовании [[Rcpp::depends(...)]]. Вы достигаете сложности, которая гарантирует создание пакета из этого.

...