今天在折腾Miller Rabin算法和Pollard Rho算法,中间过程要求随机数。于是来验证一下哪种方法更快生成随机数。
一
反正就是编一个随机整数生成的函数,每调用一次生成一个,参数分别是下界和上界。生成1e7个unsigned long long类型的随机数,并存入变量a。
二话不说,上代码。
#include<cstdio>
#include<cstdlib>
#include<chrono>
#include<random>
#pragma warning(disable:4996)
std::default_random_engine D;
template<typename _Ty> inline _Ty randbetween(const _Ty& a, const _Ty& b) {
if (b < 1 << 15)return (rand() + a) % b;
else if (b < 1 << 30)return ((rand() << 15) + rand() + a) % b;
else if (b < (_Ty)1 << 45)return (((_Ty)rand() << 30) + (rand() << 15) + rand() + a) % b;
else if (b < (_Ty)1 << 60)return (((_Ty)rand() << 45) + ((_Ty)rand() << 30) + (rand() << 15) + rand() + a) % b;
return (((_Ty)rand() << 60) + ((_Ty)rand() << 45) + ((_Ty)rand() << 30) + (rand() << 15) + rand() + a) % b;
}
template<typename _Ty> inline _Ty RandBetween(const _Ty& a, const _Ty& b) {
std::uniform_int_distribution<_Ty> U(a, b);
return U(D);
}
template<typename _Ty> inline _Ty randfrom(const _Ty& a, const _Ty& b) {
return (((_Ty)rand() << 60) + ((_Ty)rand() << 45) + ((_Ty)rand() << 30) + (rand() << 15) + rand() + a) % b;
}
int main() {
srand(std::chrono::steady_clock::now().time_since_epoch().count());
clock_t t1, t2, t3; unsigned long long a;
_sleep(5000);
t1 = clock();
for (unsigned long long i = 0; i < 10000000; ++i)
a = randbetween(0ull, (unsigned long long) - 1);
//printf("%llu\n", randbetween(0ull, (unsigned long long) - 1));
t1 = clock() - t1;
t2 = clock();
for (unsigned long long i = 0; i < 10000000; ++i)
a = RandBetween(0ull, (unsigned long long) - 1);
//printf("%llu\n", RandBetween(0ull, (unsigned long long) - 1));
t2 = clock() - t2;
t3 = clock();
for (unsigned long long i = 0; i < 10000000; ++i)
a = randfrom(0ull, (unsigned long long) - 1);
//printf("%llu\n", randfrom(0ull, (unsigned long long) - 1));
t3 = clock() - t3;
printf("\n%d %d %d\n", t1, t2, t3);
getchar();
return 0;
}
上结果:
run time记录的值从上到下分别是t1 t2 t3。
结论:
1、Release模式下,明显用头文件random产生随机数更快。Debug模式下更慢,不知道什么原因。
无论是比赛还是正式发布你的软件,肯定是用release版本的代码,所以首选random头。
2、t1和t3基本没区别,可以看出if else条件语句其实不费多少时间。
3、试了一下,GCC编出来的代码的printf输出比MSVC的要慢很(就是把三个for循环体下面的第一句都注释掉,换成第二句,无论是Debug模式还是Release模式下都慢得多,GCC跑出来约13s 10s 10s,Debug和Release都是这样,MSVC跑出来Debug是大约3s 0.6s 0.6s,Release下快近20%吧)多,不知道为何。
二
当随机整数的范围在[0, 32767]之间时,rand()和通过random头文件生成随机数的耗时对比。
代码:
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<random>
#include<ctime>
#pragma warning(disable:4996)
const unsigned long long TestQty = 10000000;
std::default_random_engine D;
std::uniform_int_distribution<int> U(0, 32767);
template<typename _Ty> inline _Ty RandBetween(const _Ty& a, const _Ty& b) {
std::uniform_int_distribution<_Ty> U(a, b);
return U(D);
}
int main() {
clock_t t1, t2, t3; int a; srand(time(0)); D.seed(time(0));
_sleep(5000);
t1 = clock();
for (unsigned long long i = 0; i < TestQty; ++i)
a = rand();
t1 = clock() - t1;
t2 = clock();
for (unsigned long long i = 0; i < TestQty; ++i)
a = U(D);
t2 = clock() - t2;
t3 = clock();
for (unsigned long long i = 0; i < TestQty; ++i)
a = RandBetween(0, 32767);
t3 = clock() - t3;
printf("%d %d %d\n", t1, t2, t3);
system("pause");
return 0;
}
经实测,局部的
std::uniform_int_distribution<_Ty> U(a, b);
前是否添加static修饰几乎不影响运行速度,但机理不明。
实验结果如下(每列的从上到下分别为t1、t2、t3,单位ms):
结论:
1、VS的Debug模式不知道增加了多少东西,使得通过random头生成随机数的耗时大大增加。
2、无论是发布你的正式版软件,还是比赛交题,肯定都是生成Release代码。而Release模式下,如果是MSVC编出来的代码,方案三即局部建立随机分布并返回值是最快的,但原理也未知。
GCC(G++)编出来的代码中,方案二和方案三两者的运行时间几乎没有区别。而Debug模式下,运行速率均为:方案一 > 方案二 > 方案三。
3、考虑到Linux下RAND_MAX的常量宏的值为4294967295而非Windows下的32767。笔者目前未安装Linux系统,故暂时不予测试。但也给出方案二和方案三在上界分别为2147483647、4294967295(此时随机分布需要构造unsigned类型)的条件下的运行耗时。
以下结果,统一左边为VS2019,MSVC编译器;右边为Codeblocks 17.12,GCC 9.2编译器,编译选项为-O2 -m64 -std=c++1z。
(边界为[0, 2147483647],整数均匀分布为int类型,Debug模式)
(边界为[0, 4294967295],整数均匀分布为unsigned类型,Debug模式)
(边界为[0, 2147483647],整数均匀分布为int类型,Release模式)
(边界为[0, 4294967295],整数均匀分布为unsigned类型,Release模式)
晚上在宿舍的时候补测两组:
(边界为[0, 2147483647],整数均匀分布为unsigned类型,Debug模式)
(边界为[0, 2147483647],整数均匀分布为unsigned类型,Release模式)
虽然晚上跑的这两组不知道为什么方案二和三慢了那么几到十几ms,但是仍然可以看出来,当std::uniform_int_distribution<>的边界从[0, 2147483647]变成[0, 4294967295]时,生成随机数的速率反而会变快。当然背后的原因也是不清楚的,真的很玄学。