随机引擎的差异

C ++ 11标准为随机数生成指定了许多不同的引擎: linear_congruential_enginemersenne_twister_enginesubtract_with_carry_engine等等。 显然,这是std::rand的旧用法的一个很大的改变。

显然,这些引擎(至less有一些)的主要好处之一就是大大增加了周期长度(它被构build在std::mt19937的名字std::mt19937 )。

但是,引擎之间的差异不太清楚。 不同引擎的优缺点是什么? 什么时候应该使用另一个? 是否有一个明智的默认应该通常是首选?

从下面的解释可以看出,线性引擎似乎更快但更less随机,而Marsenne Twister具有更高的复杂性和随机性。 随机减号随机数引擎是对线性引擎的一种改进,并且更加随意性更强。 在最后的参考文献中指出,Mersenne Twister比具有进位减法的随机数引擎

线性同余随机数引擎

产生无符号整数的伪随机数发生器引擎。

这是标准库中最简单的生成器引擎。 其状态是一个整数值,具有以下转换algorithm:

x =(ax + c)mod m

其中x是当前状态值,a和c是它们各自的模板参数,如果大于0,则m是其相应的模板参数,否则为numerics_limits :: max()加1。

它的生成algorithm是状态值的直接副本。

这使得它在处理和内存消耗方面是一个非常高效的生成器,但是根据所使用的特定参数生成具有不同程度的序列相关性的数字。

linear_congruential_engine生成的随机数的周期为m。 http://www.cplusplus.com/reference/random/linear_congruential_engine/

梅森扭曲者随机数引擎

一个伪随机数发生器引擎,在闭区间[0,2 ^ w-1]中产生无符号整数。

该引擎所使用的algorithm经过优化,可以计算大范围的数字(例如在蒙特卡罗实验中),其范围内的分布几乎是均匀的。

引擎有一个由n个整数元素组成的内部状态序列,这个元素在结构上或通过调用成员函数seed填充了一个伪随机序列。

内部状态序列成为n个元素的源:当状态被提前(例如,为了产生一个新的随机数),引擎通过使用xor掩码a在混合位上扭曲当前值来改变状态序列由来自该值的参数r和来自m个元素的值确定(详见操作符())。

产生的随机数是这些扭曲值的调和版本。 回火是由参数u,d,s,b,t,c和l定义的一系列移位和异或运算,应用于select的状态值(参见operator())。

由mersenne_twister_engine产生的随机数有一个相当于mersenne数2 ^((n-1)* w)-1的周期。 http://www.cplusplus.com/reference/random/mersenne_twister_engine/

随机减号引擎

产生无符号整数的伪随机数发生器引擎。

该引擎使用的algorithm是一个滞后斐波那契发生器,具有r个整数元素的状态序列,加上一个进位值。 http://www.cplusplus.com/reference/random/subtract_with_carry_engine/

如果使用加法或减法,滞后斐波那契发生器的最大周期为(2k-1)* ^(2M-1)。 填埋气体的初始化是一个非常复杂的问题。 LFG的输出对初始条件非常敏感,统计缺陷最初可能出现,但在输出序列中也会周期性地出现,除非采取谨慎措施。 LFG的另一个潜在的问题是背后的math理论是不完整的,因此有必要依靠统计testing而不是理论性能。 http://en.wikipedia.org/wiki/Lagged_Fibonacci_generator

最后:select使用哪种引擎需要进行许多折衷:线性同余引擎速度适中,对状态的存储要求非常小。 即使在没有高级算术指令集的处理器上,滞后的斐波纳契发生器也是非常快的,代价是更大的状态存储和有时不太理想的频谱特性。 梅森扭转器速度较慢,具有较高的状态存储要求,但正确的参数具有最长的非重复序列和最理想的光谱特性(对于给定的期望定义)。 在http://en.cppreference.com/w/cpp/numeric/random

我认为这一点是随机生成器具有不同的属性,可以使它们更适合或不适合于给定的问题。

  • 期间长度是属性之一。
  • 随机数的质量也是重要的。
  • 发电机的性能也是一个问题。

根据您的需要,您可能需要一台发电机或另一台发电机。 例如,如果你需要快速的随机数字,但并不真正关心质量,LCG可能是一个不错的select。 如果你想要更好的质量随机数,Mersenne Twister可能是更好的select。

为了帮助您做出select,有一些标准testing和结果 (我绝对喜欢本文的表格p.29)。


