Widziałem więc wykład o nazwie rand () Uważany za szkodliwy, który zalecał użycie paradygmatu dystrybucji przez silnik generowania liczb losowych zamiast prostego std::rand()
paradygmatu plus moduł.
Chciałem jednak na std::rand()
własne oczy zobaczyć wady, więc zrobiłem szybki eksperyment:
- Zasadniczo, pisałem 2 funkcje
getRandNum_Old()
igetRandNum_New()
który wygenerował liczbę losową z zakresu od 0 do 5 włącznie użyciustd::rand()
istd::mt19937
+std::uniform_int_distribution
odpowiednio. - Następnie wygenerowałem 960 000 (podzielnych przez 6) liczb losowych stosując „stary” sposób i zapisałem częstotliwości liczb 0-5. Następnie obliczyłem odchylenie standardowe tych częstotliwości. To, czego szukam, to odchylenie standardowe tak niskie, jak to możliwe, ponieważ tak by się stało, gdyby rozkład był naprawdę jednolity.
- Przeprowadziłem tę symulację 1000 razy i zapisałem odchylenie standardowe dla każdej symulacji. Zapisałem również czas w milisekundach.
- Później zrobiłem dokładnie to samo, ale tym razem generowałem liczby losowe w „nowy” sposób.
- Na koniec obliczyłem średnią i odchylenie standardowe listy odchyleń standardowych zarówno dla starej, jak i nowej drogi oraz średnią i odchylenie standardowe dla listy czasów przyjętych dla starej i nowej drogi.
Oto wyniki:
[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
Co zaskakujące, łączne rozłożenie rolek było takie samo dla obu metod. Tj. std::mt19937
+ std::uniform_int_distribution
Nie był „bardziej jednolity” niż prosty std::rand()
+ %
. Kolejną obserwacją, jaką zrobiłem, było to, że nowy był około 4x wolniejszy niż stary sposób. Ogólnie rzecz biorąc, wydawało się, że płacę olbrzymie koszty za prędkość, prawie nie podnosząc jakości.
Czy mój eksperyment jest w jakiś sposób błędny? A może std::rand()
naprawdę nie jest tak źle, a może nawet lepiej?
Dla porównania, oto kod, którego użyłem w całości:
#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));
}