Meh*_*dad 125 c c++ floating-point optimization clang
为什么Clang会优化此代码中的循环
#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);
}
Run Code Online (Sandbox Code Playgroud)
但不是这段代码中的循环?
#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);
}
Run Code Online (Sandbox Code Playgroud)
(标记为C和C++,因为我想知道每个答案是否不同.)
Iwi*_*ist 163
IEEE 754-2008浮点运算标准和ISO/IEC 10967语言独立算术(LIA)标准,第1部分回答了为什么会这样.
IEEE754§6.3符号位
当输入或结果是NaN时,该标准不解释NaN的符号.但是请注意,对位串的操作 - copy,negate,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.
摘要:在默认的舍入模式下,in x+0.0,ifx
-0.0,那么x它本身就是一个可接受的输出值.-0.0,然后输出值必须是 +0.0,它不是按位相同的-0.0.在默认的舍入模式下,不会出现此类问题x*1.0.如果x:
x*1.0 == x.+/- infinity,那么结果是+/- infinity相同的符号.是NaN,然后根据
IEEE754§6.2.3NaN传播
将NaN操作数传播到其结果并具有单个NaN作为输入的操作应该生成具有输入NaN的有效负载的NaN(如果在目标格式中可表示).
这意味着该指数和尾数(虽然不是符号)的NaN*1.0被推荐的是从输入不变NaN.根据上述§6.3p1未指定符号,但实现可以指定它与源相同NaN.
+/- 0.0,然后结果是0其符号位与符号位异或,1.0与§6.3p2一致.由于符号位1.0为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 - y被视为和x +(-y),与最多一个加数的符号不同; ".这迫使我们分配-0.0作为结果(-0.0) + (-0.0),因为-0.0符号与任何加数+0.0不同,而符号与其中两个加数不同,违反了此条款.+0.0,然后这减少到(+0.0) + (-0.0)上面在"加法案例"中考虑的加法案例, §6.3p3被裁定给予+0.0.因为对于所有情况,输入值都是合法的输出,所以允许考虑x-0.0无操作和x == x-0.0重言式.
IEEE 754-2008标准有以下有趣的引用:
IEEE754§10.4字面意义和价值改变优化
[...]
以下值更改转换保留了源代码的字面含义:
- 当x不为零且不是信令NaN时应用identity属性0 + x,结果与x具有相同的指数.
- 当x不是信令NaN时应用identity属性1×x,结果与x具有相同的指数.
- 更改安静NaN的有效负载或符号位.
- [...]
由于所有NaN和无穷的所有共享相同的指数,以及正确舍入的结果x+0.0,并x*1.0为有限x具有完全相同的量值相同x,其指数是一样的.
信令NaN是浮点陷阱值; 它们是特殊的NaN值,其用作浮点操作数会导致无效的操作异常(SIGFPE).如果优化了触发异常的循环,则软件将不再表现相同.
但是,正如user2357112 在注释中指出的那样,C11标准明确地保留了未定义信令NaNs(sNaN)的行为,因此允许编译器假定它们不会发生,因此它们引发的异常也不会发生.C++ 11标准省略了描述信令NaN的行为,因此也使其未定义.
在备用舍入模式中,允许的优化可能会改变.例如,在Round-to-Negative-Infinity模式下,优化x+0.0 -> x变得允许,但是x-0.0 -> x被禁止.
为了防止GCC采用默认的舍入模式和行为,-frounding-math可以将实验标志传递给GCC.
Clang和GCC,即使在-O3,仍然符合IEEE-754标准.这意味着它必须遵守IEEE-754标准的上述规则.在这些规则下,对于所有人x+0.0来说都不是一致的,但可以选择如此:即,当我们这样做时xxx*1.0
xNaN时的有效载荷不变.* 1.0.x是不为NaN.要启用IEEE-754不安全优化(x+0.0) -> x,-ffast-math需要将标志传递给Clang或GCC.
| 归档时间: |
|
| 查看次数: |
5523 次 |
| 最近记录: |