Así que vi una charla llamada rand () Considered Nocivo y abogó por el uso del paradigma de distribución del motor de generación de números aleatorios sobre el std::rand()
paradigma simple más módulo.
Sin embargo, quería ver las fallas de std::rand()
primera mano, así que hice un experimento rápido:
- Básicamente, escribí 2 funciones
getRandNum_Old()
ygetRandNum_New()
eso generó un número aleatorio entre 0 y 5 inclusive usandostd::rand()
ystd::mt19937
+std::uniform_int_distribution
respectivamente. - Luego generé 960.000 (divisibles por 6) números aleatorios usando la forma "antigua" y registré las frecuencias de los números 0-5. Luego calculé la desviación estándar de estas frecuencias. Lo que estoy buscando es una desviación estándar lo más baja posible, ya que eso es lo que sucedería si la distribución fuera realmente uniforme.
- Ejecuté esa simulación 1000 veces y registré la desviación estándar para cada simulación. También registré el tiempo que tomó en milisegundos.
- Después, hice exactamente lo mismo de nuevo, pero esta vez generando números aleatorios de la "nueva" forma.
- Finalmente, calculé la desviación estándar y media de la lista de desviaciones estándar tanto para la forma antigua como para la nueva y la desviación media y estándar para la lista de tiempos tomados para la forma antigua y nueva.
Estos fueron los resultados:
[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
Sorprendentemente, la extensión total de rollos fue la misma para ambos métodos. Es decir, std::mt19937
+ std::uniform_int_distribution
no era "más uniforme" que simple std::rand()
+ %
. Otra observación que hice fue que la nueva forma era 4 veces más lenta que la antigua. En general, parecía que estaba pagando un gran costo en velocidad por casi ninguna ganancia en calidad.
¿Mi experimento tiene algún defecto? ¿O std::rand()
realmente no es tan malo, y tal vez incluso mejor?
Como referencia, aquí está el código que usé en su totalidad:
#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));
}