Visual Studio 2026中 std::isnan的最佳代码生成

编程语言 2026-07-12

据我所知,用来测试一个浮点变量是否包含Not-a-Number(std::isnan)的最快汇编代码,是把它与自身进行比较,若不相等就表示 NaN。并且据我所见,GCC和 Clang都会为 std::isnan 生成这样的汇编代码。

但在最新版的Visual Studio中,std::isnan 并不总是经过优化,这可以通过下面的程序看到:

#include <cmath>

bool f( double x, double y ) {
    return std::isnan( x / y );
}

double x, y;

int main() {
    return f( x, y );
}

如果使用大多数速度优化进行编译,/Ox,对于函数 f,MSVC确实会生成最优的汇编:

ucomisd xmm0, xmm0
setp    al

但函数 main 显然并非最优,并且不是 ucomisd,而是对某个内部函数 _dclass 的调用,并对输出进行后处理:

call    _dclass
xor     ecx, ecx
cmp     ax, 2
sete    cl
mov     eax, ecx

在线演示

在MSVC中,决定 std::isnan 将如何被优化,以及如何确保使用 ucomisd 以获得最佳性能?

解决方案

早前的MSVC会把它内联成与 f 相同的汇编,这看起来像是MSVC的回归性错误,应该向官方报告。(如果他们有诸如performance或 missed-optimization之类的标签,请使用它们。)

Regression from v19.43 to v19.44Godbolt


附注:GCC与 Clang -ffast-math 优化为 return 0,因为fast-math暗示了 -ffinite-math-only,在优化时会让编译器假设不存在NaN或无穷大。显然MSVC /fp:fast 就不是那样。

我在想是否有某些选项,可以在某些情况下让它在任一 xy 为NaN时返回true,从而实现 ucomisd x,y / setp,但这显然不适用于除法,其中 0/0 也是NaN。x+yx*y 在某些边界情形下也会出现非NaN导致NaN的情况:-Inf + Inf0 * Inf

(来自问题下的评论的摘录):
@chtz建议把 (x UNORDERED y) || (fabs(x)==fabs(y) && (fabs(x)==0 || fabs(x)==inf)) 作为一种可能的方式,用来检查在 x/y 产生NaN的输入+-0 / +-0+-Inf / +-Inf,以及x 或y 已经是NaN的情形。

也许我们可以用一些巧妙的浮点位操作技巧来优化这些,特别是在可以假设不存在子正规数(denormals)的情况下。(比如在x86的 MXCSR寄存器中的FTZ/DAZ位设置,使非正规结果被冲到零,非正规输入也被视为零,甚至像比较这样的指令也不会产生新的FP值。)

我记得x86的 ptest 可以通过一条指令判断指数字段是全0 还是全1,前提是使用一个在指数字段上只有1 的常量掩码。如果我们可以忽略子正规数,那么就可以在一次ptest/jbe中检查 x== 0(指数为0)、+-InfNaN(指数全为1)。_mm_test_mix_ones_zeros 在一个 ptest 的结果上是 ZF == 0 && CF == 0,语义似乎只对掩码所选的位有效。那就是 jajnajbe 是相反的条件。所以我们可以从除数上开始进行 ptest/jbe。不过,不能省略 x UNORD y,因此需要把多次比较结果结合起来,要么通过分支,要么通过 setcc/or

当然,一般来说像GCC/Clang这样的编译器要么为全渐进下溢(-fno-fast-math)优化,要么为有限数学运算(-ffast-math)优化。但如果你知道自己已经设置了DAZ/FTZ却没有用 -ffast-math 编译,或者在较旧的ARM NEON上运行而没有处理子正规数,那么也许有趣的bithack可能对某些情景有帮助。玩这个点子肯定挺有意思的。 :P但很可能你最终还是会对x 与y 同时执行PAND清除指数位,然后进行比较,像 @chtz写的那样分别检查0 与无穷大。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章