为什么编译器不能用0优化浮点加法?

Jan*_*tke 22 c c++ optimization compilation compiler-optimization

我有四个身份函数,它们基本上什么都不做。只有乘法1才能通过 clang 优化为单个ret语句。

float id0(float x) {
    return x + 1 - 1;
}

float id1(float x) {
    return x + 0;
}

float id2(float x) {
    return x * 2 / 2;
}

float id3(float x) {
    return x * 1;
}
Run Code Online (Sandbox Code Playgroud)

以下编译器输出是:(clang 10, at -O3)

.LCPI0_0:
        .long   1065353216              # float 1
.LCPI0_1:
        .long   3212836864              # float -1
id0(float):                                # @id0(float)
        addss   xmm0, dword ptr [rip + .LCPI0_0]
        addss   xmm0, dword ptr [rip + .LCPI0_1]
        ret
id1(float):                                # @id1(float)
        xorps   xmm1, xmm1
        addss   xmm0, xmm1
        ret
.LCPI2_0:
        .long   1056964608              # float 0.5
id2(float):                                # @id2(float)
        addss   xmm0, xmm0
        mulss   xmm0, dword ptr [rip + .LCPI2_0]
        ret
id3(float):                                # @id3(float)
        ret
Run Code Online (Sandbox Code Playgroud)

我可以理解为什么id0并且id2无法优化。他们增加了可能变成正无穷大的值,第二次操作不会把它改回来。

但是为什么不能id1优化呢?无穷大的加法将产生无穷大,任何常规数的加法将产生该数字,而加法NaN将产生NaN。那么为什么它不是像* 1.

编译器资源管理器示例

Nel*_*eal 19

IEEE 754 浮点数有两个零值,一个负值,一个正值。当加在一起时,结果是正数。

那么id1(-0.f)0.f不是-0.f
请注意,id1(-0.f) == -0.f因为0.f == -0.f.

演示

另外,请注意,-ffast-math在 GCC 中编译确实会进行优化并更改结果。


t.n*_*ese 7

“我有四个身份函数,它们基本上什么都不做。”

这不是真的。

对于浮点数x + 1 - 1is not equal x + 0,它是 equal (x + 1) - 1。因此,如果您有一个非常小的x例子,那么您将在该x + 1步骤中丢失非常小的部分,并且编译器无法知道这是否是您的意图。

在 的情况下,由于浮点精度x * 2 / 2,thex * 2也可能不准确,因此您在这里有类似的情况,编译器不知道您是否出于某种原因想要以x这种方式更改 的值。

所以这些是相等的:

float id0(float x) {
    return x + (1. - 1.);
}

float id1(float x) {
    return x + 0;
}
Run Code Online (Sandbox Code Playgroud)

这些将是相等的:

float id2(float x) {
    return x * (2. / 2.);
}

float id3(float x) {
    return x * 1;
}
Run Code Online (Sandbox Code Playgroud)

所需的行为肯定可以用另一种方式定义。但是正如Nelfeal已经提到的,必须使用显式激活此优化-ffast-math

启用快速数学模式。此选项允许编译器对浮点数学做出积极的、可能有损的假设。这些包括:

  • 浮点数学遵循实数的常规代数规则(例如 + 和 * 是结合的,x/y == x * (1/y) 和 (a + b) * c == a * c + b * c) ,
  • 浮点运算的操作数不等于 NaN 和 Inf,并且
  • +0 和 -0 可以互换。

fast-math 是用于 clang 和 gcc 的一组标志(这里是 clang 列出的标志):

  • -fno-honor-infinities
  • -fno-honor-nans
  • -fno-math-errno
  • -有限数学
  • -fassociative-数学
  • -freciprocal-math
  • -fno 有符号零
  • -fno-trapping-math
  • -ffp-合同=快