使用SSE / AVX添加(FMA)指令

我了解到一些英特尔/ AMD处理器可以同时进行乘法运算并添加SSE / AVX:
沙桥和haswell SSE2 / AVX / AVX2每个周期FLOPS。

我想知道如何在代码中做到最好,我也想知道它是如何在CPU内部完成的。 我的意思是超级标量架构。 假设我想在SSE做一笔如下的长期支出:

//sum = a1*b1 + a2*b2 + a3*b3 +... where a is a scalar and b is a SIMD vector (e.g. from matrix multiplication)
sum = _mm_set1_ps(0.0f);
a1  = _mm_set1_ps(a[0]); 
b1  = _mm_load_ps(&b[0]);
sum = _mm_add_ps(sum, _mm_mul_ps(a1, b1));

a2  = _mm_set1_ps(a[1]); 
b2  = _mm_load_ps(&b[4]);
sum = _mm_add_ps(sum, _mm_mul_ps(a2, b2));

a3  = _mm_set1_ps(a[2]); 
b3  = _mm_load_ps(&b[8]);
sum = _mm_add_ps(sum, _mm_mul_ps(a3, b3));
...

我的问题是如何转换为同时乘法和加法? 数据可以依赖吗? 我的意思是CPU可以同时执行_mm_add_ps(sum, _mm_mul_ps(a1, b1))或者用于乘法和加法的寄存器必须是独立的吗?

最后,这是如何适用于FMA(与Haswell)? _mm_add_ps(sum, _mm_mul_ps(a1, b1))自动转换为单个FMA指令还是微操作?


允许编译器融合分离的加法和乘法,即使这改变了最终结果(通过使其更加精确)。

FMA只有一个四舍五入(它有效地保持了内部临时乘法结果的无限精度),而ADD + MUL则有两个。

#pragma STDC FP_CONTRACT ON有效时,IEEE和C标准允许这样#pragma STDC FP_CONTRACT ON ,编译器默认允许它ON (但不是全部)。 默认情况下,Gcc契约到FMA中(默认-std=gnu* ,而不是-std=c* ,例如-std=c++14 )。 对于Clang,它只能用-ffp-contract=fast启用。 (只有启用了#pragma ,只能在单个表达式中使用,如a+b*c ,而不是跨越单独的C ++语句。)。

这与严格浮点或宽松浮点(或gcc术语, -ffast-math-fno-fast-math )不同,它允许其他类型的优化,这可能会根据输入值增加舍入误差。 这是因为FMA内部临时性的无限精确性而特别的; 如果在内部暂时有任何四舍五入的话,这在严格的FP中是不允许的。

即使你启用了轻松的浮点运算,编译器仍可能选择不融合,因为如果你已经在使用内在函数,它可能希望你知道你在做什么。


因此确保实际获得所需的FMA指令的最佳方法是实际使用为其提供的内部函数:

FMA3 Intrinsics:(AVX2 - Intel Haswell)

  • _mm_fmadd_pd() ,_ mm256_fmadd_pd()
  • _mm_fmadd_ps()_mm256_fmadd_ps()
  • 还有一大类其他的变化......
  • FMA4 Intrinsics :( XOP - AMD推土机)

  • _mm_macc_pd()_mm256_macc_pd()
  • _mm_macc_ps()_mm256_macc_ps()
  • 还有一大类其他的变化......

  • 我在GCC 5.3,Clang 3.7,ICC 13.0.1和MSVC 2015(编译器版本19.00)中测试了以下代码。

    float mul_add(float a, float b, float c) {
        return a*b + c;
    }
    
    __m256 mul_addv(__m256 a, __m256 b, __m256 c) {
        return _mm256_add_ps(_mm256_mul_ps(a, b), c);
    }
    

    有了正确的编译器选项(见下文),每一个编译器会生成一个vfmadd指令(例如vfmadd213ss )从mul_add 。 但是,只有MSVC无法将mul_addv收缩到单个vfmadd指令(例如vfmadd213ps )。

    以下编译器选项足以生成vfmadd指令(使用MSVC的mul_addv除外)。

    GCC:   -O2 -mavx2 -mfma
    Clang: -O1 -mavx2 -mfma -ffp-contract=fast
    ICC:   -O1 -march=core-avx2
    MSVC:  /O1 /arch:AVX2 /fp:fast
    

    GCC 4.9不会将mul_addv收缩到一个单一的fma指令,但至少从GCC 5.1开始。 我不知道其他编译器何时开始这样做。

    链接地址: http://www.djcxy.com/p/15019.html

    上一篇: Add (FMA) instructions with SSE/AVX

    下一篇: Floating point division vs floating point multiplication