46

一部の Intel/AMD CPU は、SSE/AVX で同時に乗算と加算を実行できることを知りまし

コードでこれを行う方法を知りたいのですが、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 命令またはマイクロ操作に自動的に変換されますか?

4

2 に答える 2

47

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

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

IEEE および C 標準#pragma STDC FP_CONTRACT ONでは、 が有効な場合にこれが許可されており、コンパイラはONデフォルトで許可されています(ただし、すべてが許可されているわけではありません)。Gcc はデフォルトで FMA にコントラクトします (たとえば-std=gnu*ではなくデフォルトで)。 Clangの場合、 でのみ有効になります。( が有効になっているだけで、個別の C++ ステートメントではなく、 のような単一の式内でのみ。)-std=c*-std=c++14-ffp-contract=fast#pragmaa+b*c

これは、入力値に応じて丸め誤差を増加させる可能性のある他の種類の最適化を許可する厳密な浮動小数点と緩和された浮動小数点 (または gcc の用語では-ffast-math対) とは異なります。これは、FMA 内部テンポラリの精度が無限であるため、特別です。内部一時的に丸めがあった場合、これは厳密な FP では許可されません。-fno-fast-math

緩和された浮動小数点を有効にしても、組み込み関数を既に使用している場合、コンパイラはユーザーが何をしているのかを知っていると期待する可能性があるため、融合しないことを選択する場合があります。


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

FMA3 組み込み関数: (AVX2 - Intel 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()
  • そして、無数の他のバリエーションについて...
于 2013-04-10T18:33:00.633 に答える
18

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)を生成しmul_addます。ただし、MSVC のみがmul_addv単一のvfmadd命令 (例: vfmadd213ps) へのコントラクトに失敗します。

次のコンパイラ オプションは、命令を生成するのに十分です ( MSVCvfmaddを除く)。mul_addv

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

于 2015-12-25T09:40:31.403 に答える