【发布时间】:2016-06-08 19:07:58
【问题描述】:
考虑使用 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 代码(使用内在函数)以使编译器能够生成更好的代码?
【问题讨论】:
-
是的,我想我尝试了类似的方法并得到了相同的结果 - 我还尝试了混合 FMA/AVX2 以查看是否有任何可以利用的并行性,但同样没有任何好处。
-
FMA 不是为了提高准确性,而不是提高性能吗?
-
我曾经用 FMA 和 AVX 实现过 Mandelbrot。我的 Haswell 系统没有性能改进。 FMA 主要是提高准确率,并且可以减小代码大小。
-
我做了,非 fma 实现只生成了 1 条 fma 指令,而 fma 实现产生了更多的 fma 指令。但我使用的是 Visual Studio 2013。
-
@R. - FMA 主要是为了提高性能。无论如何,这就是英特尔和 AMD 的定位方式,也是大多数讨论的方向。对于面向吞吐量有界的内核,FMA 可能会使其性能翻倍。它还允许芯片制造商将其标称 GFLOPS 评级提高一倍......