问题描述
我正在尝试使用 clang 和 gcc 交叉编译一个项目,但是在使用 _mm_max_ss
时我看到了一些奇怪的差异,例如
__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);
现在,当涉及到 NaN 时,我预计 std::max
类型的行为,但 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
_mm_max_ps 当我使用它时会做预期的事情。我试过使用 -ffast-math
,-fno-fast-math
但它似乎没有效果。有什么想法可以使不同编译器的行为相似吗?
解决方法
我的理解是 IEEE-754 要求:(NaN cmp x)
返回 false
for all cmp
操作符 {==,<,<=,>,>=}
,除了 {{1} } 返回 {!=}
。 true
函数的实现可以根据任何不等运算符进行定义。
那么,问题是,max()
是如何实现的?用_mm_max_ps
,还是有点比较?
有趣的是,当禁用链接中的优化时,gcc 和 clang 都会使用相应的 {<,>=}
指令。两者产量:
maxss
这表明,给定:2.000000 0.000000 0.000000 0.000000
nan 0.000000 0.000000 0.000000
,即:max(NaN,2.0f) -> 2.0f
,其中 max(a,b) = (a op b) ? a : b
是:op
之一。根据 IEEE-754 规则,此比较的结果始终为假,因此:
{<,>=}
总是 false,返回 (NaN op val)
,(val)
总是 false,返回 (val op NaN)
启用优化后,编译器可以在编译时自由地预先计算 (NaN)
和 (c)
。似乎 clang 将结果评估为 (d)
指令将 - 更正“as-if”行为。 GCC 要么退回到 maxss
的另一种实现上——它使用 GMP 和 MPFR 库来处理编译时数字——或者只是对 max()
语义粗心大意。
GCC 在 Godbolt 上的 10.2 和主干版本仍然出错。所以我认为你发现了一个错误!我还没有回答第二部分,因为我想不出一个通用的 hack 可以有效地解决这个问题。
来自英特尔的 ISA 参考:
如果被比较的值都是 0.0s(任一符号),则值 在第二个源操作数中返回。如果第二个值 源操作数是一个 SNaN,SNaN 原样返回给 目标(即不返回 SNaN 的 QNaN 版本)。
如果该指令只有一个值是 NaN(SNaN 或 QNaN),则 第二个源操作数,可以是 NaN 或有效的浮点值, 写入结果。如果不是这种行为,则需要 返回来自任一源操作数的 NaN,动作 可以使用一系列指令来模拟 MAXSS,例如, 比较后跟 AND、ANDN 和 OR。