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

dojqjjoe  于 2024-01-04  发布在  Android
关注(0)|答案(3)|浏览(211)

我在浏览器Explorer中摆弄了一下,发现传递给std::min的参数顺序会改变发出的程序集。
下面是Godbolt浏览器的示例

  1. double std_min_xy(double x, double y) {
  2. return std::min(x, y);
  3. }
  4. double std_min_yx(double x, double y) {
  5. return std::min(y, x);
  6. }

字符串
这被编译(例如,在clang 9.0.0上使用-O3),以:

  1. std_min_xy(double, double): # @std_min_xy(double, double)
  2. minsd xmm1, xmm0
  3. movapd xmm0, xmm1
  4. ret
  5. std_min_yx(double, double): # @std_min_yx(double, double)
  6. minsd xmm0, xmm1
  7. ret


如果我将std::min改为一个老式的三元运算符,它仍然存在,在我尝试的所有现代编译器(clang,gcc,icc)中也仍然存在。
底层指令是minsd。阅读文档,minsd的第一个参数也是答案的目的地。显然xmm 0是我的函数应该放置其返回值的地方,所以如果xmm 0用作第一个参数,就不需要movapd。但是如果xmm 0是第二个参数,然后它必须通过movapd xmm0, xmm1将值传递到xmm 0中。(编者注:是的,x86-64 System V在xmm 0、xmm 1等中传递FP参数,并在xmm 0中返回。)
我的问题是:为什么编译器不改变参数本身的顺序,这样movapd就不必要了?它肯定知道minsd的参数顺序不会改变答案?是不是有什么副作用我不喜欢?

t9aqgxwy

t9aqgxwy1#

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

minsd a,bexactly 实现了(a<b) ? a : b,包括严格IEEE-754语义中关于符号零和NaN的所有含义。(即它保持源操作数b为unordered 1或相等)。正如Artyer指出的那样,-0.0+0.0比较相等(即-0. < 0.为false),但它们是不同的。
std::min是根据(a<b)比较表达式(cppref)定义的,(a<b) ? a : b是一种可能的实现,不像std::fmin,它保证从任何一个操作数传播NaN。
有关minss/minsd / maxss/maxsd(以及相应的intrinsic,它们遵循相同的非交换规则,但在某些GCC版本中除外)的更多细节,请参见What is the instruction that gives branchless FP min and max on x86?
脚注1:记住,NaN<b对于任何b都是假的,对于任何比较 predicate 都是假的。例如,NaN == b是假的,NaN > b也是假的。甚至NaN == NaN也是假的。当一对中的一个或多个是NaN时,它们彼此之间是“无序的”。
| 一|B| a < B| std::min(a,b) = minsd a,b|
| --|--|--|--|
| 楠|楠|false(无序)|a(NaN)|
| 楠|非NaN| false(无序)|a(NaN)|
| 非NaN|楠|false(无序)|a(非NaN); fmin将给予NaN|
| 非NaN|非NaN|取决于价值观|ab,越接近-Inf|
| -0.0| +0.0| false(equal)|a(-0.0)|
| +0.0|-0.0| false(equal)|a(+0.0)|
请注意,+-Infinity在与有限值的比较中工作良好,所以我不得不写“non-NaN”而不是“finite”。

使用-ffast-math(告诉编译器不假设NaN,以及其他假设和近似),编译器 * 将 * 将任一函数优化为单个minsdhttps://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. exception语义也是100%安全的优化,所以它非常糟糕。在不使用fenv.h的代码中,你永远不会知道其中的区别。

但是将std::min视为可交换的只能通过假设没有NaN之类的选项来实现,所以对于那些关心NaN发生了什么的代码来说,绝对不能被称为“安全”

clang -funsafe-math-optimizations -ffinite-math-only将做你正在寻找的优化。(unsafe-math-optimizations意味着一堆更具体的选项,包括不关心有符号的零语义)。

展开查看全部
wz1wpwve

wz1wpwve2#

考虑: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

ui7jx7zq

ui7jx7zq3#

为了扩展现有的说std::min不是可交换的答案:这里有一个具体的例子,可以可靠地区分std_min_xystd_min_yx

  1. bool distinguish1() {
  2. return 1 / std_min_xy(0.0, -0.0) > 0.0;
  3. }
  4. bool distinguish2() {
  5. return 1 / std_min_yx(0.0, -0.0) > 0.0;
  6. }

字符串
distinguish1()计算为1 / 0.0 > 0.0,即INFTY > 0.0true
distinguish2()计算为1 / -0.0 > 0.0,即-INFTY > 0.0false
(All当然,这是在IEEE规则下。我不认为C++标准 * 强制 * 编译器保留这种特定的行为。老实说,我很惊讶,表达式-0.0实际上首先计算为负零!
-ffinite-math-only eliminates this way of telling the difference-ffinite-math-only -funsafe-math-optimizations completely eliminates the difference in codegen

相关问题