但是,我想直接看到std::rand()
的缺点,所以我做了一个快速的实验:
getrandnum_old()
和getrandnum_new()
,它们分别使用std::rand()
和std::mt19937
+std::uniform_int_distribution
生成0和5之间的随机数。结果如下:
[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::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));
}
几乎任何“旧”rand()
实现都使用LCG;虽然他们通常不是最好的发电机周围,通常你不会看到他们失败这样一个基本的测试-均值和标准差通常是正确的,即使是最坏的PRNGs。
“坏”(但也足够常见)的rand()
实现的常见故障是:
RAND_MAX
;编辑:@R。正确地注意到,rand
/srand
接口受到以下事实的限制,即srand
采用一个无符号的int
,因此实现可能放在它们后面的任何生成器本质上都限于uint_max
可能的起始种子(以及由此生成的序列)。这确实是正确的,尽管API可以稍加扩展,使srand
采用无符号长
,或者添加单独的srand(unsigned char*,size_t)
重载。
实际上,rand()
的实际问题不是原则上的实现问题,而是:
>
rand_max
只有32767。但是,这不能轻易地改变,因为这会破坏与过去的兼容性--使用srand
并带有固定种子以进行可复制模拟的人不会太高兴(实际上,IIRC前面提到的实现可以追溯到80年代中期的Microsoft C早期版本--甚至是Lattice C);简化界面;rand()
为整个程序提供具有全局状态的单个生成器。虽然这对于许多简单的用例来说是非常好的(实际上也非常方便),但它也带来了一些问题:
最后,rand
状态:
time(NULL)
不是,因为它不够粒度,而且通常--想想没有RTC的嵌入式设备--甚至不够随机)。...以及默认的random_device
来种子它们。
现在,如果你问我,我也希望在这个基础上构建一个简单的API,用于“简单的”、“猜一个数字”的情况(类似于Python提供“复杂的”API,但也可以使用简单的random.randint
&Co.为我们这些不复杂的人使用一个全局的、预先播种的PRNG,他们不希望每次我们想要提取宾果牌的数字时都淹没在随机设备/引擎/适配器/任何东西中),但确实,您可以通过当前的工具轻松地构建它(而通过一个简单的工具构建“完整的”API是不可能的)。
最后,回到您的性能比较:正如其他人所指定的,您正在比较一个快速的LCG和一个较慢的(但通常被认为质量更好的)梅森扭转器;如果您对LCG的质量没有问题,可以使用std::minstd_rand
代替std::mt19937
。
实际上,在调整函数以使用std::minstd_rand
并避免用于初始化的无用静态变量之后
int getRandNum_New() {
static std::minstd_rand eng{std::random_device{}()};
static std::uniform_int_distribution<int> dist{0, 5};
return dist(eng);
}
我得到9毫秒(旧)和21毫秒(新);最后,如果我去掉dist
(与经典的模运算符相比,它处理的是输出范围的分布偏斜,而不是输入范围的倍数),然后回到您在getrandnum_old()
中所做的工作
int getRandNum_New() {
static std::minstd_rand eng{std::random_device{}()};
return eng() % 6;
}
我将其压缩到6 ms(因此,快了30%),这可能是因为与对rand()
的调用不同,std::minstd_rand
更容易内联。
我想知道为什么c标准要求只接受随机访问迭代器?我不认为这有什么好处,因为std::sort和std::list::sort的复杂性都是。将限制为随机访问迭代器(RAI),似乎需要为具有相同复杂性的列表编写单独的函数。 同样的情况也适用于,其中列表的非RAI计数器部分至今仍然缺失。 这种设计是因为历史上人们使用了的变体来实现? 如果在RAI容器上编写排序算法有好处,那么最好使更通用,并让像这样的RA
本文向大家介绍php中随机函数mt_rand()与rand()性能对比分析,包括了php中随机函数mt_rand()与rand()性能对比分析的使用技巧和注意事项,需要的朋友参考一下 本文实例对比分析了php中随机函数mt_rand()与rand()性能问题。分享给大家供大家参考。具体分析如下: 在php中mt_rand()和rand()函数都是可以随机生成一个纯数字的,他们都是需要我们设置好种子
问题内容: 有没有什么方法可以模拟Collections.shuffle的行为,而比较器不容易受到排序算法实现的影响,从而确保结果安全? 我的意思是不违反可比合同等。 问题答案: 不打破合同就不可能实现真正的“改组比较器”。合同的一个基本方面是,结果是可 重现的, 因此必须确定特定实例的顺序。 当然,您可以使用混洗操作预先初始化该固定顺序,并创建一个比较器来精确地建立此顺序。例如 虽然没有意义。显
是否有任何方法可以模拟Collections.shuffle的行为,而比较器不容易受到排序算法实现的影响,以确保结果安全? 我的意思是不违反类似的合同等..
我正在研究一种算法,它需要以尽可能快的速度生成数百万个数字。实际上,我发现我的算法的rand()函数占用了75%的处理时间。 所以我在找更快的东西。而且我根本不需要大范围。(我只需要1000以下的整数) 你知道我需要什么吗? 谢啦! 编辑: 我使用这个数字来洗牌少于1000个实体的组。 我发现了更多关于“快速兰特”的信息。还有SSE版本,它更快,一次生成4个数字。 https://software