前言
ACM 时,曾经测试过 Lutece 的速度并记录在这篇博客,毕竟 OJ 运算速度直接关系到了每一次写的代码的复杂度。
脱坑 ACM 以后,在编程珠玑里面看到了自己的电脑的运算速度应该作为常识记住,于是就花一天的时间测了一下,并进行了简要分析。附测试的代码和完整数据。
本文涉及到的次数并不准确,因为没有计算 for 循环递增量以及执行 for 循环的时间。本文只是粗略统计一下电脑计算速度的数量级,以及比较各状态下的速度。
测试环境
测试电脑:Surface Book
CPU:Intel(R) Core(TM) i7-6600U CPU @ 2.60GHz
测试线程数:1
操作系统:Windows 10 10.0.18912.1001 (64 bit)
编译器:MSVC 2017
编译选项(除测试架构和编译选项时):X64, Release, O2 优化
每次测试组数:20(程序启动的前两组速度有明显的差异,因此统计运算速度时之计后 18 组)
结论
- 对于架构和编译选项来说,O2 优化能带给程序 3-9 倍的速度提升;同样的代码编译出的 64 位程序较 32 位快 13% - 218%;
- 整型的运算的加、乘都是 1e9 次/秒数量级的,浮点类型的加、乘都是 1e8 次/秒数量级。对于 log 和 sqrt 类型,整型可能会慢于浮点类型,但都在 1e7次/秒 数量级;
- 极端数据范围的数据运算不影响整型运算的速度,略微影响浮点型运算的速度(10%);
- 高精度运算在
long long
范围内加,比 C 自带整型类型慢三个数量级;100 位的加法也只比前面慢不到一个数量级(后经测试,乘法慢于加法,但差别不大,虽然是乘法 $O(n^2)$,但 n=100 也较小)。
测试一:架构和编译选项
测试的是 long long
类型从 1 加到 1e9。以下是测试结果:
1s 运算次数 | debug(Od) | release(Od) | release(O1) | release(O2) |
---|---|---|---|---|
x86 | 3.05E+08 | \ | \ | 9.55E+08 |
x64 | 3.47E+08 | 3.61E+08 | 1.9E+09 | 3.04E+09 |
注:Od
为禁用优化,O1
为最大优化(优选大小),O2
为最大优化(优选速度)。
可以观察到,在没有优化的情况下,debug 和 release 的区别其实不大。但是开了 O2 优化以后,程序获得了 3-9 倍的加速。
而对于 x86 和 x64 来说,无优化下,x64 略快于 x86 (约 13%);而在 O2 优化下,x64 较 x86 快了 218%。
测试二:数据类型和运算
测试的类型有 int
,long long
,float
,double
和一个自定义的高精度运算 BigInteger
(源码包含在测试代码中)。测试的运算有 +
,*
,cmath
中的 sqrt()
和 log()
。
int
,long long
,double
都是从 1 测到 1e9。
在进行 float
的测试时发现使用 i++
,当数据达到 $2^{24} \approx 1.6 \times 10^7$ 时,i
的值不会发生变化(1 溢出了,这和和浮点数的记法有关)。于是对于 float
的测试都是从 1 测到 1e7,重复 100 次。BigInteger
由于太慢,只从 1 测到 1e7。
以下是测试结果:
1s 运算次数 | int | long long | float | double | BigInteger |
---|---|---|---|---|---|
+ | 3.26E+09 | 3.04E+09 | 5.92E+08 | 5.20E+08 | 1.51E+06 |
* | 1.99E+09 | 2.00E+09 | 6.36E+08 | 6.43E+08 | 1.11E+06 |
log() | 3.70E+07 | 4.94E+07 | 5.69E+07 | 5.35E+07 | \ |
sqrt() | 6.79E+07 | 6.83E+07 | 7.63E+07 | 5.59E+07 | \ |
从数量级上来看,整数的加和乘能达到 1e9 次/秒 的数量级,而浮点数也能达到 1e8 次/秒 的数量级。
相比下,高精度就慢得多了,只能达到 1e6 次/秒 的数量级,比整数运算低了 3 个数量级。(所以 ACM 中,能用 __int128
就别用高精度)。
对于不同精度的整型和浮点型数据,加法上,int
比 long long
快约 7%,float
比 double
快约 14%。但是乘法上,long long
甚至比 int
快不到 1%,double
比 float
快 1%。都不是很明显。
对于 log
和 sqrt
函数来说,int
和 long long
反而可能比 float
和 double
慢。这可能是因为 C 语言并没有实现整型的 log
和 sqrt
函数,而是把整型强转为浮点数来进行计算。但是这几种数据类型的运算仍然在一个数量级。
测试三:数据范围对运算速度的影响
由于浮点数有移位的过程,所以可能数据范围对运算速度也有一定影响。
本次测试选用了 long long
(做对照),float
,double
,BigInteger
,分别从 1e7, 1e10, 1e14 开始累加量为 1,累加 1e9 次(或 1e7次,循环 100 遍)(BigInteger
由于过慢,只累加 1e7 次;float
由于上面提到的 1 溢出问题,只计算从 1e7 累加至 1.5e7)。
1s 运算次数 | long long | float | double | BigInteger |
---|---|---|---|---|
1e7 | 2.03E+09 | 5.87E+08 | 5.97E+08 | 1.70E+06 |
1e10 | 2.02E+09 | \ | 5.36E+08 | 1.08E+06 |
1e14 | 2.11E+09 | \ | 5.31E+08 | 9.78E+05 |
1e100 | \ | \ | \ | 3.32E+05 |
可以注意到,long long
的数据范围和运算速度关系确实不大;而double
会有影响:数据范围从 1e7 扩大到 1e14,运算速度下降了 11%,但是仍然在一个数量级内。
而 BigInteger
的影响同样也不大,即使是从 1e7 加到 1e100,运算速度也只下降了一个数量级。可能是其他操作比较慢