我了解到一些Intel / AMD CPU可以同时进行多次加法并添加SSE / AVX:FLOPS per cycle for sandy-bridge and haswell SSE2/AVX/AVX2。
我想知道如何在代码中做到最好,我也想知道它是如何在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))
还是在乘法中使用的寄存器和add必须是独立的?
最后,这如何适用于FMA(与Haswell一起)? _mm_add_ps(sum, _mm_mul_ps(a1, b1))
是自动转换为单个FMA指令还是微操作?
答案 0 :(得分:40)
允许编译器融合分离的加法和乘法,即使这会改变最终结果(通过使其更准确)。
FMA只有一个舍入(它有效地保持内部临时乘法结果的无限精度),而ADD + MUL有两个。
IEEE {和C标准允许#pragma STDC FP_CONTRACT ON
生效,compilers are allowed to have it ON
by default(但不是全部)。默认情况下,Gcc会合并为FMA(默认为-std=gnu*
,但不会-std=c*
,例如-std=c++14
。 For Clang,仅在-ffp-contract=fast
启用。 (仅启用#pragma
,仅在a+b*c
之类的单个表达式中,而不是在单独的C ++语句中。)。
这与严格与宽松的浮点(或gcc术语,-ffast-math
与-fno-fast-math
)不同,后者允许进行其他类型的优化that could increase the rounding error depending on the input values。这是特殊的,因为FMA内部临时的无限精度;如果在内部临时中存在任何舍入,则严格的FP中不允许这样做。
即使您启用了宽松的浮点数,编译器仍可能选择不融合,因为如果您已经在使用内在函数,它可能会让您知道自己在做什么。
所以最好的方式,以确保您真正获得所需的FMA说明,实际上是使用提供的内在函数:
FMA3 Intrinsics:(AVX2 - Intel Haswell)
_mm_fmadd_pd()
,_ mm256_fmadd_pd()
_mm_fmadd_ps()
,_mm256_fmadd_ps()
FMA4 Intrinsics:(XOP - AMD Bulldozer)
_mm_macc_pd()
,_mm256_macc_pd()
_mm_macc_ps()
,_mm256_macc_ps()
答案 1 :(得分:13)
我在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
指令(mul_addv
与MSVC除外)。
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开始。我不知道其他编译器何时开始这样做。