C语言 如何使用融合乘加(FMA)指令与SSE/AVX



我了解到一些Intel/AMD cpu可以同时进行SSE/AVX的乘法和加法:对于sandy-bridge和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标准允许这样做,并且编译器默认允许使用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 intrinsic: (AVX2 - Intel Haswell)

  • _mm_fmadd_pd(), _ mm256_fmadd_pd()
  • _mm_fmadd_ps(), _mm256_fmadd_ps()
  • 和无数其他的变化…

FMA4 intrinsic: (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);
}

使用正确的编译器选项(见下文),每个编译器将从mul_add生成vfmadd指令(例如vfmadd213ss)。然而,只有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开始它会这样做。我不知道其他编译器什么时候开始这么做的

相关内容

  • 没有找到相关文章

最新更新