编辑:从纸上,

  1. LCG(纸( LCG(***) )家族是最快的发电机,但质量最差。
  2. Mersenne Twister( MT19937 )稍微慢一些,但是产生更好的随机数。
  3. 带有进位的减法( SWB(***) ,我认为)速度较慢,但​​在调整好的时候可以产生更好的随机特性。

由于其他答案忘记了ranlux,下面是AMD开发人员最近将其移植到OpenCL的一个小logging:

http://devgurus.amd.com/thread/139236

RANLUX也是极less数(我知道的唯一一个)PRNG之一,它有一个基本理论,解释为什么它会产生“随机”数字,以及为什么它们是好的。 事实上,如果理论是正确的(我不知道谁有争议),RANLUX在最高的奢侈品水平产生完全去相关的数字到最后一点,没有长期的相关性,只要我们保持良好低于期限(10 ^ 171)。 大多数其他的发电机可以说他们的质量很less(如梅森捻转,KISS等),他们必须依靠通过统计testing。

CERN的物理学家是这个PRNG的粉丝。 “nuff说。

这些其他答案中的一些信息与我的发现相冲突。 我已经使用Visual Studio 2013在Windows 8.1上运行testing,并且始终如一地发现mersenne_twister_engine质量要高一些,比linear_congruential_enginesubtract_with_carry_engine快得多。 这使我相信,当考虑到其他答案中的信息时,发动机的具体实施对性能有重大影响。

对于没有人来说,这是一个很大的惊喜,我相信,但在mersenne_twister_engine被认为比较慢的其他答案中没有提到。 我没有其他平台和编译器的testing结果,但是对于我的configuration, mersenne_twister_engine显然是考虑周期,质量和速度性能的最佳select。 我没有分析内存使用情况,所以我不能说空间要求属性。

下面是我用来testing的代码(为了便于使用,您只需要用适当的时间机制来replacewindows.h QueryPerformanceXxx() API调用):

 // compile with: cl.exe /EHsc #include <random> #include <iostream> #include <windows.h> using namespace std; void test_lc(const int a, const int b, const int s) { /* typedef linear_congruential_engine<unsigned int, 48271, 0, 2147483647> minstd_rand; */ minstd_rand gen(1729); uniform_int_distribution<> distr(a, b); for (int i = 0; i < s; ++i) { distr(gen); } } void test_mt(const int a, const int b, const int s) { /* typedef mersenne_twister_engine<unsigned int, 32, 624, 397, 31, 0x9908b0df, 11, 0xffffffff, 7, 0x9d2c5680, 15, 0xefc60000, 18, 1812433253> mt19937; */ mt19937 gen(1729); uniform_int_distribution<> distr(a, b); for (int i = 0; i < s; ++i) { distr(gen); } } void test_swc(const int a, const int b, const int s) { /* typedef subtract_with_carry_engine<unsigned int, 24, 10, 24> ranlux24_base; */ ranlux24_base gen(1729); uniform_int_distribution<> distr(a, b); for (int i = 0; i < s; ++i) { distr(gen); } } int main() { int a_dist = 0; int b_dist = 1000; int samples = 100000000; cout << "Testing with " << samples << " samples." << endl; LARGE_INTEGER ElapsedTime; double ElapsedSeconds = 0; LARGE_INTEGER Frequency; QueryPerformanceFrequency(&Frequency); double TickInterval = 1.0 / ((double) Frequency.QuadPart); LARGE_INTEGER StartingTime; LARGE_INTEGER EndingTime; QueryPerformanceCounter(&StartingTime); test_lc(a_dist, b_dist, samples); QueryPerformanceCounter(&EndingTime); ElapsedTime.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart; ElapsedSeconds = ElapsedTime.QuadPart * TickInterval; cout << "linear_congruential_engine time: " << ElapsedSeconds << endl; QueryPerformanceCounter(&StartingTime); test_mt(a_dist, b_dist, samples); QueryPerformanceCounter(&EndingTime); ElapsedTime.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart; ElapsedSeconds = ElapsedTime.QuadPart * TickInterval; cout << " mersenne_twister_engine time: " << ElapsedSeconds << endl; QueryPerformanceCounter(&StartingTime); test_swc(a_dist, b_dist, samples); QueryPerformanceCounter(&EndingTime); ElapsedTime.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart; ElapsedSeconds = ElapsedTime.QuadPart * TickInterval; cout << "subtract_with_carry_engine time: " << ElapsedSeconds << endl; } 

输出:

用100000000个样本进行testing。
 linear_congruential_engine时间:10.0821
    mersenne_twister_engine时间:6.11615
 subtract_with_carry_engine时间:9.26676

它真的是一个折衷。 像Mersenne Twister这样的PRNG更好,因为它具有非常大的周期和其他良好的统计特性。

但是大的时间段PRNG占用更多的内存(用于维护内部状态),并且还需要更多时间来生成随机数(由于复杂的转换和后处理)。

根据您的应用程序的需要select一个PNRG。 如果有疑问,请使用Mersenne Twister ,它是许多工具的默认设置。

我刚刚看到马尔诺斯的这个答案 ,决定自己testing一下。 我使用std::chono::high_resolution_clock来计算100000样本100次以产生一个平均值。 我测量了std::chrono::nanoseconds所有内容,并以不同的结果结束:

std::minstd_rand的平均值为28991658纳秒

std::mt19937平均分为29871710纳秒

ranlux48_base的平均值为29281677纳秒

这是在Windows 7机器上。 编译器是Mingw-Builds 4.8.1 64bit。 这显然是使用C ++ 11标志,没有优化标志。

当我打开-O3优化时, std::minstd_randranlux48_base实际上比high_precision_clock的实现可以测量的速度更快; 但std::mt19937仍然需要730045纳秒,或3/4秒。

所以,正如他所说的那样,它是特定于实现的,但至less在GCC中,平均时间似乎坚持接受答案中的描述。 Mersenne Twister似乎从优化中获益最less,而其他两个人只要考虑到编译器优化,实际上只是快速地抛出随机数字的速度令人难以置信。

顺便说一下,我在我的噪音生成库中使用了Mersenne Twister引擎(它不会预先计算梯度),所以我想我会切换到其中一个来真正看到一些速度的提升。 就我而言,“真正的”随机性并不重要。

码:

 #include <iostream> #include <chrono> #include <random> using namespace std; using namespace std::chrono; int main() { minstd_rand linearCongruentialEngine; mt19937 mersenneTwister; ranlux48_base subtractWithCarry; uniform_real_distribution<float> distro; int numSamples = 100000; int repeats = 100; long long int avgL = 0; long long int avgM = 0; long long int avgS = 0; cout << "results:" << endl; for(int j = 0; j < repeats; ++j) { cout << "start of sequence: " << j << endl; auto start = high_resolution_clock::now(); for(int i = 0; i < numSamples; ++i) distro(linearCongruentialEngine); auto stop = high_resolution_clock::now(); auto L = duration_cast<nanoseconds>(stop-start).count(); avgL += L; cout << "Linear Congruential:\t" << L << endl; start = high_resolution_clock::now(); for(int i = 0; i < numSamples; ++i) distro(mersenneTwister); stop = high_resolution_clock::now(); auto M = duration_cast<nanoseconds>(stop-start).count(); avgM += M; cout << "Mersenne Twister:\t" << M << endl; start = high_resolution_clock::now(); for(int i = 0; i < numSamples; ++i) distro(subtractWithCarry); stop = high_resolution_clock::now(); auto S = duration_cast<nanoseconds>(stop-start).count(); avgS += S; cout << "Subtract With Carry:\t" << S << endl; } cout << setprecision(10) << "\naverage:\nLinear Congruential: " << (long double)(avgL/repeats) << "\nMersenne Twister: " << (long double)(avgM/repeats) << "\nSubtract with Carry: " << (long double)(avgS/repeats) << endl; } 

一般来说,mersenne twister是最好的(也是最快的)RNG,但它需要一些空间(大约2.5千字节)。 哪一个适合你的需要取决于你需要多less次实例化生成器对象。 (如果你只需要实例化一次或几次,那么MT就是一个使用的例子,如果你需要实例化数百万次,那么可能要小一些)

有些人报告MT比其他的慢。 根据我的实验,这取决于你的编译器优化设置。 最重要的是-march =本地设置可能会有很大的不同,这取决于您的主机架构。

我跑了一个小程序来testing不同的发电机的速度和它们的大小,并得到了这个:

 std::mt19937 (2504 bytes): 1.4714 s std::mt19937_64 (2504 bytes): 1.50923 s std::ranlux24 (120 bytes): 16.4865 s std::ranlux48 (120 bytes): 57.7741 s std::minstd_rand (4 bytes): 1.04819 s std::minstd_rand0 (4 bytes): 1.33398 s std::knuth_b (1032 bytes): 1.42746 s