【问题标题】:Why some arithmetic operations take more time than usual?为什么有些算术运算比平时花费更多时间?
【发布时间】:2012-09-05 19:02:48
【问题描述】:

我在执行具有小精度浮点数的算术运算时检测到异常的计算时间。以下简单代码展示了这种行为:

#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.32000segs

  • 当 y = 0.9

    x = 0.000000
    时间:19.99000segs

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

  • CPU:Intel® Core™2 Duo CPU T5800 @ 2.00GHz × 2
  • 内存:4 GB
  • 操作系统:Ubuntu 12.04(64 位)
  • 型号:Dell Studio 1535

有人可以详细解释为什么会发生这种行为吗?我知道 y = 0.9 时 x 值变为 0 的速度比 y = 0.5 时要慢,所以我怀疑问题与此直接相关。

【问题讨论】:

  • 在达到 0 之前,您可能会在第二种情况下获得更多的非正规化。
  • 另请参阅this answer,了解非规范化对性能的影响。

标签: c performance time


【解决方案1】:

您得到可测量的差异并不是因为 0.9^n 在数学上收敛到 0 的速度比 0.5^n 慢,而是因为在 IEEE-754 浮点实现中,它根本不会收敛到 0。

IEEE-754 表示中最小的正double 是2-1074,最小的正法线是2-1021,所以用y = 0.5,循环遇到 53 个次正规数。一旦达到最小的正次正规,下一个产品将是 2-1075,但由于 round-ties-to-last-bit-zero 默认舍入模式舍入为 0。(IEEE -754 表示浮点数和默认的round-ties-to-last-bit-zero舍入模式在标准消费硬件上几乎无处不在,即使该标准没有完全实现。)来自然后,您有一个乘法 0*y,这是一个普通的浮点乘法(即使 y 是一个次正规数,它也很快)。

对于0.5 &lt; y &lt; 1,一旦达到(正)次正常范围的下限,x*y 的结果将再次舍入为x 的值(对于y = 0.9,迭代是 5*2-1074)。由于在几千次迭代 (0.9^7 &lt; 0.5) 后达到此值,因此您基本上是在将一个次正规数与整个循环的非零数相乘。在许多处理器上,这样的乘法不能直接处理,必须在微码中处理,这要慢得多。

如果速度比 IEEE-754 语义更重要(或者如果出于其他原因不希望这样做),许多编译器会提供选项来禁用该行为并将次正规数刷新为 0(如果硬件支持)。我在 gcc 的手册页中找不到明确的选项,但 -ffast-math 在这里成功了。

【讨论】:

  • 感谢证明可以通过精确计算分析问题并提供编译器选项进行优化。你能解释一下为什么在 0.5
  • 如果x是最小的正次正规,而y &gt; 0.5,那么x*y的精确值大于x/2,所以不是平局,而是四舍五入到更接近x0,即 x。如果 y 足够大于 0.5,则在 x 达到最小的次正规 s 之前发生,例如对于y = 0.75,数学上你有(2*s)*y = 1.5*s,这会产生一个平局——它正好在s2*s之间,所以它再次四舍五入为2*s,最后一位为0。对于@987654348 @ 和 x = 5*s,你会得到 4.5*s,但最接近的 double 略大于 0.9,所以它向上取整。
  • 谢谢,我不知道这个低级细节。
【解决方案2】:

非正规(或者更确切地说是次正规)数字通常会影响性能。根据您的第二个示例,慢慢收敛到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。这 称为次正规数。有次正规数的目的 就是平滑最小正态数和零之间的差距。

意识到次正规数的表示是非常重要的 精度低于正常数字。事实上,他们在交易 由于尺寸较小而降低了精度。因此计算使用 次正规数不会具有与 正常数的计算。所以一个应用程序 对次正规数进行大量计算可能值得 调查以查看是否重新缩放(即将数字乘以 一些比例因子)会产生更少的次正规,并且更准确 结果。

我想自己解释一下,但上面的解释写得非常好,简洁。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-09
    • 1970-01-01
    • 2018-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-07
    • 1970-01-01
    相关资源
    最近更新 更多