如何让 GCC (C++) 停止简化算术表达式?
Posted
技术标签:
【中文标题】如何让 GCC (C++) 停止简化算术表达式?【英文标题】:How do I get GCC (C++) to stop simplifying arithmetic expressions? 【发布时间】:2020-05-15 22:28:48 【问题描述】:我正在制作一个有趣的物理引擎。 我试图让它即使在低滴答率下也很可靠,所以我在浮点算术和精度上做着危险的舞蹈。
在调试时,我最终运行了这段代码:
#define A 0.2063387632369995100000000000000f
#define B 0.7307806611061096200000000000000f
float a = A;
float b = B;
float floatie1 = A + (B * ((A)/(-B)));
float floatie2 = a + (b * ((a)/(-b)));
printf("%.40f\n", floatie1);
printf("%.40f\n", floatie2);
输出是:
0.0000000149011611938476560000000000000000
0.0000000000000000000000000000000000000000
以及每个(分别)的位:
00110010100000000000000000000000
00000000000000000000000000000000
我知道该表达式的计算结果应该为 0。 我不希望它的计算结果为零,因为我可能在代码中的某处有一些其他不相关的算术,它会给我一个以完全相同的方式不精确的值,如果我然后减去 2,它将是 0。 我不希望我的表达在任意情况下被简化或优化。
我尝试过使浮点数不稳定并关闭优化无济于事。
我也尝试过-f-associative-math
、-ftrapping-math
和-funsafe-math-optimizations
,以及它们的no-
变体的组合。
如果我分解表达式,将内容放入不同的变量中,一次只做一件事,它会起作用,但我不想每次写一段代码时都背着我。
MSVC++ 使用此代码为我提供了正确的结果。
版本:gcc (MinGW.org GCC-6.3.0-1) 6.3.0
windows 8.1。
如何关闭此功能?
【问题讨论】:
你为什么不为此使用一个理智的函数,比如remainderf(A, B)
?
@EOF 因为这与该表达式(或派生该表达式的原始表达式)应该做什么无关。
这里有几点需要说明。首先,你是为 32 位编译的吗?如果是这样,一个很好的猜测是问题源于可恶的 x87 FPU,默认情况下它在 32 位模式下使用,并且不(完全)符合 ieee754。在为 x86_64 Linux 编译 gcc
时,我无法重现您的问题。您自己编写的不稳定的 remainderf()
会为编译时和运行时计算产生相同(不稳定)的结果,而库 remainderf()
会产生正确的结果。
如果这不是另一个x87脑损伤的受害者,我会感到惊讶。在编译时,gcc
尊重 ieee754,因为为什么不呢,那时它不会花费任何成本。在运行时,x87 FPU 需要 可怕 的性能成本才能符合 ieee754,因为要真正以float
精度进行计算,每个中间结果都必须存储到内存中并随后重新加载以继续计算。太疯狂了,编译器通常只在必须时才这样做。您可以通过将每个中间结果存储到变量(提示、提示)或将ffloat-store
传递给gcc
来强制它。
您可以通过一些#pragma push #pragma GCC optimize (STRING, ...) [...] #pragma pop
或__attribute__((optimize (STRING, ...)))
魔法启用-ffloat-store
-deoptimization。
【参考方案1】:
所有功劳归EOF。
我问错问题了。简化表达式并不是真正的 GCC。
显然 GCC 在默认情况下对 32 位进行了优化,使其使用更少的 x87 FPU 指令。尽管速度更快,但并不能真正保证符合 IEEE 754,因为使用了引擎盖下的双精度(EOF 在他的comment 中给出了更详细的解释)。
结果不同的原因是因为 GCC 在编译时使用了符合 IEEE 754 的算法来计算第一个表达式的值,而对于第二个表达式(运行时),它使用了不符合标准的 FPU 指令.
为了避免这些指令(并记住它会使代码变慢),可以使用-ffloat-store
作为编译选项。
或者,如果您不希望整个程序变慢,请使用a pragma 或函数属性:
void __attribute__((optimize ("-ffloat-store"))) func(float a, float b)
//your code
注意:可以选择尝试定位SSE extensions,而不是x87 FPU,它们既符合IEEE 754 标准,也是x86_64 的基准。更多信息here。
【讨论】:
ffloat-store
不是优化。它严重地去优化代码,以换取硬件上的 ieee754 一致性,该硬件符合有效早于 ieee754 的设计。您可能要注意,您还可以通过针对更新的(现在也很老的)向量/浮点单元(SSE 和更高版本)来获得 ieee754 一致性,它始终是 x86_64 的一部分。 x86_64 的默认编译器目标是符合 ieee754 的标准,并且不会影响性能。
@EOF 已编辑。抱歉,我的想法是正确的,但那时我的睡眠不足。也添加了注释。谢谢!
-ffloat-store
不会避免使用一些“更快的指令”,而是使用相同的指令,但更多的指令(特别是更多的浮点加载/存储指令)。
@EOF 我很困惑。如果它使用相同的指令,结果怎么会不同?
问题在于 x87 FPU 的设计。 x87 FPU 由一组 8 个 80 位 extended double
浮点寄存器组成。没有办法让 x87 FPU 存储 32 位 float
变量(AFAIR,它总是使用 16 位的扩展指数范围,而不是 float
或 double
的 8 / 11 位)。将中间结果正确转换为符合 ieee754 的float
的唯一 方法是将值存储到内存中(它是 ieee754 格式),然后重新加载该值,所以它不再具有扩展的精度/范围。以上是关于如何让 GCC (C++) 停止简化算术表达式?的主要内容,如果未能解决你的问题,请参考以下文章