因此,我看到了一个名为rand()认为有害的演讲,该演讲提倡在简单std::rand()加模范范式上使用随机数生成的引擎分布范式。
但是,我想看看std::rand()第一手的失败,所以我做了一个快速实验:
- 基本上,我编写了2个函数getRandNum_Old(),分别getRandNum_New()使用std::rand()和std::mt19937+生成了一个介于0和5之间(含0和5)的随机数std::uniform_int_distribution。
- 然后,我使用“旧”方式生成了960,000(可被6整除)随机数,并记录了数字0-5的频率。然后,我计算了这些频率的标准偏差。我要寻找的是尽可能低的标准偏差,因为如果分布真正均匀,就会发生这种情况。
- 我对该模拟运行了1000次,并记录了每个模拟的标准偏差。我还记录了所花费的时间(以毫秒为单位)。
- 之后,我再次进行了完全相同的操作,但是这次以“新”方式生成随机数。
- 最后,我计算了旧方法和新方法的标准差列表的均值和标准差,以及新方法和旧方法的时间列表的均值和标准差。
结果如下:
[OLD WAY]
Spread
       mean:  346.554406
    std dev:  110.318361
Time Taken (ms)
       mean:  6.662910
    std dev:  0.366301
[NEW WAY]
Spread
       mean:  350.346792
    std dev:  110.449190
Time Taken (ms)
       mean:  28.053907
    std dev:  0.654964
出人意料的是,两种方法的面包卷总撒布相同。即,std::mt19937+std::uniform_int_distribution不是简单std::rand()+的“统一” %。我所做的另一项观察是,新方法比旧方法慢大约4倍。总体而言,似乎我付出了巨大的速度代价,却几乎没有质量上的提高。
我的实验有某种缺陷吗?还是std::rand()真的不是那么糟糕,甚至更好?
作为参考,这是我完整使用的代码:
#include <cstdio>
#include <random>
#include <algorithm>
#include <chrono>
int getRandNum_Old() {
    static bool init = false;
    if (!init) {
        std::srand(time(nullptr)); // Seed std::rand
        init = true;
    }
    return std::rand() % 6;
}
int getRandNum_New() {
    static bool init = false;
    static std::random_device rd;
    static std::mt19937 eng;
    static std::uniform_int_distribution<int> dist(0,5);
    if (!init) {
        eng.seed(rd()); // Seed random engine
        init = true;
    }
    return dist(eng);
}
template <typename T>
double mean(T* data, int n) {
    double m = 0;
    std::for_each(data, data+n, [&](T x){ m += x; });
    m /= n;
    return m;
}
template <typename T>
double stdDev(T* data, int n) {
    double m = mean(data, n);
    double sd = 0.0;
    std::for_each(data, data+n, [&](T x){ sd += ((x-m) * (x-m)); });
    sd /= n;
    sd = sqrt(sd);
    return sd;
}
int main() {
    const int N = 960000; // Number of trials
    const int M = 1000;   // Number of simulations
    const int D = 6;      // Num sides on die
    /* Do the things the "old" way (blech) */
    int freqList_Old[D];
    double stdDevList_Old[M];
    double timeTakenList_Old[M];
    for (int j = 0; j < M; j++) {
        auto start = std::chrono::high_resolution_clock::now();
        std::fill_n(freqList_Old, D, 0);
        for (int i = 0; i < N; i++) {
            int roll = getRandNum_Old();
            freqList_Old[roll] += 1;
        }
        stdDevList_Old[j] = stdDev(freqList_Old, D);
        auto end = std::chrono::high_resolution_clock::now();
        auto dur = std::chrono::duration_cast<std::chrono::microseconds>(end-start);
        double timeTaken = dur.count() / 1000.0;
        timeTakenList_Old[j] = timeTaken;
    }
    /* Do the things the cool new way! */
    int freqList_New[D];
    double stdDevList_New[M];
    double timeTakenList_New[M];
    for (int j = 0; j < M; j++) {
        auto start = std::chrono::high_resolution_clock::now();
        std::fill_n(freqList_New, D, 0);
        for (int i = 0; i < N; i++) {
            int roll = getRandNum_New();
            freqList_New[roll] += 1;
        }
        stdDevList_New[j] = stdDev(freqList_New, D);
        auto end = std::chrono::high_resolution_clock::now();
        auto dur = std::chrono::duration_cast<std::chrono::microseconds>(end-start);
        double timeTaken = dur.count() / 1000.0;
        timeTakenList_New[j] = timeTaken;
    }
    /* Display Results */
    printf("[OLD WAY]\n");
    printf("Spread\n");
    printf("       mean:  %.6f\n", mean(stdDevList_Old, M));
    printf("    std dev:  %.6f\n", stdDev(stdDevList_Old, M));
    printf("Time Taken (ms)\n");
    printf("       mean:  %.6f\n", mean(timeTakenList_Old, M));
    printf("    std dev:  %.6f\n", stdDev(timeTakenList_Old, M));
    printf("\n");
    printf("[NEW WAY]\n");
    printf("Spread\n");
    printf("       mean:  %.6f\n", mean(stdDevList_New, M));
    printf("    std dev:  %.6f\n", stdDev(stdDevList_New, M));
    printf("Time Taken (ms)\n");
    printf("       mean:  %.6f\n", mean(timeTakenList_New, M));
    printf("    std dev:  %.6f\n", stdDev(timeTakenList_New, M));
}