为什么天真的Abs实现在C ++中不能得到很好的优化?

问题描述

我一直在研究abs(float)的幼稚实现如何编译,并对结果感到惊讶:

float abs(float x) {
    return x < 0 ? -x : x;
}

在-O3处使用clang 10.1时,结果为:

.LCPI0_0:
        .long   2147483648              # float -0
        .long   2147483648              # float -0
        .long   2147483648              # float -0
        .long   2147483648              # float -0
abs(float):
        movaps  xmm2,xmmword ptr [rip + .LCPI0_0]
        xorps   xmm2,xmm0
        xorps   xmm3,xmm3
        movaps  xmm1,xmm0
        cmpltss xmm1,xmm3
        andps   xmm2,xmm1
        andnps  xmm1,xmm0
        orps    xmm1,xmm2
        movaps  xmm0,xmm1
        ret

我发现这非常令人惊讶,因为老实说我只是希望清除浮点数的符号位,而这应该只是一条XOR指令。 IEEE-754浮点语义必须要引起这种复杂性,但我只是不明白是什么使它如此变得复杂。为什么只需要比较和有条件的举动?

也许是因为与NaN的比较总是会失败,所以在这种情况下不会清除符号位?但是由于NaN的符号位可以是0或1,所以没关系。

为进行比较,当仅使用std::fabs时,输出要简单得多,这正是人们所期望的:

abs(float):
        andps   xmm0,xmmword ptr [rip + .LCPI0_0]
        ret

启用-ffast-math标志时会产生相同的输出。

更新:-O3的gcc 10.2生成:

abs(float):
        pxor    xmm1,xmm1
        comiss  xmm1,xmm0
        ja      .L6
        ret
.L6:
        xorps   xmm0,XMMWORD PTR .LC1[rip]
        ret

解决方法

IEEE浮点空间包含许多特殊值,例如正和负0,正和负无穷以及两个“非数字”(NaN)系列。所有这些值都具有定义明确的语义wrt。 <运算符,因此编译器必须生成正确处理所有特殊情况的代码。

标志-ffast-math可用于通知编译器它可以假定未使用特殊值,正负0之间的区别无关紧要,并可以进行其他一些简化的假设(例如该添加是关联的)。使用此标志,clang可能会为您的abs函数生成最佳代码:

abs:
        andps   .LCPI0_0(%rip),%xmm0
        retq

默认情况下是否遵循巴洛克式IEEE语义的选择有些争议。除gcc和clang之外的其他编译器往往会做出相反的选择,它们默认情况下编译快速且紧凑的代码,并且如果需要完全符合IEEE要求,则需要显式命令行标志(例如,对于Intel编译器,-mp )。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...