Как упомянуто в комментариях, издержки могут быть проблематичными, особенно когда общее время выполнения короткое, лучше не инициализировать выходные векторы с нулем и использовать потоки локальных RNG. Пример реализации:
#include <Rcpp.h>
// [[Rcpp::plugins("cpp11")]]
// [[Rcpp::depends(RcppParallel)]]
#include <RcppParallel.h>
// [[Rcpp::depends(RcppZiggurat)]]
#include <Ziggurat.h>
using namespace RcppParallel;
struct Norm : public Worker
{
// saved draws
RVector<double> draws;
// constructors
Norm(Rcpp::NumericVector draws)
: draws(draws) {}
void operator()(std::size_t begin, std::size_t end) {
Ziggurat::Ziggurat::Ziggurat zigg(end);
for (std::size_t i = begin; i < end; i++) {
draws[i] = zigg.norm();
}
}
};
// [[Rcpp::export]]
Rcpp::NumericVector parallelDraws(int x) {
// allocate the output vector
Rcpp::NumericVector draws(Rcpp::no_init(x));
Norm norm(draws);
parallelFor(0, x, norm);
return draws;
}
// [[Rcpp::export]]
Rcpp::NumericVector serialDraws(int x) {
// allocate the output vector
Rcpp::NumericVector draws(Rcpp::no_init(x));
Ziggurat::Ziggurat::Ziggurat zigg(42);
for (int i = 0; i < x; i++) {
draws[i] = zigg.norm();
}
return draws;
}
Обратите внимание, что я использую "параллельный RNG бедняка", то есть различные семена для разных потоков, и надеюсь на лучшее. Я использую end
в качестве начального числа, поскольку begin
может быть нулем, и я не уверен, что RNG в RcppZiggurat это нравится. Поскольку для создания Ziggurat
объекта требуется некоторое время (и память), я также использую локальный объект для последовательных вычислений, чтобы быть справедливым.
Для 10 ^ 5 случайных дро все еще нет выгоды от использования параллельных вычислений:
> bench::mark(parallelDraws(1e5), serialDraws(1e5), check = FALSE, min_iterations = 10)[,1:5]
# A tibble: 2 x 5
expression min median `itr/sec` mem_alloc
<bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt>
1 parallelDraws(1e+05) 1.08ms 1.78ms 558. 784KB
2 serialDraws(1e+05) 624.16µs 758.6µs 1315. 784KB
Но за 10 ^ 8 ничьих я получаю хорошее ускорение на моем двухъядерном ноутбуке:
> bench::mark(parallelDraws(1e8), serialDraws(1e8), check = FALSE, min_iterations = 10)[,1:5]
# A tibble: 2 x 5
expression min median `itr/sec` mem_alloc
<bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt>
1 parallelDraws(1e+08) 326ms 343ms 2.91 763MB
2 serialDraws(1e+08) 757ms 770ms 1.30 763MB
Так что имеет ли смысл использовать параллельные вычисления, во многом зависит от того, сколько случайных дро вам нужно.
Кстати, в комментариях упоминается мой пакет dqrng . В этом пакете также используется метод Ziggurat для обычных (и экспоненциальных) отрисовок в сочетании с очень быстрыми 64-битными ГСЧ, что придает ему сопоставимую последовательную скорость с RcppZiggurat для обычных отрисовок. Кроме того, используемые RNG * предназначены для параллельных вычислений , т. Е. Нет необходимости надеяться получить непересекающиеся случайные потоки с использованием различных начальных чисел.