为什么一些算术运算需要比平常更多的时间?

时间:2012-09-12 17:40:51

标签: c performance time

我在使用小精度浮点数执行算术运算时检测到了不寻常的计算时间。以下简单代码表现出这种行为:

#include <time.h>
#include <stdlib.h>
#include <stdio.h>

const int MAX_ITER = 100000000;

int main(int argc, char *argv[]){
    double x = 1.0, y;
    int i;
    clock_t t1, t2;
    scanf("%lf", &y);
    t1 = clock();
    for (i = 0; i < MAX_ITER; i++)
        x *= y;
    t2 = clock();
    printf("x = %lf\n", x);
    printf("Time: %.5lfsegs\n", ((double) (t2 - t1)) / CLOCKS_PER_SEC);
    return 0;
}

以下是该程序的两个不同运行:

  • y = 0.5

      

    x = 0.000000
      时间:1.32000秒

  • y = 0.9

      

    x = 0.000000
      时间:19.99000秒

我正在使用具有以下规格的笔记本电脑来测试代码:

  • CPU :英特尔®酷睿™2双核CPU T5800 @ 2.00GHz×2
  • RAM :4 GB
  • 操作系统:Ubuntu 12.04(64位)
  • 型号:Dell Studio 1535

有人可以详细解释为什么会出现这种情况吗?我知道,当y = 0.9时,x值比y = 0.5更慢,所以我怀疑问题与此直接相关。

2 个答案:

答案 0 :(得分:10)

非常规(或更确切地说是次正常的)数字通常会受到影响。根据您的第二个示例,慢慢收敛到0将产生更多次正规。阅读更多herehere。要获得更严肃的阅读,请查看常被引用的(并且非常密集)What Every Computer Scientist Should Know About Floating-Point Arithmetic

来自第二个来源:

  

在IEEE-754下,浮点数用二进制表示为:

     

Number = signbit \* mantissa \* 2exponent

     

有可能有多种方式来表示相同的数字,   以十进制为例,数字0.1可以表示为   1 * 10-1或0.1 * 100甚至0.01 * 10.标准规定了   数字始终与第一位一起存储。小数点   对应于1 * 10-1的例子。

     

现在假设可以表示的最低指数是-100。   因此,可以用正常形式表示的最小数字是   1 * 10-100。但是,如果我们放松了前导位的约束   一个,那么我们实际上可以代表相同的较小数字   空间。以十进制为例,我们可以表示0.1 * 10-100。这个   被称为次正规数。具有次正规数的目的   是为了平滑最小正常数和零之间的差距。

     

认识到表示次正规数是非常重要的   精度低于正常数字。事实上,他们正在交易   尺寸越小,精度越低。因此使用的计算   次正规数不会具有相同的精度   计算正常数字。这样的应用程序   对正常数字的重要计算可能是值得的   调查以确定是否重新缩放(即将数字乘以   一些比例因子)会产生更少的次正规,并且更准确   结果

我正在考虑自己解释,但上面的解释写得非常简洁。

答案 1 :(得分:3)

你得到一个可衡量的差异不是因为0.9^n在数学上比0.5^n收敛到0慢,而是因为在IEEE-754浮点实现中,它根本不会收敛到0。 / p>

IEEE-754表示中的最小正double为2 -1074 ,最小正正常为2 -1021 ,因此使用{{1} },循环遇到53个次正规数。一旦达到最小的正次正规,下一个乘积将是2 -1075 ,但是由于舍入到最后一位零的默认舍入模式被四舍五入为0.(IEEE -754浮点数的表示和默认的round-ties-to-last-bit-zero舍入模式在标准的消费者硬件上几乎无处不在,即使标准没有完全实现。)然后,你有一个乘法y = 0.5,它是一个普通的浮点乘法(即使0*y是一个次正规数,它也很快)。

使用y,一旦达到(正)次正常范围的下限,0.5 < y < 1的结果再次向x*y的值舍入({{1}迭代的固定点是5 * 2 -1074 )。由于在几千次迭代(x)之后达到了这个值,所以基本上将一个次正规数乘以整数循环的非零数。在许多处理器上,这样的乘法不能直接处理,必须在微码中处理,这要慢得多。

如果速度比IEEE-754语义更重要(或者由于其他原因而不合适),许多编译器提供禁用该行为的选项,并在硬件支持时将次正常数字刷新为0。我在gcc的手册页中找不到明确的选项,但是y = 0.9在这里做了诀窍。