我有一个应用程序,模数操作每秒执行数百万次。我必须使用非常大的数字,因此我选择unsigned long long
作为数据类型。大约一周前,我为我的应用程序编写了一个新算法,该算法要求对数字远小于 的数字执行模数运算(例如 26 而不是 10000000 )。我选择使用unsigned int
作为数据类型。 速度急剧增加,而算法几乎相同。
我用C编写了两个简单的程序来测试模数计算的速度。
#include <stdio.h>
typedef unsigned long long ull;
int main(){
puts("Testing modulus with ull...");
ull cnt;
ull k, accum=0;
for(k=1, cnt=98765432;k<=10000000;++k,--cnt)
accum+=cnt%80;
printf("%llu\n",accum);
return 0;
}
我唯一要改变的是名为cnt
的变量类型。
我用time ./progname
测试了这些程序,结果如下。
unsigned long long
: 3.28秒 unsigned int
: 0.33秒 注意:我在越狱的iPad上测试它,这就是为什么需要花费这么多时间的原因。
为什么unsigned long long
的版本需要花费很多时间才能运行?
Update1:将--cnt
添加到循环中,因此cnt%80
不会保持不变;结果仍然相同。
更新2:已移除printf
并添加accum
以消除printf
所用的时间;现在结果要少得多,但仍然差异很大。
答案 0 :(得分:2)
从根本上说,执行算术运算所需的时间量至少与运算数中的位数成比例。对于现代cpus,时间是加法,减法,逻辑运算的常数(通常是一个周期),并且当操作数适合寄存器时可以是乘法,但是可以扩展到RSA数量级或其他&#34; bignum&#34;用法,您将清楚地看到执行算术比例的时间。
在除法和余数运算的情况下,这些本身就更昂贵,并且通常您会注意到不同操作数大小的显着差异。当然,如果你的cpu是32位,那么进行64位除法/余数操作将涉及用多个较小的操作构造它(就像一个小的特殊情况&#34; bignum&#34;算术)所以它会慢得多。
然而,您的测试完全无效。除法是不变的,因此在每次循环迭代中都不应该重新计算,在循环中花费的时间应该由printf
支配,并且与printf
一起使用的格式说明符不是对于打印unsigned long long
类型的参数有效,因此您的程序具有未定义的行为。
答案 1 :(得分:1)
假设采用32位系统,差异在于64位与32位模运算。
ull cnt;
导致(使用-O2优化):
.L2:
pushl $0
pushl $80
pushl %edi
pushl %esi
call __umoddi3 ; note the function call here
addl $16, %esp
addl %eax, -32(%ebp)
adcl %edx, -28(%ebp)
addl $-1, %esi
movl %esi, %eax
adcl $-1, %edi
xorl $88765432, %eax
orl %edi, %eax
jne .L2
pushl -28(%ebp)
pushl -32(%ebp)
pushl $.LC1
pushl $1
call __printf_chk
,而
unsigned int cnt;
导致(也使用-O2优化):
.L2:
movl %ecx, %eax
mull %ebx
shrl $6, %edx
leal (%edx,%edx,4), %eax
movl %ecx, %edx
sall $4, %eax
subl %eax, %edx
movl %edx, %eax
xorl %edx, %edx
addl %eax, %esi
adcl %edx, %edi
subl $1, %ecx
cmpl $88765432, %ecx
jne .L2
pushl %edi
pushl %esi
pushl $.LC1
pushl $1
call __printf_chk
考虑到__umoddi3
函数中的代码量,我们回答了问题。