考虑使用 Haswell 的 FMA 指令的以下指令序列:
__m256 r1 = _mm256_xor_ps (r1, r1);
r1 = _mm256_fmadd_ps (rp1, m6, r1);
r1 = _mm256_fmadd_ps (rp2, m7, r1);
r1 = _mm256_fmadd_ps (rp3, m8, r1);
__m256 r2 = _mm256_xor_ps (r2, r2);
r2 = _mm256_fmadd_ps (rp1, m3, r2);
r2 = _mm256_fmadd_ps (rp2, m4, r2);
r2 = _mm256_fmadd_ps (rp3, m5, r2);
__m256 r3 = _mm256_xor_ps (r3, r3);
r3 = _mm256_fmadd_ps (rp1, m0, r3);
r3 = _mm256_fmadd_ps (rp2, m1, r3);
r3 = _mm256_fmadd_ps (rp3, m2, r3);
可以使用非 FMA 指令表示相同的计算,如下所示:
__m256 i1 = _mm256_mul_ps (rp1, m6);
__m256 i2 = _mm256_mul_ps (rp2, m7);
__m256 i3 = _mm256_mul_ps (rp3, m8);
__m256 r1 = _mm256_xor_ps (r1, r1);
r1 = _mm256_add_ps (i1, i2);
r1 = _mm256_add_ps (r1, i3);
i1 = _mm256_mul_ps (rp1, m3);
i2 = _mm256_mul_ps (rp2, m4);
i3 = _mm256_mul_ps (rp3, m5);
__m256 r2 = _mm256_xor_ps (r2, r2);
r2 = _mm256_add_ps (i1, i2);
r2 = _mm256_add_ps (r2, i3);
i1 = _mm256_mul_ps (rp1, m0);
i2 = _mm256_mul_ps (rp2, m1);
i3 = _mm256_mul_ps (rp3, m2);
__m256 r3 = _mm256_xor_ps (r3, r3);
r3 = _mm256_add_ps (i1, i2);
r3 = _mm256_add_ps (r3, i3);
人们会期望 FMA 版本比非 FMA 版本提供一些性能优势。
但不幸的是,在这种情况下,性能改进为零 (0)。
谁能帮我理解为什么?
我在基于核心 i7-4790 的机器上测量了这两种方法。
更新:
因此,我分析了生成的机器代码并确定 MSFT VS2013 C++ 编译器正在生成机器代码,因此 r1 和 r2 的依赖链可以并行调度,因为 Haswell 有 2 个 FMA 管道。
r3 必须在 r1 之后调度,因此在这种情况下,第二个 FMA 管道是空闲的。
我认为如果我展开循环以执行 6 组 FMA 而不是 3 组,那么我可以让所有 FMA 管道在每次迭代时都处于忙碌状态。
不幸的是,当我在这种情况下检查程序集转储时,MSFT 编译器没有选择允许我正在寻找的并行调度类型的寄存器分配,并且我证实我没有得到我正在寻找的性能提升为了。
有没有办法可以更改我的 C 代码(使用内在函数)以使编译器能够生成更好的代码?