Big*_*ith 6 c++ x86 gcc sse clang
我正在尝试使用 clang 和 gcc 交叉编译一个项目,但是在使用_mm_max_ss
eg时我看到了一些奇怪的差异
__m128 a = _mm_set_ss(std::numeric_limits<float>::quiet_NaN());
__m128 b = _mm_set_ss(2.0f);
__m128 c = _mm_max_ss(a,b);
__m128 d = _mm_max_ss(b,a);
Run Code Online (Sandbox Code Playgroud)
现在我期望std::max
涉及 NaN 时的类型行为,但 clang 和 gcc 给出不同的结果:
Clang: (what I expected)
c: 2.000000 0.000000 0.000000 0.000000
d: nan 0.000000 0.000000 0.000000
Gcc: (Seems to ignore order)
c: nan 0.000000 0.000000 0.000000
d: nan 0.000000 0.000000 0.000000
Run Code Online (Sandbox Code Playgroud)
_mm_max_ps 在我使用它时会做预期的事情。我试过使用-ffast-math
,-fno-fast-math
但它似乎没有效果。有什么想法可以使编译器之间的行为相似吗?
Godbolt 链接在这里
我的理解是 IEEE-754 要求:(NaN cmp x)
返回所有运算false
符,除了返回。函数的实现可以根据任何不等式运算符来定义。 cmp
{==, <, <=, >, >=}
{!=}
true
max()
那么,问题来了,如何_mm_max_ps
实施呢?与{<, <=, >, >=}
,还是有点比较?
有趣的是,当禁用链接优化时,maxss
gcc 和 clang 都会使用相应的指令。两者产量:
2.000000 0.000000 0.000000 0.000000
nan 0.000000 0.000000 0.000000
Run Code Online (Sandbox Code Playgroud)
这表明,给定:max(NaN, 2.0f) -> 2.0f
,即:max(a, b) = (a op b) ? a : b
,其中op
是其中之一:{<, <=, >, >=}
。根据 IEEE-754 规则,此比较的结果始终为 false,因此:
(NaN op val)
始终为假,返回(val)
,
(val op NaN)
始终为假,返回(NaN)
启用优化后,编译器可以在编译时自由进行预计(c)
算(d)
。看来 clangmaxss
会按照指令评估结果 - 纠正“假设”行为。GCC 要么依赖于另一种实现max()
- 它使用 GMP 和 MPFR 库来进行编译时数字 - 或者只是对语义不小心_mm_max_ss
。
GCC 在 godbolt 上的 10.2 和 trunk 版本上仍然出现错误。所以我认为你发现了一个错误!我还没有回答第二部分,因为我想不出可以有效解决这个问题的万能黑客。
来自 Intel 的 ISA 参考:
如果比较的值都是 0.0s(任一符号),则返回第二个源操作数中的值。如果第二个源操作数中的值是 SNaN,则该 SNaN 会原封不动地返回到目标(即,不返回 SNaN 的 QNaN 版本)。
如果该指令只有一个值为 NaN(SNaN 或 QNaN),则将第二个源操作数(NaN 或有效浮点值)写入结果。如果需要返回来自任一源操作数的 NaN,而不是此行为,则可以使用一系列指令来模拟 MAXSS 的操作,例如,比较后跟 AND、ANDN 和 OR。