Also sah ich einen Vortrag namens rand (), der als schädlich angesehen wurde, und er befürwortete die Verwendung des Motorverteilungsparadigmas der Zufallszahlengenerierung gegenüber dem einfachen std::rand()
Plusmodul-Paradigma.
Ich wollte jedoch die Fehler aus std::rand()
erster Hand sehen, also machte ich ein kurzes Experiment:
- Im Grunde genommen habe ich 2 Funktionen
getRandNum_Old()
undgetRandNum_New()
daß eine Zufallszahl zwischen 0 und 5 einschließlich erzeugt wurde ,std::rand()
undstd::mt19937
+std::uniform_int_distribution
sind. - Dann habe ich 960.000 (durch 6 teilbare) Zufallszahlen auf die "alte" Weise generiert und die Häufigkeiten der Zahlen 0-5 aufgezeichnet. Dann habe ich die Standardabweichung dieser Frequenzen berechnet. Was ich suche, ist eine möglichst geringe Standardabweichung, da dies passieren würde, wenn die Verteilung wirklich gleichmäßig wäre.
- Ich habe diese Simulation 1000 Mal ausgeführt und die Standardabweichung für jede Simulation aufgezeichnet. Ich habe auch die Zeit in Millisekunden aufgezeichnet.
- Danach habe ich genau das gleiche noch einmal gemacht, aber diesmal habe ich Zufallszahlen auf "neue" Weise generiert.
- Schließlich berechnete ich den Mittelwert und die Standardabweichung der Liste der Standardabweichungen für den alten und den neuen Weg sowie den Mittelwert und die Standardabweichung für die Liste der Zeiten, die sowohl für den alten als auch für den neuen Weg genommen wurden.
Hier waren die Ergebnisse:
[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
Überraschenderweise war die Gesamtverteilung der Rollen für beide Methoden gleich. Dh std::mt19937
+ std::uniform_int_distribution
war nicht "einheitlicher" als einfach std::rand()
+ %
. Eine andere Beobachtung, die ich machte, war, dass der neue ungefähr 4x langsamer war als der alte Weg. Insgesamt schien es, als würde ich enorme Geschwindigkeitskosten für fast keinen Qualitätsgewinn zahlen.
Ist mein Experiment irgendwie fehlerhaft? Oder ist das std::rand()
wirklich nicht so schlimm und vielleicht sogar noch besser?
Als Referenz ist hier der Code, den ich in seiner Gesamtheit verwendet habe:
#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));
}
rand()
es gut genug ist, hängt weitgehend davon ab, wofür Sie die Sammlung von Zufallszahlen verwenden. Wenn Sie eine bestimmte Art der Zufallsverteilung benötigen, ist die Bibliotheksimplementierung natürlich besser. Wenn Sie einfach Zufallszahlen benötigen und sich nicht um die "Zufälligkeit" oder die Art der Verteilung kümmern, rand()
ist dies in Ordnung. Passen Sie das richtige Werkzeug an den jeweiligen Auftrag an.
for (i=0; i<k*n; i++) a[i]=i%n;
erzeugt den gleichen exakten Mittelwert und die gleiche Standardabweichung wie das beste RNG da draußen. Wenn dies für Ihre Anwendung gut genug ist, verwenden Sie einfach diese Sequenz.