62

这个问题展示了一个非常有趣的现象:非规范化的浮点数会使代码变慢一个数量级以上。

在接受的答案中很好地解释了这种行为。但是,有一条评论,目前有 153 票,我无法找到令人满意的答案:

在这种情况下,为什么编译器不只是删除 +/- 0 ?!?—— 迈克尔·多根

旁注:我的印象是 0f 是/必须是完全可表示的(此外 - 它的二进制表示必须全为零),但在 c11 标准中找不到这样的声明。证明这一点的引述或反驳这一主张的论点将是最受欢迎的。无论如何,迈克尔的问题是这里的主要问题。


§5.2.4.2.2

实现可能会给零和不是浮点数的值(例如无穷大和 NaN)一个符号,或者可能使它们无符号。

4

2 回答 2

66

编译器无法消除浮点正零的添加,因为它不是恒等运算。按 IEEE 754 规则,结果加 +0。为-0。不是-0。它是+0。

编译器可能会消除 +0 的减法。或添加-0。因为这些是身份操作。

例如,当我编译这个时:

double foo(double x) { return x + 0.; }

-O3在 Intel Mac 上使用 Apple GNU C 4.2.1 ,生成的汇编代码包含addsd LC0(%rip), %xmm0. 当我编译这个:

double foo(double x) { return x - 0.; }

没有添加指令;程序集仅返回其输入。

因此,原始问题中的代码很可能包含此语句的添加指令:

y[i] = y[i] + 0;

但不包含此声明的说明:

y[i] = y[i] - 0;

但是,第一条语句涉及到 中的次正规值的算术运算y[i],因此足以减慢程序的速度。

于 2013-05-10T10:30:59.550 回答
6

非规范化的不是零常数0.0f,而是每次循环迭代接近零的值。随着它们越来越接近于零,它们需要更高的精度来表示,因此是非规范化。在原始问题中,这些是y[i]值。

代码的慢版本和快版本之间的关键区别在于语句y[i] = y[i] + 0.1f;。一旦执行此行,浮点中的额外精度就会丢失,并且不再需要表示该精度所需的非规范化。之后,浮点运算y[i]仍然很快,因为它们没有被非规范化。

为什么添加时会丢失额外的精度0.1f?因为浮点数只有这么多有效数字。假设您有足够的存储空间来存储三个有效数字,然后0.00001 = 1e-50.00001 + 0.1 = 0.1,至少对于这个示例浮点格式,因为它没有空间存储0.10001.

于 2018-08-01T13:17:19.423 回答