为什么MSVS不能优化掉+0?

65

这个question展示了一个非常有趣的现象:denormalized浮点数会使代码变慢超过一个数量级。

这种行为在accepted answer中得到了很好的解释。然而,有一条评论目前有153个赞,我无法找到令人满意的答案:

为什么编译器不直接在这种情况下删除+/-0呢?!? - Michael Dorgan

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


§5.2.4.2.2

实现可以给零和非浮点数(如无穷大和NaN)赋予符号,也可以将它们保留为无符号数。

17
这个问题在链接问题的答案中的最后一条评论中得到了回答:“@s73v3r: +0.f不能被优化掉,因为浮点数有一个负零,并且将+0.f加上-.0f的结果是+0.f。因此,加上0.f不是一个恒等操作,不能被优化掉。- Eric Postpischil" - Michael Burr
7
要明确一点 - 不是 +0.f-0.f 被规范化了 - 而是数组中被加零的值被规范化了(并导致了减速)。 - Michael Burr
1
我认为编辑没有改变任何东西。MSVC使用的浮点实现使用有符号零。这可能不是C标准所要求的,但可能是IEEE 754所要求的(老实说我不知道)。然而,/fp:fast选项可能会导致编译器优化+0.f - 我不知道。 - Michael Burr
1
我认为C或C++标准并没有规定浮点零应该如何表示。然而,我的理解是IEEE 754规定零由所有零位表示(负零的情况下除外,符号位为1)。但是,我对浮点数远非专家,并且对IEEE标准的细节知之甚少。因此,我在这条评论中所说的可能并不是很有用。 - Michael Burr
1
现在它有153票。 - L. F.
显示剩余2条评论
2个回答

72

编译器不能消除浮点正零的加法,因为它不是一个恒等操作。根据IEEE 754标准,将+0.和-0.相加的结果不是-0.,而是+0.

编译器可以消除+0.的减法或-0.的加法,因为这些是恒等操作。

例如,当我编译这个时:

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

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

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

没有加法指令;汇编只是返回其输入。

因此,原问题的代码很可能包含了一个加法指令来执行这个语句:

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

但是没有包含关于该声明的指示:

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

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


8
不是常量0.0f被非规格化,而是每次循环接近零的值被非规格化。随着这些值越来越接近零,需要更多精度来表示,因此才会发生非规格化现象。在原问题中,这些值是y[i]
慢速版本和快速版本之间的关键区别在于语句y[i] = y[i] + 0.1f;。一旦执行这行代码,浮点数中额外的精度就会丢失,并且不再需要表示该精度所需的非规格化操作。之后,在y[i]上进行的浮点运算仍然很快,因为它们没有非规格化操作。
当加上0.1f时为什么会丢失额外的精度?因为浮点数只有那么多有效数字。假设您有足够的存储空间来存储三个有效数字,则0.00001 = 1e-5,并且0.00001 + 0.1 = 0.1,至少对于此示例浮点格式是这样的,因为它没有足够的空间来存储0.10001中的最低有效位。

4
我理解为什么最近因没有回答当前问题而收到了负评。 原本这个问题的标题是“为什么MSVS不能优化掉+0?相反,它将其变成了非规格化浮点数?”而我的回答试图澄清第二部分的困惑。 当然,还有+0.0f和-0.0f之间的区别,这回答了主要问题。 - remcycles

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接