std::min的参数顺序更改了浮点的编译器输出

我在编译器资源管理器中摆弄,发现传递给std::min的参数顺序改变了发出的程序集

下面是Godbolt编译器资源管理器上的示例

双标准最小xy(双x,双y){
返回标准::最小值(x,y);
}
双标准最小值(双x,双y){
返回标准::最小值(y,x);
}

这是编译的(例如,clang 9.0.0上的-O3),目的是:

std_min_xy(双精度,双精度):#@std_min_xy(双精度,双精度)
分钟xmm1,xmm0
movapd xmm0,xmm1
ret
标准最小值(双精度,双精度):#@标准最小值(双精度,双精度)
分钟xmm0,xmm1
ret

如果我将std::min更改为旧式三值运算符,这种情况将持续存在。它也适用于我试用过的所有现代编译器(clang、gcc、icc)

基本指令是minsd。阅读文档时,minsd的第一个参数也是答案的目的地。显然,xmm0是我的函数应该放置其返回值的地方,因此如果xmm0用作第一个参数,则不需要movapd。但是如果xmm0是第二个参数,那么它必须movapd xmm0,xmm1才能将值转换为xmm0。(编者注:是的,x86-64系统V在xmm0、xmm1等中传递FP参数,并在xmm0中返回。)

我的问题:为什么编译器不切换参数本身的顺序,这样就不需要这个movapd?它当然必须知道,minsd的争论顺序不会改变答案?有没有我不欣赏的副作用

answer=“64083083”的数据

minsd a,b对于某些特殊的FP值是不可交换的,并且std::min,除非您使用-ffast math

分钟a、b准确地执行(a<b)?a:b包括严格IEEE-754语义中关于符号零和NaN的所有含义。(即,它将源操作数b保持为无序1或相等)。正如Artyer指出的,-0.0+0.0比较相等(即-0.为假),但它们是不同的

std::min是根据(a<b)比较表达式(cppreference)和(a<b)定义的?a:b作为一种可能的实现,不同于std::fmin,后者保证从任一操作数进行NaN传播。( fmin 最初来自C数学库,而不是C++模板)

查看在x86上给出无分支FP最小值和最大值的指令是什么?有关mins/minsd/maxss/maxsd(以及相应的内部函数,除某些GCC版本外,它们遵循相同的非交换规则)的更多详细信息

脚注1:记住NaN<对于任何b,以及任何比较谓词,b都为false。e、 g.NaN==b为假,NaN>b。甚至NaN==NaN都是false。当一对中的一个或多个为NaN时,它们是;无序的;wrt。彼此


使用-ffast math(告诉编译器不要假设NaN,以及其他假设和近似值),编译器将两个函数优化为单个分钟。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还阻止了一些即使是wrt也100%安全的优化。异常语义,所以它非常糟糕。在不使用fenv.h的代码中,您永远不会知道其中的区别

但将std::min视为可交换的,只能通过假设没有NaN之类的选项来实现,所以绝对不能称之为"“安全”用于关心NAN到底发生了什么的代码。e、 g.-仅有限数学假设没有n(也没有无穷大)

clang-funsafe数学优化-ffinite math only将完成您想要的优化。(不安全的数学优化意味着一系列更具体的选项,包括不关心符号零语义)

发表评论