Assuming we only instantiate less than 20 objects of class Blob and regarding efficiency (time execution) and memory management issues, is there a best option between:
Setting the random generator and the generated distributions as private class members such as:
class Blob {
private:
std::mt19937 engine;
std::uniform_real_distribution<double> R_distribution;
std::binomial_distribution<int> B_distribution;
}
and using them directly in Blob methods. Thus when we call a distribution, we also alter the state of the engine that is a member.
Or setting the random generator as a private class members and passing the distributions by reference to the methods? For instance:
class Blob {
private:
std::mt19937 engine; //engine
}
void Blob::run() {
int blabla = 10;
std::uniform_real_distribution<double> R_distribution(0, 10);
do_something(blabla, R_distribution);
...
}
While passing by reference induce lower overhead in general, does it matter in that case in particular? How does the overall question scale when calling the distributions a huge number of times (10^9 or more)?
Distributions are cheap and can be created/tossed away willy-nilly. Engines are not. Ideally, you should only initialize your PRNG once and ensure it is thread_local
if your program is multithreaded. PRNG's like std::mt19937
are bulky and have a large internal state. Consider doing something like this:
inline auto global_rng() -> std::mt19937& {
thread_local std::mt19937 e{ get_seed() };
return e;
}
void foo() {
thread_local std::uniform_real_distribution<double> d;
// ...
}
A while back I had made a set of wrapper classes to enclose most of the std's random number generators, engines, seed types and distributions to work together seamlessly. You are free to use this class and can modify it to fit your own needs if so desired. Here is the header only class and all functions are declared as static. The constructors are protected default. You can not create an instance of these classes. There are 2 classes: RandomEngine
and RandomDistribution
. To make life a little easier after the two classes I've created 2 typedefs
to shorten the amount of typing while using them, RE
& RD
respectively. There are a few sets of enums
in these classes, only one of them is directly used, the other 2 are there just for visual reference but the user can use them if needed. Here are the classes in a header only file.
RandomGenerator.h
#ifndef RANDOM_GENERATOR_H
#define RANDOM_GENERATOR_H
#include <limits>
#include <chrono>
#include <random>
// ----------------------------------------------------------------------------
// Class RandomEngine { typedef = RE }
class RandomEngine {
public:
using Clock = std::conditional_t<std::chrono::high_resolution_clock::is_steady,
std::chrono::high_resolution_clock,
std::chrono::steady_clock>;
// Used To Determine Which Seeding Process To Use
enum SeedType {
USE_CHRONO_CLOCK,
USE_RANDOM_DEVICE,
USE_SEED_VALUE,
USE_SEED_SEQ,
}; // SeedType
// This Enum Is Not In Use - It Is A Visual Reference Only; But If User Wants To
// Use It For Their Own Pupose They Are Free To Do So.
enum EngineType {
// Default Random Engine
DEFAULT_RANDOM_ENGINE,
// Linear Congruential Engines
MINSTD_RAND0,
MINSTD_RAND,
// Mersenne Twister Engines
MT19937,
MT19937_64,
// Subtract With Carry Engines
RANLUX24_BASE,
RANLUX48_BASE,
// Discard Block Engines
RANLUX24,
RANLUX48,
// Shuffle Order Engines
KNUTH_B,
}; // EngineType
protected:
RandomEngine() = default;
// Internal Helper Function
// ---------------------------------------------------------------------------
// getRandomDevice()
static std::random_device& getRandomDevice() {
static std::random_device device{};
return device;
} // getRandomDevice
public:
// ---------------------------------------------------------------------------
// getTimeNow()
static unsigned int getTimeNow() {
unsigned int now = static_cast<unsigned int>(Clock::now().time_since_epoch().count());
return now;
} // getTimeNow
// ---------------------------------------------------------------------------
// getDefaultRandomEngine()
static std::default_random_engine& getDefaultRandomEngine( SeedType type, unsigned seedValue = 0, std::seed_seq& seq = std::seed_seq{} ) {
static std::default_random_engine engine{};
switch ( type ) {
case USE_CHRONO_CLOCK: {
engine.seed( getTimeNow() );
break;
}
case USE_SEED_VALUE: {
engine.seed( seedValue );
break;
}
case USE_SEED_SEQ: {
engine.seed( seq );
break;
}
default: {
engine.seed( getRandomDevice()() );
break;
}
}
return engine;
} // getDefaultRandomEngine
// ---------------------------------------------------------------------------
// getMinStd_Rand0()
static std::minstd_rand0& getMinStd_Rand0( SeedType type, unsigned seedValue = 0, std::seed_seq& seq = std::seed_seq{} ) {
static std::minstd_rand0 engine{};
switch ( type ) {
case USE_CHRONO_CLOCK: {
engine.seed( getTimeNow() );
break;
}
case USE_SEED_VALUE: {
engine.seed( seedValue );
break;
}
case USE_SEED_SEQ: {
engine.seed( seq );
break;
}
default: {
engine.seed( getRandomDevice()() );
break;
}
}
return engine;
} // getMinStd_Rand0
// ---------------------------------------------------------------------------
// getMinStd_Rand()
static std::minstd_rand& getMinStd_Rand( SeedType type, unsigned seedValue = 0, std::seed_seq& seq = std::seed_seq{} ) {
static std::minstd_rand engine{};
switch( type ) {
case USE_CHRONO_CLOCK: {
engine.seed( getTimeNow() );
break;
}
case USE_SEED_VALUE: {
engine.seed( seedValue );
break;
}
case USE_SEED_SEQ: {
engine.seed(seq);
break;
}
default: {
engine.seed( getRandomDevice()() );
break;
}
}
return engine;
} // getMinStd_Rand
// ---------------------------------------------------------------------------
// getMt19937()
static std::mt19937& getMt19937( SeedType type, unsigned seedValue = 0, std::seed_seq& seq = std::seed_seq{} ) {
static std::mt19937 engine{};
switch ( type ) {
case USE_CHRONO_CLOCK: {
engine.seed( getTimeNow() );
break;
}
case USE_SEED_VALUE: {
engine.seed( seedValue );
break;
}
case USE_SEED_SEQ: {
engine.seed( seq );
break;
}
default: {
engine.seed( getRandomDevice()() );
break;
}
}
return engine;
} //getMt19937
// ---------------------------------------------------------------------------
// getMt19937_64()
static std::mt19937_64& getMt19937_64( SeedType type, unsigned seedValue = 0, std::seed_seq& seq = std::seed_seq{} ) {
static std::mt19937_64 engine{};
switch ( type ) {
case USE_CHRONO_CLOCK: {
engine.seed( getTimeNow() );
break;
}
case USE_SEED_VALUE: {
engine.seed( seedValue );
break;
}
case USE_SEED_SEQ: {
engine.seed( seq );
break;
}
default: {
engine.seed( getRandomDevice()() );
break;
}
}
return engine;
} // getMt19937_64
// ---------------------------------------------------------------------------
// getRanLux24_base()
static std::ranlux24_base& getRanLux24_base( SeedType type, unsigned seedValue = 0, std::seed_seq& seq = std::seed_seq{} ) {
static std::ranlux24_base engine{};
switch ( type ) {
case USE_CHRONO_CLOCK: {
engine.seed( getTimeNow() );
break;
}
case USE_SEED_VALUE: {
engine.seed( seedValue );
break;
}
case USE_SEED_SEQ: {
engine.seed( seq );
break;
}
default: {
engine.seed( getRandomDevice()() );
break;
}
}
return engine;
} // getRanLux24_base
// ---------------------------------------------------------------------------
// getRanLux48_base()
static std::ranlux48_base& getRanLux48_base( SeedType type, unsigned seedValue = 0, std::seed_seq& seq = std::seed_seq{} ) {
static std::ranlux48_base engine{};
switch ( type ) {
case USE_CHRONO_CLOCK: {
engine.seed( getTimeNow() );
break;
}
case USE_SEED_VALUE: {
engine.seed( seedValue );
break;
}
case USE_SEED_SEQ: {
engine.seed( seq );
break;
}
default: {
engine.seed( getRandomDevice()() );
break;
}
}
return engine;
} // getRanLux48_base
// ---------------------------------------------------------------------------
// getRanLux24()
static std::ranlux24& getRanLux24( SeedType type, unsigned seedValue = 0, std::seed_seq& seq = std::seed_seq{} ) {
static std::ranlux24 engine{};
switch ( type ) {
case USE_CHRONO_CLOCK: {
engine.seed( getTimeNow() );
break;
}
case USE_SEED_VALUE: {
engine.seed( seedValue );
break;
}
case USE_SEED_SEQ: {
engine.seed( seq );
break;
}
default: {
engine.seed( getRandomDevice()() );
break;
}
}
return engine;
} // getRanLux24
// ---------------------------------------------------------------------------
// getRanLux48()
static std::ranlux48& getRanLux48( SeedType type, unsigned seedValue = 0, std::seed_seq& seq = std::seed_seq{} ) {
static std::ranlux48 engine{};
switch ( type ) {
case USE_CHRONO_CLOCK: {
engine.seed( getTimeNow() );
break;
}
case USE_SEED_VALUE: {
engine.seed( seedValue );
break;
}
case USE_SEED_SEQ: {
engine.seed( seq );
break;
}
default: {
engine.seed( getRandomDevice()() );
break;
}
}
return engine;
} //getRanLux48
private:
}; // RandomEngine
// ----------------------------------------------------------------------------
// Class - RandomDistrubtion { typedef = RD }
class RandomDistribution {
public:
// This Enum Is Not In Use - It Is A Visual Reference Only; But If User Wants To
// Use It For Their Own Pupose They Are Free To Do So.
enum DistributionType {
// Uniform Distributions
UNIFORM_INT,
UNIFORM_INT_DISTRIBUTION,
UNIFORM_REAL,
UNIFORM_REAL_DISTRIBUTION,
// GENERATE_CANONICAL, - This is a function template and not a class template use it directly form std:: <random> c++11
// Bernoulli Distributions
BERNOULLI_DISTRIBUTION,
BINOMAIL_DISTRIBUTION,
NEGATIVE_BINOMIAL_DISTRIBUTION,
GEOMETRIC_DISTRIBUTION,
// Poisson Distributions
POISSON_DISTRIBUTION,
EXPONENTIAL_DISTRIBUTION,
GAMMA_DISTRIBUTION,
WEIBULL_DISTRIBUTION,
EXTREME_VALUE_DISTRIBUTION,
// Normal Distributions
NORMAL_DISTRIBUTION,
LOGNORMAL_DISTRIBUTION,
CHI_SQUARED_DISTRIBUTION,
CAUCHY_DISTRIBUTION,
FISHER_F_DISTRIBUTION,
STUDENT_T_DISTRIBUTION,
// Sampling Distributions
DISCRETE_DISTRIBUTION,
PIECEWISE_CONSTANT_DISTRIBUTION,
PIECEWISE_LINEAR_DISTRIBUTION
}; // DistributionType
protected:
RandomDistribution() = default;
public:
// UNIFORM DISTRIBUTIONS
// ---------------------------------------------------------------------------
// getUniformIntDistribution()
template<class IntType = int>
static std::uniform_int_distribution<IntType>& getUniformIntDistribution( IntType lowerBound = 0, IntType upperBound = (std::numeric_limits<IntType>::max)() ) {
static std::uniform_int_distribution<IntType> dist( lowerBound, upperBound );
return dist;
} // getUniformIntDistribution
// ---------------------------------------------------------------------------
// getUniformRealDistribution()
template<class RealType = double>
static std::uniform_real_distribution<RealType>& getUniformRealDistribution( RealType lowerBound = 0.0, RealType upperBound = 1.0 ) {
static std::uniform_real_distribution<RealType> dist( lowerBound, upperBound );
return dist;
} // getUniformRealDistribution
// BERNOULLI DISTRIBUTIONS
// ---------------------------------------------------------------------------
// getBernoulliDistribution()
static std::bernoulli_distribution& getBernoulliDistribution( double probability = 0.5 ) {
static std::bernoulli_distribution dist( probability );
return dist;
} // getBernoulliDistribution
// ---------------------------------------------------------------------------
// getBinomialDistribution()
template<class IntType = int>
static std::binomial_distribution<IntType>& getBinomialDistribution( IntType numTrials = 1, double probability = 0.5 ) {
static std::binomial_distribution<IntType> dist( numTrials, probability );
return dist;
} // getBinomialDistribution
// ---------------------------------------------------------------------------
// getNegativeBinomialDistribution()
template<class IntType = int>
static std::negative_binomial_distribution<IntType>& getNegativeBinomialDistribution( IntType numTrialFailures = 1, double probability = 0.5 ) {
static std::negative_binomial_distribution<IntType> dist( numTrialFailures, probability );
return dist;
} // getNegativeBinomialDistribution
// ---------------------------------------------------------------------------
// getGeometricDistribution()
template<class IntType = int>
static std::geometric_distribution<IntType>& getGeometricDistribution( double probability = 0.5 ) {
static std::geometric_distribution<IntType> dist( probability );
return dist;
} // getGeometricDistribution
// POISSON DISTRIBUTIONS
// ---------------------------------------------------------------------------
// getPoissonDistribution()
template<class IntType = int>
static std::poisson_distribution<IntType>& getPoissonDistribution( double mean = 1.0 ) {
static std::poisson_distribution<IntType> dist( mean );
return dist;
} // getPoissonDistribution
// ---------------------------------------------------------------------------
// getExponentialDistribution()
template<class RealType = double>
static std::exponential_distribution<RealType>& getExponentialDistribution( RealType rate = 1.0 ) {
static std::exponential_distribution<RealType> dist( rate );
return dist;
} // getExponentialDistribution
// ---------------------------------------------------------------------------
// getGammDistribution()
template<class RealType = double>
static std::gamma_distribution<RealType>& getGammaDistribution( RealType alpha_shape = 1.0, RealType beta_scale = 1.0 ) {
static std::gamma_distribution<RealType> dist( alpha_shape, beta_scale );
return dist;
} // getGammaDistribution
// ---------------------------------------------------------------------------
// getWeibullDistribution()
template<class RealType = double>
static std::weibull_distribution<RealType>& getWeibullDistribution( RealType alpha_shape = 1.0, RealType beta_scale = 1.0 ) {
static std::weibull_distribution<RealType> dist( alpha_shape, beta_scale );
return dist;
} // getWeibullDistribution
// ---------------------------------------------------------------------------
// getExtremeValueDistribution()
template<class RealType = double>
static std::extreme_value_distribution<RealType>& getExtremeValueDistribution( RealType location = 0.0, RealType scale = 1.0 ) {
static std::extreme_value_distribution<RealType> dist( location, scale );
return dist;
} // getExtremeValueDistribution
// NORMAL DISTRIBUTIONS
// ---------------------------------------------------------------------------
// getNormalDistribution()
template<class RealType = double>
static std::normal_distribution<RealType>& getNormalDistribution( RealType mean = 0.0, RealType stddev = 1.0 ) {
static std::normal_distribution<RealType> dist( mean, stddev );
return dist;
} // getNormaDistribution
// ---------------------------------------------------------------------------
// getLogNormalDistribution()
template<class RealType = double>
static std::lognormal_distribution<RealType>& getLogNormalDistribution( RealType logScale = 0.0, RealType shape = 1.0 ) {
static std::lognormal_distribution<RealType> dist( logScale, shape );
return dist;
} // getLogNormalDistribution
// ---------------------------------------------------------------------------
// getChiSquaredDistribution()
template<class RealType = double>
static std::chi_squared_distribution<RealType>& getChiSquaredDistribution( RealType degreesOfFreedom = 1.0 ) {
static std::chi_squared_distribution<RealType> dist( degreesOfFreedom );
return dist;
} // getChiSquaredDistribution
// ---------------------------------------------------------------------------
// getCauchyDistribution()
template<class RealType = double>
static std::cauchy_distribution<RealType>& getCauchyDistribution( RealType location = 0.0, RealType scale = 1.0 ) {
static std::cauchy_distribution<RealType> dist( location, scale );
return dist;
} // getCauchyDistribution
// ---------------------------------------------------------------------------
// getFisherFDistribution() Both m,n are degress of freedom
template<class RealType = double>
static std::fisher_f_distribution<RealType>& getFisherFDistribution( RealType m = 1.0, RealType n = 1.0 ) {
static std::fisher_f_distribution<RealType> dist( m, n );
return dist;
} // getFisherFDistribution
// ---------------------------------------------------------------------------
// getStudentTDistribution()
template<class RealType = double>
static std::student_t_distribution<RealType>& getStudentTDistribution( RealType degreesOfFreedom = 1.0 ) {
static std::student_t_distribution<RealType> dist( degreesOfFreedom );
return dist;
} // getStudentTDistribution
// SAMPLING DISTRIBUTIONS
// ---------------------------------------------------------------------------
// getDiscreteDistribution()
template<class IntType = int>
static std::discrete_distribution<IntType>& getDiscreteDistribution() {
static std::discrete_distribution<IntType> dist;
return dist;
} // getDiscreteDistribution
// ---------------------------------------------------------------------------
// getDiscreteDistribution()
template<class IntType = int, class InputIt>
static std::discrete_distribution<IntType>& getDiscreteDistribution( InputIt first, InputIt last ) {
static std::discrete_distribution<IntType> dist( first, last );
return dist;
} // getDiscreteDistribution
// ---------------------------------------------------------------------------
// getDiscreteDistribution()
template<class IntType = int>
static std::discrete_distribution<IntType>& getDiscreteDistribution( std::initializer_list<double> weights ) {
static std::discrete_distribution<IntType> dist( weights );
return dist;
} // getDiscreteDistribution
// ---------------------------------------------------------------------------
// getDiscreteDistribution()
template<class IntType = int, class UnaryOperation>
static std::discrete_distribution<IntType>& getDiscreteDistribution( std::size_t count, double xmin, double xmax, UnaryOperation unary_op ) {
static std::discrete_distribution<IntType> dist( count, xmin, xmax, unary_op );
return dist;
} // getDiscreteDistribution
// ---------------------------------------------------------------------------
// getPiecewiseConstantDistribution()
template<class RealType = double>
static std::piecewise_constant_distribution<RealType>& getPiecewiseConstantDistribution() {
static std::piecewise_constant_distribution<RealType> dist;
return dist;
} // getPiecewiseConstantDistribution
// ---------------------------------------------------------------------------
// getPiecewiseConstantDistribution()
template<class RealType = double, class InputIt1, class InputIt2>
static std::piecewise_constant_distribution<RealType>& getPiecewiseConstantDistribution( InputIt1 first_i, InputIt1 last_i, InputIt2 first_w ) {
static std::piecewise_constant_distribution<RealType> dist( first_i, last_i, first_w );
return dist;
} // getPiecewiseConstantDistribution
// ---------------------------------------------------------------------------
// getPiecewiseConstantDistribution()
template<class RealType = double, class UnaryOperation>
static std::piecewise_constant_distribution<RealType>& getPiecewiseConstantDistribution( std::initializer_list<RealType> bl, UnaryOperation fw ) {
static std::piecewise_constant_distribution<RealType> dist( bl, fw );
return dist;
} // getPiecewiseConstantDistribution
// ---------------------------------------------------------------------------
// getPiecewiseConstantDistribution()
template<class RealType = double, class UnaryOperation>
static std::piecewise_constant_distribution<RealType>& getPiecewiseConstantDistribution( std::size_t nw, RealType xmin, RealType xmax, UnaryOperation fw ) {
static std::piecewise_constant_distribution<RealType> dist( nw, xmin, xmax, fw );
return dist;
} // getPiecewiseConstantDistribution
// ---------------------------------------------------------------------------
// getPiecewiseLinearDistribution()
template<class RealType = double>
static std::piecewise_linear_distribution<RealType>& getPiecewiseLinearDistribution() {
static std::piecewise_linear_distribution<RealType> dist;
return dist;
} // getPiecewiseLinearDistribution
// ---------------------------------------------------------------------------
// getPiecewiseLinearDistribution()
template<class RealType = double, class InputIt1, class InputIt2>
static std::piecewise_linear_distribution<RealType>& getPiecewiseLinearDistribution( InputIt1 first_i, InputIt1 last_i, InputIt2 first_w ) {
static std::piecewise_linear_distribution<RealType> dist( first_i, last_i, first_w );
return dist;
} // getPiecewiseLinearDistribution
// ---------------------------------------------------------------------------
// getPiecewiseLinearDistribution()
template<class RealType = double, class UnaryOperation>
static std::piecewise_linear_distribution<RealType>& getPiecewiseLinearDistribution( std::initializer_list<RealType> bl, UnaryOperation fw ) {
static std::piecewise_linear_distribution<RealType> dist( bl, fw );
return dist;
} // getPiecewiseLinearDistribution
// ---------------------------------------------------------------------------
// getPiecewiseLinearDistribution()
template<class RealType = double, class UnaryOperation>
static std::piecewise_linear_distribution<RealType>& getPiecewiseLinearDistribution( std::size_t nw, RealType xmin, RealType xmax, UnaryOperation fw ) {
static std::piecewise_linear_distribution<RealType> dist( nw, xmin, xmax, fw );
return dist;
} // getPiecewiseLinearDistribution
}; // RandomDistribution
typedef RandomEngine RE;
typedef RandomDistribution RD;
#endif // !RANDOM_GENERATOR_H
And one would use this class as such that can be seen in these few samples below.
main.cpp
#include <sstream>
#include <iostream>
#include "RandomGenerator.h"
int main() {
std::ostringstream strStream;
strStream << "Random number generated between [0.0, 1.0] \nusing mersenne & chrono clock for seeding:\n";
std::cout << strStream.str();
std::uniform_real_distribution<double> urd = RD::getUniformRealDistribution<double>( 0.0, 1.0 );
for ( unsigned i = 1; i <= 50; i++ ) {
std::ostringstream strStream;
double val = urd( RE::getMt19937( RE::SeedType::USE_CHRONO_CLOCK, 12 ) );
strStream << i << " : " << val << "\n";
std::cout << strStream.str();
}
std::cout << std::endl;
strStream.clear();
//std::ostringstream strStream;
strStream << "Random number generated Between [1,9] using default random engine & uniform int distribution is: " << std::endl;
std::cout << strStream.str();
std::uniform_int_distribution<unsigned> uid = RD::getUniformIntDistribution<unsigned>( 1, 9 );
// std::uniform_int_distribution<unsigned> uid( 1, 9 );
for ( unsigned int i = 1; i < 101; i++ ) {
std::ostringstream strStream;
unsigned val = uid( RE::getDefaultRandomEngine( RE::SeedType::USE_CHRONO_CLOCK, 14 ) );
strStream << i << " : " << val << std::endl;
std::cout << strStream.str();
}
std::cout << std::endl;
for ( unsigned int i = 1; i < 101; i++ ) {
std::ostringstream strStream;
// Using the same distribution above but reseeding it with a different type of seeding method.
unsigned val = uid( RE::getDefaultRandomEngine( RE::SeedType::USE_RANDOM_DEVICE ) );
strStream << i << " : " << val << std::endl;
std::cout << strStream.str();
}
std::cout << "\nPress any key and enter to quit." << std::endl;
char q;
std::cin >> q;
return 0;
}
These classes will automatically declare, set and call the generators and distributions by using the static methods with the parameters that they require. If you need them to be thread local, it shouldn't be an issue to modify this to your needs.
Typically how I use this is what ever class needs a random distribution I will include this header, I will then have a member variable of a distribution type that I need and I will set it using the desired generator and seeding type mechanism required.
If you have any questions please don't hesitate to ask.