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 ベースのマシンで両方のアプローチを測定しました。
アップデート:
そこで、生成されたマシン コードを分析したところ、Haswell には 2 つの FMA パイプがあるため、r1 と r2 の依存関係チェーンが並行してディスパッチできるように、MSFT VS2013 C++ コンパイラがマシン コードを生成していると判断しました。
r3 は r1 の後にディスパッチする必要があるため、この場合、2 番目の FMA パイプはアイドル状態になります。
ループを展開して 3 セットではなく 6 セットの FMA を実行すれば、反復ごとにすべての FMA パイプをビジー状態に保つことができると考えました。
残念ながら、この場合のアセンブリ ダンプを確認したところ、MSFT コンパイラは、探していた種類の並列ディスパッチを許可するレジスタ割り当てを選択していませんでした。また、求めていたパフォーマンスの向上が得られなかったことが確認されました。為に。
C コードを (組み込み関数を使用して) 変更して、コンパイラがより良いコードを生成できるようにする方法はありますか?