46

我了解到,一些 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指令还是微操作?

4

2 回答 2

47

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

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

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-mathvs -fno-fast-math)不同,后者将允许其他类型的优化,这些优化可能会根据输入值增加舍入误差。这个是特殊的,因为 FMA 内部临时的无限精度;如果内部临时有任何舍入,这在严格的 FP 中是不允许的。

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


因此,确保您真正获得所需的 FMA 指令的最佳方法是实际使用为它们提供的内在函数:

FMA3 内在函数:(AVX2 - 英特尔 Haswell)

  • _mm_fmadd_pd(), _mm256_fmadd_pd()
  • _mm_fmadd_ps(),_mm256_fmadd_ps()
  • 以及无数其他变体……

FMA4 内在函数:(XOP - AMD 推土机)

  • _mm_macc_pd(),_mm256_macc_pd()
  • _mm_macc_ps(),_mm256_macc_ps()
  • 以及无数其他变体……
于 2013-04-10T18:33:00.633 回答
18

我在 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都会vfmadd213ssmul_add. 但是,只有 MSVC 无法收缩mul_addv到单个vfmadd指令(例如vfmadd213ps)。

以下编译器选项足以生成vfmadd指令(mul_addvMSVC 除外)。

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 会收缩。我不知道其他编译器何时开始这样做。

于 2015-12-25T09:40:31.403 回答