为什么 Clang 会添加额外的 FMA 指令?

Why does Clang add extra FMA instructions?

提问人:HesLg 提问时间:6/24/2022 更新时间:6/24/2022 访问量:122

问:

#include <immintrin.h>

__m256 mult(__m256 num) {
    return 278*num/(num+1400);
}
.LCPI0_0:
        .long   0x438b0000                      # float 278
.LCPI0_1:
        .long   0x44af0000                      # float 1400
mult(float __vector(8)):                           # @mult(float __vector(8))
        vbroadcastss    ymm1, dword ptr [rip + .LCPI0_0] # ymm1 = [2.78E+2,2.78E+2,2.78E+2,2.78E+2,2.78E+2,2.78E+2,2.78E+2,2.78E+2]
        vmulps  ymm1, ymm0, ymm1
        vbroadcastss    ymm2, dword ptr [rip + .LCPI0_1] # ymm2 = [1.4E+3,1.4E+3,1.4E+3,1.4E+3,1.4E+3,1.4E+3,1.4E+3,1.4E+3]
        vaddps  ymm0, ymm0, ymm2
        vrcpps  ymm2, ymm0
        vmulps  ymm3, ymm1, ymm2
        vfmsub213ps     ymm0, ymm3, ymm1        # ymm0 = (ymm3 * ymm0) - ymm1
        vfnmadd213ps    ymm0, ymm2, ymm3        # ymm0 = -(ymm2 * ymm0) + ymm3
        ret

为什么 Clang 要在代码中添加两个额外的 FMA 指令?结果应该已经用 计算了。额外的指令不会增加延迟,而不仅仅是使用 like with 吗?vmulps ymm3, ymm1, ymm2vdivps-O3

戈德博尔特

C++ 程序集 x86 精度 数值方法

评论

0赞 Peter Cordes 6/24/2022
你忘了在你用 编译的问题中说。( 等价于 )。因此,它使用 RCPPS + 牛顿迭代来替换 ,这在某些 CPU 上是一场胜利,具体取决于周围的代码。(如果你周围的代码不涉及太多的划分,通常最好只使用 ,尤其是在它是单个 uop 的 CPU 上。正则除法与近似倒数的乘法一样快。为什么?-ffast-math-Ofast-O3 -ffast-mathvdivpsvdivps

答:

7赞 harold 6/24/2022 #1

额外的 FMA 补偿了 的精度降低。 是结果的估计值,但精度约为通常精度的一半。vrcppsymm3

为简单起见,假设除法是 。q = a / b

第一个 FMA 计算差值,即对除法“偏离”的估计值(在原始比例中,除以之前)。第二个 FMA 将近似地将该差值除以(乘以 ),因此它成为 的刻度差值,然后减去它以使其更接近 。vfmsub213ps(a * b⁻¹) * b - abbb⁻¹qqa / b

如果您可以降低精度,则可以显式使用并乘以该精度,那么就不会有额外的 FMA 来补偿。_mm256_rcp_ps

除了使用之外,额外的指令不会增加延迟吗vdivps

是的,这个 4 指令序列在 Ice Lake 上需要 16 个周期,而需要 11 个周期。但是,与 相比,吞吐量大约翻了一番。根据上下文,延迟或吞吐量可能更重要。更多时候是吞吐量。编译器不一定非常擅长决定哪个更重要,尽管在这种情况下我不能责怪它(没有上下文)。vdivpsvdivps