你可以写assert(x*x >= 0.f)
在 GNU C 中作为编译时承诺而不是运行时检查,如下所示:
#include <cmath>
float test1 (float x)
{
float tmp = x*x;
if (!(tmp >= 0.0f))
__builtin_unreachable();
return std::sqrt(tmp);
}
(有关的:__builtin_unreachable 有助于哪些优化? https://stackoverflow.com/questions/54764535/what-optimizations-does-builtin-unreachable-facilitate你也可以包裹if(!x)__builtin_unreachable()
在宏中并调用它promise()
或者其他的东西。)
但海湾合作委员会不知道如何利用这一承诺tmp
是非 NaN 且非负。我们仍然得到(Godbolt https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(j:1,lang:c%2B%2B,source:%27%23include+%3Ccmath%3E%0A%0Afloat+test1+(float+x)%0A%7B%0A++++float+tmp+%3D+x*x%3B%0A++++if+(!!(tmp+%3E%3D+0.0f))+%0A++++++++__builtin_unreachable()%3B++++%0A++++return+std::sqrt(tmp)%3B%0A%7D%0A%0Afloat+test2+(float+x)%0A%7B%0A++++return+std::sqrt(x*x)%3B%0A%7D%0A%27),l:%275%27,n:%270%27,o:%27C%2B%2B+source+%231%27,t:%270%27)),k:36.64524356415925,l:%274%27,m:100,n:%270%27,o:%27%27,s:0,t:%270%27),(g:!((h:compiler,i:(compiler:g92,filters:(b:%270%27,binary:%271%27,commentOnly:%270%27,demangle:%270%27,directives:%270%27,execute:%271%27,intel:%270%27,libraryCode:%271%27,trim:%271%27),lang:c%2B%2B,libs:!((name:rangesv3,ver:%27036%27)),options:%27-fno-math-errno+-std%3Dgnu%2B%2B17+-O3%27,source:1),l:%275%27,n:%270%27,o:%27x86-64+gcc+9.2+(Editor+%231,+Compiler+%231)+C%2B%2B%27,t:%270%27)),header:(),k:30.021423102507427,l:%274%27,m:100,n:%270%27,o:%27%27,s:0,t:%270%27),(g:!((h:compiler,i:(compiler:gsnapshot,filters:(b:%270%27,binary:%271%27,commentOnly:%270%27,demangle:%270%27,directives:%270%27,execute:%271%27,intel:%270%27,libraryCode:%271%27,trim:%271%27),lang:c%2B%2B,libs:!((name:rangesv3,ver:%27036%27)),options:%27-std%3Dgnu%2B%2B17+-O3+-fno-trapping-math%27,source:1),l:%275%27,n:%270%27,o:%27x86-64+gcc+(trunk)+(Editor+%231,+Compiler+%232)+C%2B%2B%27,t:%270%27)),k:33.33333333333333,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27)),l:%272%27,m:100,n:%270%27,o:%27%27,t:%270%27)),version:4) 检查的相同的固定 asm 序列x>=0
并以其他方式调用sqrtf
to set errno
. 据推测,扩展为比较和分支是在其他优化过程之后发生的,所以编译器了解更多信息并没有帮助。
这是推测性内联逻辑中的一个错过的优化sqrt
when -fmath-errno
已启用(不幸的是默认情况下启用)。
你想要的是-fno-math-errno
,这在全球范围内都是安全的
如果您不依赖数学函数设置,那么这是 100% 安全的errno
。没有人想要这样,这就是 NaN 传播和/或记录屏蔽 FP 异常的粘性标志的用途。例如C99/C++11fenv https://en.cppreference.com/w/cpp/numeric/fenv通过访问#pragma STDC FENV_ACCESS ON
然后像这样的函数fetestexcept() https://en.cppreference.com/w/cpp/numeric/fenv/fetestexcept。请参阅中的示例feclearexcept https://en.cppreference.com/w/cpp/numeric/fenv/feclearexcept它显示了使用它来检测除零。
FP 环境是线程上下文的一部分,而errno
是全球性的。
对这个过时的错误功能的支持不是免费的;您应该将其关闭,除非您有编写使用它的旧代码。不要在新代码中使用它:使用fenv
。理想情况下支持-fmath-errno
会尽可能便宜,但实际使用的人很少__builtin_unreachable()
或其他排除 NaN 输入的因素可能会让开发人员不值得花时间来实现优化。不过,如果您愿意,您可以报告错过优化的错误。
事实上,现实世界的 FPU 硬件确实有这些粘性标志,这些标志在清除之前一直保持设置状态,例如x86's mxcsr http://softpixel.com/%7Ecwright/programming/simd/sse.php用于 SSE/AVX 数学的状态/控制寄存器,或其他 ISA 中的硬件 FPU。在 FPU 可以检测异常的硬件上,高质量的 C++ 实现将支持诸如fetestexcept()
。如果没有,那么数学-errno
可能也不起作用。
errno
for math 是一个过时的设计,C / C++ 默认情况下仍然沿用它,现在被广泛认为是一个坏主意。它使编译器更难有效地内联数学函数。或者也许我们并不像我想象的那样坚持下去:为什么 errno 没有设置为 EDOM,即使 sqrt 取出域参数? https://stackoverflow.com/questions/56243525/why-errno-is-not-set-to-edom-even-sqrt-takes-out-of-domain-arguement解释说在数学函数中设置 errno 是optional在 ISO C11 中,实现可以表明他们是否这样做。大概在 C++ 中也是如此。
结块是个大错误-fno-math-errno
进行价值改变的优化,例如-ffast-math
or -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
同样,如果您不打算使用以下方法揭露任何 FP 异常feenableexcept()
。 (虽然此优化不需要该选项,但它只是errno
- 设置废话,这是一个问题。)。
-fno-trapping-math
不假设 no-NaN 或任何东西,它只假设像 Invalid 或 Inexact 这样的 FP 异常不会实际调用信号处理程序,而不是产生 NaN 或舍入结果。-ftrapping-math
是默认值,但是根据 GCC 开发者 Marc Glisse 的说法,它已经损坏并且“从未工作过” https://stackoverflow.com/questions/56670132/simd-for-float-threshold-operation#comment99952463_56681744。 (即使打开了它,GCC 也会进行一些优化,这些优化可以将引发的异常数量从零更改为非零,反之亦然。并且它会阻止一些安全优化)。但不幸的是,https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54192 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54192(默认关闭)仍然打开。
如果您确实曾经揭露过异常,那么最好-ftrapping-math
,但同样,您很少会希望这样做,而不是在一些数学运算后检查标志或检查 NaN。而且它实际上并没有保留精确的异常语义。
See 用于浮点阈值操作的 SIMD https://stackoverflow.com/questions/56670132/simd-for-float-threshold-operation对于这样的情况-ftrapping-math
默认错误地阻止了安全优化。 (即使在提升潜在陷阱操作以便 C 无条件执行此操作之后,gcc 也会生成有条件执行此操作的非向量化汇编!因此,GCC 不仅会阻止向量化,而且还会更改与 C 抽象机相比的异常语义。)-fno-trapping-math
实现预期的优化。