gpt4 book ai didi

c++ - 如何强制 GCC 假定浮点表达式为非负数?

转载 作者:行者123 更新时间:2023-12-01 19:27:22 25 4
gpt4 key购买 nike

在某些情况下,您知道某个浮点表达式始终为非负数。例如,在计算 vector 的长度时,会出现 sqrt(a[0]*a[0] + ... + a[N-1]*a[N-1]) (注意:我知道 std::hypot ,这与问题无关),并且平方根下的表达式显然是非负的。然而,海湾合作委员会outputs以下组件适用于 sqrt(x*x) :

        mulss   xmm0, xmm0
pxor xmm1, xmm1
ucomiss xmm1, xmm0
ja .L10
sqrtss xmm0, xmm0
ret
.L10:
jmp sqrtf

即比较 x*x的结果为零,如果结果为非负,则执行 sqrtss指令,否则调用 sqrtf .

所以,我的问题是: 我怎样才能强制 GCC 假设 x*x总是非负的,所以它跳过比较和 sqrtf调用,而不编写内联汇编?

我想强调一下,我对本地解决方案感兴趣,而不是做类似 -ffast-math 的事情。 , -fno-math-errno , 或 -ffinite-math-only (尽管这些确实解决了问题,感谢 ks1322、harold 和 Eric Postpischil 在评论中)。

此外,“强制 GCC 假设 x*x 为非负”应解释为 assert(x*x >= 0.f) ,所以这也排除了 x*x 的情况是 NaN。

我对特定于编译器、特定于平台、特定于 CPU 等的解决方案没有意见。

最佳答案

你可以写assert(x*x >= 0.f)作为编译时 promise 而不是运行时检查,在 GNU C 中如下所示:

#include <cmath>

float test1 (float x)
{
float tmp = x*x;
if (!(tmp >= 0.0f))
__builtin_unreachable();
return std::sqrt(tmp);
}

(相关: What optimizations does __builtin_unreachable facilitate? 你也可以将 if(!x)__builtin_unreachable() 包裹在一个宏中,并称它为 promise() 或其他。)

但是 gcc 不知道如何利用 tmp 的 promise 。是非 NaN 和非负数。我们仍然得到 ( Godbolt ) 相同的封装 asm 序列,用于检查 x>=0否则调用 sqrtf设置 errno . 据推测,在其他优化通过之后,扩展为比较和分支,所以编译器了解更多信息无济于事。

这是推测性内联 sqrt 的逻辑中的遗漏优化当 -fmath-errno已启用(不幸的是默认情况下启用)。

你想要的是 -fno-math-errno ,这是全局安全的

如果您不依赖数学函数设置 errno,这是 100% 安全的。 .没有人想要那样,这就是 NaN 传播和/或记录屏蔽 FP 异常的粘性标志的用途。例如C99/C++11 fenv 通过 #pragma STDC FENV_ACCESS ON 访问然后函数像 fetestexcept() .请参阅 feclearexcept 中的示例这表明使用它来检测除以零。

FP 环境是线程上下文的一部分,而 errno是全局性的。

对这个过时的错误功能的支持不是免费的;除非您编写了旧代码来使用它,否则您应该关闭它。不要在新代码中使用它:使用 fenv .理想支持 -fmath-errno会尽可能便宜,但很少有人真正使用 __builtin_unreachable()或其他排除 NaN 输入的事情可能使开发人员不值得花时间实现优化。不过,如果您愿意,您可以报告遗漏优化错误。

现实世界的 FPU 硬件实际上确实有这些粘性标志,这些标志在清除之前一直保持设置,例如 x86's mxcsr SSE/AVX 数学或其他 ISA 中的硬件 FPU 的状态/控制寄存器。在 FPU 可以检测异常的硬件上,高质量的 C++ 实现将支持诸如 fetestexcept() 之类的东西。 .如果不是,那么数学- errno可能也不起作用。
errno因为数学是一个陈旧的过时设计,默认情况下 C/C++ 仍然坚持使用,现在被广泛认为是一个坏主意。它使编译器更难有效地内联数学函数。或者也许我们并不像我想的那样坚持下去: Why errno is not set to EDOM even sqrt takes out of domain arguement?解释了在数学函数中设置 errno 在 ISO C11 中是可选的,并且实现可以指示它们是否这样做。大概也在 C++ 中。

大错特错-fno-math-errno与值(value)改变优化类似 -ffast-math-ffinite-math-only . 您应该强烈考虑全局启用它,或者至少为包含此功能的整个文件启用它。
float test2 (float x)
{
return std::sqrt(x*x);
}
# g++ -fno-math-errno -std=gnu++17 -O3
test2(float): # and test1 is the same
mulss xmm0, xmm0
sqrtss xmm0, xmm0
ret

您不妨使用 -fno-trapping-math同样,如果您不打算使用 feenableexcept() 来揭开任何 FP 异常的掩码. (尽管此优化不需要该选项,但只有 errno 设置废话是这里的问题。)。
-fno-trapping-math不假设没有 NaN 或任何东西,它只假设像 Invalid 或 Inexact 这样的 FP 异常不会实际调用信号处理程序,而不是产生 NaN 或舍入结果。 -ftrapping-math是默认值,但 it's broken and "never worked" according to GCC dev Marc Glisse . (即使启用它,GCC 也会进行一些优化,这些优化可以将异常数量从零变为非零,反之亦然。并且它会阻止一些安全优化)。但不幸的是, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54192 (默认关闭)仍然打开。

如果您确实曾经取消屏蔽异常,最好使用 -ftrapping-math ,但同样非常罕见的是,您想要它而不是在一些数学运算之后检查标志,或检查 NaN。无论如何,它实际上并没有保留确切的异常语义。

SIMD for float threshold operation对于 -fno-trapping-math 的情况错误地阻止了安全优化。 (即使在提升了一个潜在的陷阱操作以便 C 无条件地执行它之后,gcc 也会使非向量化的 asm 有条件地执行它!因此它不仅阻止向量化,而且与 C 抽象机相比,它改变了异常语义。)

关于c++ - 如何强制 GCC 假定浮点表达式为非负数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57673825/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com