I wrote a class around a UniformRandomBitGenerator
, which in turn also satisfies the concept of a UniformRandomBitGenerator
. It uses a buffer and parallelism (openmp
) to quickly generate large amounts of random numbers.
I'm happy about all kinds of feedback, but I'm most unsure about my use of openmp
and ensuring that the same seed always generates the same sequence of pseudo-random numbers.
Here's the class:
#pragma once
#include <random>
#include <vector>
#include <omp.h>
template<class RNG_type>
class BP_RNG {
public:
using unsigned_t = unsigned;
using result_type = typename RNG_type::result_type;
constexpr static result_type min() { return RNG_type::min(); };
constexpr static result_type max() { return RNG_type::max(); };
BP_RNG(int seed = 42, unsigned_t buffer_size = 1e8, unsigned_t nthreads = 4);
result_type operator()();
private:
using buffer_t = std::vector<result_type>;
using iter_t = typename std::vector<result_type>::iterator;
void refill();
RNG_type engine_;
buffer_t buffer_;
iter_t position_;
const unsigned_t nthreads_;
};
template<class RNG_type>
BP_RNG<RNG_type>::BP_RNG(int seed, unsigned_t buffer_size, unsigned_t nthreads):
engine_(seed),
buffer_(buffer_size),
position_(buffer_.begin()),
nthreads_(nthreads)
{
refill();
}
template<class RNG_type>
void BP_RNG<RNG_type>::refill()
{
// Get seeds for RNGs in parallel section beforehand to ensure determinism
buffer_t seedvals(nthreads_);
for (auto& seed : seedvals) seed = engine_();
#pragma omp parallel num_threads(nthreads_)
{
RNG_type local_engine( seedvals[omp_get_thread_num()] );
#pragma omp for
// Pedestrian vector-iteration so that omp works
for (unsigned_t i = 0; i < buffer_.size(); ++i) buffer_[i] = local_engine();
}
position_ = buffer_.begin();
}
template<class RNG_type>
typename BP_RNG<RNG_type>::result_type BP_RNG<RNG_type>::operator()()
{
if (position_ == buffer_.end()) refill();
return *(position_++);
}
And the main.cpp
I used to test it:
#include <iostream>
#include <chrono>
#include "buffered_parallel_rng.hpp"
template<class RNG>
void time_rng(RNG& eng, unsigned long iter, std::string name)
{
typename RNG::result_type a;
auto start = std::chrono::high_resolution_clock::now();
for (unsigned long i = 0; i < iter; ++i)
{
a = eng();
}
auto stop = std::chrono::high_resolution_clock::now();
double elapsed = static_cast<std::chrono::duration<double> >(stop-start).count();
std::cout << name << " : " << elapsed << "\n";
}
void test_determinism(unsigned ntests)
{
BP_RNG<std::mt19937_64> seeder(42);
for (unsigned j = 0; j < ntests; ++j)
{
int seed = seeder();
BP_RNG<std::mt19937_64> myeng1(seed),myeng2(seed);
for (unsigned i = 0; i < 10; ++i)
{
if (myeng1() != myeng2()) {
std::cout << "Different results from same seed!\n"
<< "Seed is: " << seed << "\n";
return;
}
}
}
}
int main() {
unsigned iter = 1e9;
std::mt19937_64 ref_eng;
ref_eng.seed(100);
BP_RNG<std::mt19937_64> my_eng(100);
time_rng(ref_eng, iter, "Other");
time_rng(my_eng , iter, "Mine ");
test_determinism(100);
}