随机引擎的差异(Random Engine Differences)

2019-09-03 01:45发布

在C ++ 11标准规定一个数用于随机数生成不同的引擎的: linear_congruential_enginemersenne_twister_enginesubtract_with_carry_engine等。 显然,这是从旧的使用变化较大std::rand

显然,这些发动机的主要好处(至少部分)一个是大量增加的周期长度(它的建成为名称std::mt19937 )。

然而,引擎间的差别还不太清楚。 有哪些不同引擎的优势和劣势? 当应了另一种使用吗? 是否有一个合理的默认即一般应首选?

Answer 1:

从下面的说明,线性发动机似乎是快,但不能随意而Marsenne倍捻机具有较高的复杂性和随机性。 减去与 - 携带随机数引擎是一种改进的线性发动机,它是definitelly更随机。 在最后一个引用,更说明梅森倍捻机具有高于复杂减法,用携带的随机数引擎

线性同余随机数引擎

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

这是在标准库中最简单的生成引擎。 它的状态是一个整数值,用下面的过渡算法:

X =(AX + C)模m

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

它的生成算法的状态值直接拷贝。

这使得在处理和存储器消耗方面极其有效的发电机,但具有不同程度的序列相关性的,这取决于所使用的特定参数产生数字。

通过linear_congruential_engine产生的随机数有一段米。 http://www.cplusplus.com/reference/random/linear_congruential_engine/

梅森倍捻机随机数引擎

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

由该发动机所使用的算法进行了优化,以计算大系列号码(如在蒙特卡洛实验)与在范围内的几乎均匀的分布。

该发动机具有n个整数元素,其填充有在建筑或通过调用成员函数种子产生的伪随机序列的内部状态序列。

内部状态序列变成为n个元素的来源:当状态是高级(例如,为了产生一个新的随机数),所述发动机通过使用异或掩蔽的比特的混合扭转的电流值改变的状态序列通过参数R来从该值和由值m元件远确定(见操作者()的详细信息)。

产生的随机数是这些扭曲的价值观的强化版本。 回火是通过参数U,d,S,B,T,C和L施加在所选择的状态值限定移位和XOR操作的序列(见运算符())。

通过mersenne_twister_engine产生的随机数的周期相当于梅森数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/

滞后斐波纳契发电机具有的第(2k - 1)的最大周期* ^(2M-1),如果使用加法或减法。 LFGs的初始化是一个非常复杂的问题。 LFGs的输出是对初始条件非常敏感,并且除非极端小心统计缺陷可能最初也周期性地出现在输出序列。 与LFGs另一个潜在的问题是,他们背后的数学理论是不完整的,因此有必要依靠统计检验,而不是理论性能。 http://en.wikipedia.org/wiki/Lagged_Fibonacci_generator

最后:选择该引擎使用的涉及多个权衡:线性同余发动机是适度快速和有状态的非常小的存储需求。 滞后的斐波那契数生成器的速度非常快,即使在没有先进的运算指令集的处理器,在更大的固态存储的费用,有时不太理想的光谱特性。 Mersenne扭曲较慢且具有更大的状态存储要求,但是具有正确的参数具有最期望的光谱特性的最长非重复序列(对于期望在给定的定义)。 在http://en.cppreference.com/w/cpp/numeric/random



Answer 2:

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

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

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

为了帮助你做出选择,也有一些标准测试和结果 (我绝对喜欢的表第29页文章 )。


编辑:从纸张,

  1. 该LCG( LCG(***)的文件)的家庭是最快的发电机,但质量最差。
  2. 梅森倍捻机( MT19937 )是有点慢,但产生更好的随机数。
  3. 与携带。减去( SWB(***)我认为)的方法要慢,但当以及调整可以产生更好的随机特性。


Answer 3:

至于其他的答案忘记ranlux,这里是由AMD开发者,最近它移植到OpenCL的一张小纸条:

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

RANLUX也是一个极少数(只有一个我知道实际上的)的PRNG具有基本的理论解释了为什么它会产生“随机”的数字,为什么他们是很好的。 事实上,如果理论是正确的(我不知道谁一直是有争议的),RANLUX在最高水平的奢华完全产生去相关的数量下降到最后一位,没有长程关联,只要我们保持健康以下的期间(10 ^ 171)。 大多数其他发电机可以说是很了解他们的质量(如梅森难题,KISS等),他们必须依靠通过统计检验。

欧洲核子研究中心物理学家此PRNG的风扇。 “这份厚礼说。



Answer 4:

一些在这些其他的答案中的信息与我的研究结果相冲突。 我已经运行使用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


Answer 5:

在一般情况下,梅森难题是最好的,最快的RNG,但它需要一定的空间(约2.5千字节)。 哪一个适合您需要取决于你需要多少次实例化生成器对象。 (如果你需要初始化它只有一次,或者几次,然后MT是使用的人。如果你需要初始化它几百万次,那么也许更小的东西。)

有些人报告说MT比一些其他的慢。 根据我的实验,这在很大程度上取决于你的编译器优化设置。 最重要的是-march =原始设置可能产生巨大的变化,这取决于你的主机体系结构。

我跑了一个小程序来测试不同的发电机,以及它们尺寸的速度,并得到这个:

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


Answer 6:

它是一种折衷真的。 如A PRNG Mersenne Twister是更好,因为它具有非常大的周期等有良好的统计特性。

但大的周期PRNG占用更多的存储器(用于保持内部状态),也需要更多的时间用于产生随机数(由于复杂的过渡和后处理)。

根据您的应用需求选择PNRG。 有疑问时使用Mersenne Twister ,它在许多工具的默认。



Answer 7:

我刚才看到这个答案从Marnos并决定测试它自己。 我用std::chono::high_resolution_clock到时间100000样品100次,以产生平均。 我测量了一切std::chrono::nanoseconds ,并结束了与不同的结果:

std::minstd_rand有平均28991658纳秒

std::mt19937有平均29871710纳秒

ranlux48_base有平均29281677纳秒

这是一个Windows 7机器上。 编译器是MinGW的-4.8.1构建64位。 这显然是使用C ++ 11的标志,没有优化标志。

当我打开-O3的优化工作, std::minstd_randranlux48_base实际运行比执行速度更快high_precision_clock可以测量; 然而std::mt19937仍需要730045纳秒,或第二的3/4。

所以,他说,这是实现特定的,但至少在GCC的平均时间似乎坚持什么在接受答案的描述说。 梅森难题似乎到至少从优化中受益,而另外两个真的只是扔出去的随机数unbelieveably快,一旦你在编译器优化因素。

顺便说一句,我一直在使用梅森难题引擎在我噪音生成库(它不预先计算的梯度),所以我想我会切换到其他的一个真正看到一些速度改进。 在我的情况下,“真正的”随机性无所谓。

码:

#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;
}


文章来源: Random Engine Differences
标签: c++ random c++11