为什么编译器不能优化带0的浮点加法?
Why can the compiler not optimize floating point addition with 0?
我有四个身份函数,它们基本上什么都不做。只有与 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;
}
下面的编译器输出是:(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
我能理解为什么id0
和id2
无法优化。他们增加了可能变成正无穷大的值,第二次操作不会把它改回来。
可是为什么id1
优化不了呢?与无穷大相加会产生无穷大,与任何常规数字相加会产生该数字,与 NaN
相加会产生 NaN
。那为什么不是像* 1
.
这样的"true"恒等运算
IEEE 754 浮点数有两个零值,一负一正。加起来就是正数。
所以 id1(-0.f)
是 0.f
,而不是 -0.f
。
请注意 id1(-0.f) == -0.f
因为 0.f == -0.f
.
另请注意,在 GCC 中使用 -ffast-math
进行编译确实会进行优化并更改结果。
"I have four identity functions which do essentially nothing."
这不是真的。
对于浮点数x + 1 - 1
不等于x + 0
,等于(x + 1) - 1
。所以如果你有例如非常小的 x
那么您将在 x + 1
步骤中丢失那非常小的部分,并且编译器无法知道那是否是您的意图。
而在 x * 2 / 2
的情况下,由于浮点精度,x * 2
也可能不准确,所以你这里有类似的情况,编译器不知道你是否出于某种原因想以这种方式更改 x
的值。
所以它们是相等的:
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;
}
所需的行为肯定可以用另一种方式定义。但是正如 已经提到的,必须使用 -ffast-math
显式激活此优化
Enable fast-math mode. This option lets the compiler make aggressive, potentially-lossy assumptions about floating-point math. These include:
- Floating-point math obeys regular algebraic rules for real numbers (e.g. + and * are associative, x/y == x * (1/y), and (a + b) * c == a * c + b * c),
- Operands to floating-point operations are not equal to NaN and Inf, and
- +0 and -0 are interchangeable.
fast-math
是 clang 和 gcc 的标志集合(这里是 clang 列出的标志):
- -fno-honor-infinities
- -fno-honor-nans
- -fno-math-errno
- -ffinite-math
- -fassociative-math
- -freciprocal-math
- -fno-signed-zeros
- -fno-trapping-math
- -ffp-contract=fast
阅读floating-number-gui.de web page, more about IEEE 754, the C11 standard n1570, the C++11 standard n3337。
float id1(float x) {
return x + 0;
}
如果 x
恰好是信号 NaN,你的 id1
甚至可能不是 return(并且可能 不应该 return).
如果 x
是一个安静的 NaN,那么 id1(x) != x
因为 NaN != NaN
(至少 NaN == NaN
应该是 false)。
在一些情况下,您需要昂贵的arbitrary precision arithmetic. Then consider using GMPlib。
PS。浮点数可以让你做噩梦或获得博士学位,由你选择。他们有时 kill people 或至少造成巨大的金融灾难(例如损失数亿美元或欧元)。
我有四个身份函数,它们基本上什么都不做。只有与 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;
}
下面的编译器输出是:(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
我能理解为什么id0
和id2
无法优化。他们增加了可能变成正无穷大的值,第二次操作不会把它改回来。
可是为什么id1
优化不了呢?与无穷大相加会产生无穷大,与任何常规数字相加会产生该数字,与 NaN
相加会产生 NaN
。那为什么不是像* 1
.
IEEE 754 浮点数有两个零值,一负一正。加起来就是正数。
所以 id1(-0.f)
是 0.f
,而不是 -0.f
。
请注意 id1(-0.f) == -0.f
因为 0.f == -0.f
.
另请注意,在 GCC 中使用 -ffast-math
进行编译确实会进行优化并更改结果。
"I have four identity functions which do essentially nothing."
这不是真的。
对于浮点数x + 1 - 1
不等于x + 0
,等于(x + 1) - 1
。所以如果你有例如非常小的 x
那么您将在 x + 1
步骤中丢失那非常小的部分,并且编译器无法知道那是否是您的意图。
而在 x * 2 / 2
的情况下,由于浮点精度,x * 2
也可能不准确,所以你这里有类似的情况,编译器不知道你是否出于某种原因想以这种方式更改 x
的值。
所以它们是相等的:
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;
}
所需的行为肯定可以用另一种方式定义。但是正如 -ffast-math
Enable fast-math mode. This option lets the compiler make aggressive, potentially-lossy assumptions about floating-point math. These include:
- Floating-point math obeys regular algebraic rules for real numbers (e.g. + and * are associative, x/y == x * (1/y), and (a + b) * c == a * c + b * c),
- Operands to floating-point operations are not equal to NaN and Inf, and
- +0 and -0 are interchangeable.
fast-math
是 clang 和 gcc 的标志集合(这里是 clang 列出的标志):
- -fno-honor-infinities
- -fno-honor-nans
- -fno-math-errno
- -ffinite-math
- -fassociative-math
- -freciprocal-math
- -fno-signed-zeros
- -fno-trapping-math
- -ffp-contract=fast
阅读floating-number-gui.de web page, more about IEEE 754, the C11 standard n1570, the C++11 standard n3337。
float id1(float x) {
return x + 0;
}
如果 x
恰好是信号 NaN,你的 id1
甚至可能不是 return(并且可能 不应该 return).
如果 x
是一个安静的 NaN,那么 id1(x) != x
因为 NaN != NaN
(至少 NaN == NaN
应该是 false)。
在一些情况下,您需要昂贵的arbitrary precision arithmetic. Then consider using GMPlib。
PS。浮点数可以让你做噩梦或获得博士学位,由你选择。他们有时 kill people 或至少造成巨大的金融灾难(例如损失数亿美元或欧元)。