为什么Clang优化了x*1.0而不是x+0.0?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (12)

如何优化了这个代码中的循环

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

static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };

int main()
{
    clock_t const start = clock();
    for (int i = 0; i < N; ++i) { arr[i] *= 1.0; }
    printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}

但不是这个代码中的循环?

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

static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };

int main()
{
    clock_t const start = clock();
    for (int i = 0; i < N; ++i) { arr[i] += 0.0; }
    printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}

(标记为C和C ++,因为我想知道每个答案是否不同。)

提问于
用户回答回答于

IEEE 754-2008浮点运算标准和ISO / IEC 10967语言无关算术(LIA)标准,第1部分回答了为什么如此。

IEEE 754§6.3符号位 当输入或结果是NaN时,此标准不解释NaN的符号。但是请注意,位串上的操作 - 复制,否定,abs,copySign - 指定NaN结果的符号位,有时基于NaN操作数的符号位。逻辑谓词totalOrder也受NaN操作数的符号位影响。对于所有其他操作,即使只有一个输入NaN,或者NaN由无效操作产生,该标准也不会指定NaN结果的符号位。 当输入和结果都不是NaN时,产品或商的符号是操作数符号的异或; 总和的符号,或者被认为是总和x +(-y)的差异x-y的符号与最多的一个加数符号不同; 并且转换结果的符号,量化操作,roundTo-Integral操作和roundToIntegralExact(见5.3.1)是第一个或唯一操作数的符号。即使操作数或结果为零或无限时,这些规则也适用。 当两个符号相反的操作数之和(或两个操作数与相同符号的差值)恰好为零时,除了roundTowardNegative之外,该总和(或差值)的符号在所有舍入方向属性中应为+0; 在该属性下,确切的零和(或差值)的符号应为-0。但是,x + x = x - (-x)即使在x为零时也保留与x相同的符号。

加法的情况

在默认舍入模式下 (Round-to-Nearest,Ties-to-Even),我们看到x+0.0产生了x,除非x-0.0:在这种情况下,我们有两个操作数的总和,其符号相反,其和为零,§6.3段落这个加法产生的3条规则+0.0

由于与原始数据+0.0不是按位相同的-0.0,并且这-0.0是可能作为输入发生的合法值,所以编译器必须将代码转换为可能的负零+0.0

总结:在缺省舍入模式下,在x+0.0,如果x

  • 不是 -0.0,那么x它本身就是一个可接受的输出值。
  • -0.0,那么输出值必须是 +0.0,这不是按位相同的-0.0

乘法的例子

在默认舍入模式下,不会出现此类问题x*1.0。如果x

  • 是(子)正常数字,x*1.0 == x总是。
  • +/- infinity,那么结果是+/- infinity一样的。
  • NaN,然后按照 IEEE 754§6.2.3 NaN传播 将NaN操作数传播到其结果并将单个NaN作为输入的操作应生成一个NaN,并且输入NaN的有效负载(如果可用目标格式表示)。 这意味着该指数和尾数(虽然不是符号)的NaN*1.0推荐的是从输入不变NaN。符号未按照上面的6.3p1规定,但实施可以指定它与源相同NaN
  • +/- 0.0,那么结果是一个0符号位与符号位1.0XORed,符合§6.3p2。由于1.0is 的符号位0,输出值与输入值相同。因此,x*1.0 == x即使x是(负)零。

减法的例子

在默认舍入模式下,减法x-0.0也是无操作,因为它相当于x + (-0.0)。如果x

  • NaN,那么§6.3p1和§6.2.3的应用与添加和乘法的方式大致相同。
  • +/- infinity,那么结果是+/- infinity一样的。
  • 是(子)正常数字,x-0.0 == x总是。
  • -0.0,随后通过§6.3p2我们有“ [...]的总和,或差x的的符号- Ÿ视为总和X +(-y),不同于的加数符号的至多一个; ”。这迫使我们分配-0.0作为的结果(-0.0) + (-0.0),因为-0.0从不同的标志没有加数,而+0.0在不同的标志2加数,违反本条款。
  • +0.0,则这会降低到加法情况下(+0.0) + (-0.0)在上面考虑添加的情况下,其通过§6.3p3被排除,得到+0.0

因为在所有情况下输入值都是合法的输出值,所以允许考虑x-0.0一个无操作和x == x-0.0重言式。

改变价值的优化

IEEE 754-2008标准有以下有趣的引用:

IEEE 754§10.4字面含义和值变化优化 [...] 下面的值转换转换等等保留了源代码的字面含义:

  • 当x不为零时,应用标识属性0 + x并且不是信号NaN,结果与x指数相同。
  • 当x不是信号NaN并且结果与x指数相同时,应用身份属性1×x。
  • 更改静音NaN的有效载荷或符号位。
  • [...]

由于所有的NaN和所有的无穷大都具有相同的指数,并且对于有限的正确舍入结果x+0.0和完全相同的大小与它们的指数是相同的。x*1.0xx

sNaNs

信号NaN是浮点陷阱值; 它们是特殊的NaN值,其用作浮点操作数会导致无效的操作异常(SIGFPE)。如果触发异常的循环被优化了,软件将不再表现相同。

C11标准显式地留下了未定义的信号NaNs(sNaN)的行为,因此编译器可以假定它们不会发生,因此它们引发的异常也不会发生。C ++ 11标准省略了描述用于发信号通知的行为,因此也使其不确定。

舍入模式

在交替舍入模式下,允许的优化可能会改变。例如,在轮到负无限模式下,优化x+0.0 -> x变得允许,但x-0.0 -> x被禁止。

为了防止海湾合作委员会采取默认的舍入模式和行为,实验标志-frounding-math可以传递给海湾合作委员会。

结论

Clang和GCC甚至在-O3,仍然符合IEEE-754标准。这意味着它必须遵守IEEE-754标准的上述规则。x+0.0没有被比特相同,以x对所有x的那些规则,但x*1.0 可以被选择成这样:即,当我们

  1. 遵守建议,以便x当它是NaN时通过不变的有效载荷。
  2. NaN结果的符号位保持不变* 1.0
  3. 服从的商/产品期间以异或符号位,当x为NaN。

要启用IEEE-754不安全优化(x+0.0) -> x,该标志-ffast-math需要传递给Clang或GCC。

用户回答回答于

x += 0.0如果x是,不是NOOP -0.0。尽管如此,优化器仍然可以去掉整个循环,因为结果没有被使用。一般来说,很难说出优化器为什么会做出决定。

扫码关注云+社区