web-dev-qa-db-ja.com

SSE / AVXでFMA(Fused Multiply-Add)命令を使用する方法

いくつかのIntel/AMD CPUがSSE/AVXで同時に乗算および加算できることを学びました。
Sandy-bridgeおよびhaswell SSE2/AVX/AVX2のサイクルあたりのFLOPS

私はコードでこれを最高に行う方法を知りたいのですが、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(ハスウェル)にどのように適用されますか? _mm_add_ps(sum, _mm_mul_ps(a1, b1))は自動的に単一のFMA命令またはマイクロオペレーションに変換されますか?

40
user2088790

コンパイラーは、最終結果が(より正確にすることで)変更される場合でも、分離された加算と乗算を融合できます。

FMAには1つの丸めのみがあり(内部の一時的な乗算結果に対して事実上無限の精度を維持します)、ADD + MULには2つの丸めがあります。

IEEE標準およびC標準では、_#pragma STDC FP_CONTRACT ON_が有効であり、 コンパイラーがデフォルトでONを持つことが許可されています (ただし、すべてが許可されているわけではありません)。 GccはデフォルトでFMAに縮小します(デフォルトの_-std=gnu*_を使用しますが、_-std=c*_は使用しません(例:_-std=c++14_)。 Clang の場合、_-ffp-contract=fast_でのみ有効になります。 (_#pragma_のみを有効にすると、個別のC++ステートメントではなく、_a+b*c_のような単一の式内でのみ有効になります。).

これは、厳密な浮動小数点とリラックスした浮動小数点(またはgccの用語で_-ffast-math_対_-fno-fast-math_)とは異なります。これにより、他の種類の最適化 が可能になります。入力値 。これは、FMA内部テンポラリーの無限の精度のために特別です。内部テンポラリで丸めが行われた場合、厳密なFPでは許可されません。

リラックスした浮動小数点を有効にしても、コンパイラーは、既に組み込み関数を使用している場合は何をしているのかを知ることを期待するため、Fuseを選択しない場合があります。


したがって、最良の方法は、必要なFMA命令を実際に取得するために、提供された組み込み関数を実際に使用することです:

FMA3組み込み関数: (AVX2-Intel Haswell)

  • _mm_fmadd_pd()、_mm256_fmadd_pd()
  • _mm_fmadd_ps()_mm256_fmadd_ps()
  • 約1億のバリエーションがあります...

FMA4組み込み関数: (XOP-AMD Bulldozer)

  • _mm_macc_pd()_mm256_macc_pd()
  • _mm_macc_ps()_mm256_macc_ps()
  • 約1億のバリエーションがあります...
41
Mysticial

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)from 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以降は使用します。他のコンパイラがいつこれを始めたのかわかりません。

14
Z boson