Rav*_*ole 75 c++ floating-point x86 assembly android
我在编译器资源管理器中摆弄,我发现传递给 std::min 的参数顺序改变了发出的程序集。
这是 Godbolt Compiler Explorer 上的示例
double std_min_xy(double x, double y) {
return std::min(x, y);
}
double std_min_yx(double x, double y) {
return std::min(y, x);
}
Run Code Online (Sandbox Code Playgroud)
这被编译(例如,在 clang 9.0.0 上使用 -O3):
std_min_xy(double, double): # @std_min_xy(double, double)
minsd xmm1, xmm0
movapd xmm0, xmm1
ret
std_min_yx(double, double): # @std_min_yx(double, double)
minsd xmm0, xmm1
ret
Run Code Online (Sandbox Code Playgroud)
如果我将 std::min 更改为老式三元运算符,这种情况仍然存在。它也适用于我尝试过的所有现代编译器(clang、gcc、icc)。
底层指令是minsd. 阅读文档,第一个参数minsd也是答案的目的地。显然 xmm0 是我的函数应该放置其返回值的地方,所以如果 xmm0 用作第一个参数,则movapd不需要。但是如果 xmm0 是第二个参数,那么它必须movapd xmm0, xmm1将值放入 xmm0。(编者注:是的,x86-64 System V在 xmm0、xmm1 等中传递 FP 参数,并在 xmm0 中返回。)
我的问题:为什么编译器不切换参数本身的顺序,这样movapd就没有必要了?它肯定必须知道 minsd 的参数顺序不会改变答案?是否有一些我不欣赏的副作用?
Pet*_*des 78
minsd a,b对于某些特殊的 FP 值不是可交换的,也不是std::min,除非您使用-ffast-math.
minsd a,b 完全实现(a<b) ? a : b包括在严格的 IEEE-754 语义中暗示有关有符号零和 NaN 的所有内容。(即它保持源操作数,b,无序1或等于)。作为Artyer指出,-0.0和+0.0比较相等(即-0. < 0.是假的),但它们是不同的。
std::min是根据(a<b)比较表达式 ( cppreference ) 定义的,(a<b) ? a : b作为一种可能的实现,不同于std::fmin它保证从任一操作数传播 NaN,等等。(fmin最初来自 C 数学库,而不是 C++ 模板。)
请参阅在 x86 上提供无分支 FP 最小值和最大值的指令是什么?有关 minss/minsd / maxss/maxsd 的更多详细信息(以及相应的内在函数,它们遵循相同的非交换规则,但在某些 GCC 版本中除外。)
脚注 1:请记住,这NaN<b对于 anyb和任何比较谓词都是错误的。egNaN == b是假的,所以也是NaN > b。甚至NaN == NaN是假的。当一对中的一个或多个是 NaN 时,它们是“无序”的。彼此。
使用-ffast-math(告诉编译器假设没有 NaN 以及其他假设和近似值),编译器会将任一函数优化为单个minsd. https://godbolt.org/z/a7oK91
对于 GCC,请参阅https://gcc.gnu.org/wiki/FloatingPointMath
clang 支持类似的选项,包括-ffast-math作为一个包罗万象的选项。
几乎所有人都应该启用其中的一些选项,除了奇怪的遗留代码库,例如-fno-math-errno. (有关推荐的数学优化的更多信息,请参阅此问答)。并且 gcc-fno-trapping-math是一个好主意,因为它无论如何都不能完全工作,尽管默认情况下是打开的(一些优化仍然可以更改如果未屏蔽异常将引发的 FP 异常的数量,有时甚至从 1 到 0 或 0 到非零,IIRC)。 gcc -ftrapping-math还会阻止一些即使是 100% 安全的优化。异常语义,所以很糟糕。在不使用 的代码中fenv.h,您永远不会知道其中的区别。
但是std::min只能通过假设没有 NaN 之类的选项才能将其视为可交换的,因此对于关心 NaN 究竟会发生什么的代码来说,绝对不能称为“安全”。例如,-ffinite-math-only假设没有 NaN(也没有无穷大)
clang -funsafe-math-optimizations -ffinite-math-only会做你正在寻找的优化。(不安全的数学优化意味着一堆更具体的选项,包括不关心符号零语义)。
Art*_*yer 14
考虑:std::signbit(std::min(+0.0, -0.0)) == false && std::signbit(std::min(-0.0, +0.0)) == true。
唯一的另一个区别是,如果两个参数都是(可能不同的)NaN,则应返回第二个参数。
您可以允许 gcc 使用-funsafe-math-optimizations -fno-math-errno优化(均由 启用-ffast-math)重新排序参数。unsafe-math-optimizations允许编译器不关心有符号零,finite-math-only也不关心 NaN
为了扩大对现有的回答是说std::min是不可交换:这是一个具体的例子是可靠的区别std_min_xy从std_min_yx。神弩:
bool distinguish1() {
return 1 / std_min_xy(0.0, -0.0) > 0.0;
}
bool distinguish2() {
return 1 / std_min_yx(0.0, -0.0) > 0.0;
}
Run Code Online (Sandbox Code Playgroud)
distinguish1()计算为1 / 0.0 > 0.0,即INFTY > 0.0,或true。
distinguish2()计算为1 / -0.0 > 0.0,即-INFTY > 0.0,或false。
(当然,所有这些都符合 IEEE 规则。我认为 C++ 标准并没有要求编译器保留这种特定的行为。老实说,我很惊讶表达式-0.0实际上首先评估为负零!
-ffinite-math-only消除了这种区分差异的方式,并-ffinite-math-only -funsafe-math-optimizations完全消除了 codegen 中的差异。
| 归档时间: |
|
| 查看次数: |
2771 次 |
| 最近记录: |