随机引擎差异

时间:2013-05-14 06:37:57

标签: c++ random c++11

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

显然,这些引擎(至少有一些)的主要好处之一是大大增加了周期长度(它内置于std::mt19937的名称中)。

但是,引擎之间的差异不太明显。不同发动机的优点和缺点是什么?应该何时使用另一个?是否存在通常应该首选的合理默认值?

7 个答案:

答案 0 :(得分:28)

根据下面的解释,线性引擎似乎更快但随机性更小,而Marsenne Twister具有更高的复杂性和随机性。减去携带随机数引擎是对线性引擎的改进,并且它更加随机。在最后的参考文献中,Mersenne Twister的复杂性高于带有减法的随机数引擎

线性同余随机数引擎

伪随机数生成器引擎,用于生成无符号整数。

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

x =(ax + c)mod m

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

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

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

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

Mersenne twister随机数引擎

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

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

引擎具有n个整数元素的内部状态序列,其中填充了构造时生成的伪随机序列或通过调用成员函数种子。

内部状态序列成为n个元素的源:当状态提前时(例如,为了产生新的随机数),引擎通过使用xor mask a扭转当前值来改变状态序列由参数r确定的比特混合,来自该值和m个元素之外的值(有关详细信息,请参阅operator())。

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

mersenne_twister_engine生成的随机数的周期等于mersenne数2 ^((n-1)* w)-1。http://www.cplusplus.com/reference/random/mersenne_twister_engine/

减去携带随机数引擎

伪随机数生成器引擎,用于生成无符号整数。

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

如果使用加法或减法,滞后Fibonacci生成器的最大周期为(2k - 1)* ^(2M-1)。 LFG的初始化是一个非常复杂的问题。 LFG的输出对初始条件非常敏感,并且除非特别小心,否则最初可能出现统计缺陷,但也会在输出序列中周期性地出现统计缺陷。 LFG的另一个潜在问题是它们背后的数学理论是不完整的,因此有必要依靠统计检验而不是理论性能。 http://en.wikipedia.org/wiki/Lagged_Fibonacci_generator

最后: 选择使用哪种引擎涉及许多权衡:线性同余引擎适度快,并且对状态的存储要求非常小。即使在没有高级算术指令集的处理器上,滞后的Fibonacci发生器也非常快,代价是更大的状态存储和有时不太理想的频谱特性。梅森捻线机速度较慢且具有较大的状态存储要求,但正确的参数具有最长的非重复序列,具有最理想的光谱特性(对于给定的理想定义)。在http://en.cppreference.com/w/cpp/numeric/random

答案 1 :(得分:11)

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

  • 期间长度是其中一个属性。
  • 随机数的质量也很重要。
  • 生成器的性能也可能是一个问题。

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

为了帮助您做出选择,有一些标准测试和结果(我非常喜欢this paper的表格第29页)。


编辑:从论文中,

  1. LCG(论文中LCG(***))系列产品是最快的,但质量最差。
  2. Mersenne Twister(MT19937)有点慢,但会产生更好的随机数。
  3. 带进位的减法(SWB(***),我认为)速度较慢,但​​经过充分调整后可以产生更好的随机属性。

答案 2 :(得分:5)

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

https://community.amd.com/thread/139236

  

RANLUX也是极少数(我实际上唯一知道的)PRNG之一,它有一个基础理论解释为什么它会产生“随机”数字,以及为什么它们是好的。事实上,如果理论是正确的(并且我不知道有人对此提出异议),那么最高级别的RANLUX会产生完全相关的数字,直到最后一位,只要我们保持良好状态就没有长距离相关性低于期间(10 ^ 171)。大多数其他发电机的质量很少(如Mersenne Twister,KISS等)。它们必须依靠通过统计测试。

欧洲核子研究中心的物理学家是这个PRNG的粉丝。 '努夫说。

答案 3 :(得分:1)

这些其他答案中的一些信息与我的发现相冲突。我使用Visual Studio 2013在Windows 8.1上运行测试,并且始终发现mersenne_twister_engine质量更高,速度明显快于linear_congruential_enginesubtract_with_carry_engine。这使我相信,当其他答案中的信息被考虑在内时,引擎的具体实现会对性能产生重大影响。

对于没有人来说,这是非常令人惊讶的,我敢肯定,但在其他答案中没有提到mersenne_twister_engine据说速度较慢。我没有其他平台和编译器的测试结果,但在我的配置中,mersenne_twister_engine显然是考虑周期,质量和速度性能的最佳选择。我没有分析内存使用情况,所以我不能说空间要求属性。

以下是我用来测试的代码(为了便于使用,您只需要用适当的计时机制替换windows.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;
}

输出:

Testing with 100000000 samples.
linear_congruential_engine time: 10.0821
   mersenne_twister_engine time: 6.11615
subtract_with_carry_engine time: 9.26676

答案 4 :(得分:1)

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

有些人报告MT比其他人慢。根据我的实验,这很大程度上取决于您的编译器优化设置。最重要的是,-march = native设置可能会产生巨大的差异,具体取决于您的主机架构。

我运行了一个小程序来测试不同发电机的速度及其尺寸,并得到了这个:

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

答案 5 :(得分:0)

真的是一种权衡。类似Mersenne Twister的PRNG更好,因为它具有极大的周期和其他良好的统计特性。

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

根据您的应用需求选择PNRG。如果有疑问,请使用Mersenne Twister,这是许多工具的默认设置。

答案 6 :(得分:0)

我刚看到Marnos的this answer并决定自己测试一下。我使用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秒。

所以,正如他所说,这是具体的实施,但至少在GCC中,平均时间似乎坚持接受的答案中的描述。 Mersenne Twister似乎从优化中受益最少,而其他两个实际上只是在你考虑编译器优化时就会非常快地抛出随机数。

顺便说一句,我一直在我的噪音生成库中使用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;
}