我已经了解到,一些英特尔/ AMD的CPU可以做点大乘法与SSE / AVX补充:
每个周期FLOPS为沙桥和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))
同时或做乘法使用的寄存器,并添加要学会独立?
最后,这如何适用于FMA(与Haswell的)? 被_mm_add_ps(sum, _mm_mul_ps(a1, b1))
自动转换成一个单一的FMA指令或微操作?
编译器允许熔合分离增加和繁殖,尽管这种改变最终结果(通过使它更加准确)。
一个FMA仅具有一个舍入(它有效地保持为内部临时乘法结果无限精度),而一个ADD MUL +有两个。
在IEEE和C标准允许这时候#pragma STDC FP_CONTRACT ON
生效,和编译器都不允许有它ON
在默认情况下 (但不是所有的事)。 GCC合同成FMA默认(默认-std=gnu*
,但不是-std=c*
,例如-std=c++14
)。 对于铛 ,它只有在使能-ffp-contract=fast
。 (只用#pragma
启用,只有内的类似的单一表达式a+b*c
,而不是跨越独立的C ++语句)。
这是严格对不同宽松浮点(或GCC而言, -ffast-math
与-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()
- 大约一个极大其它变体...
我测试下面的代码在GCC 5.3,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指令,但由于5.1至少GCC它。 我不知道什么时候其他编译器开始这样做。