为什么带有unsigned int的模数比使用unsigned long long运行得更快?

时间:2015-06-28 15:28:25

标签: c modulus speed-test

为什么要测试模量的速度?

我有一个应用程序,模数操作每秒执行数百万次。我必须使用非常大的数字,因此我选择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所用的时间;现在结果要少得多,但仍然差异很大。

2 个答案:

答案 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函数中的代码量,我们回答了问题。