web-dev-qa-db-fra.com

Comment utiliser les instructions FMA (Multused Multiply-Add) avec SSE / AVX

J'ai appris que certains processeurs Intel/AMD peuvent faire des multiplications et des ajouts simultanés avec SSE/AVX:
FLOPS par cycle pour pont de sable et haswell SSE2/AVX/AVX2 .

J'aime savoir comment faire le mieux dans le code et je veux aussi savoir comment cela se fait en interne dans le CPU. Je veux dire avec l'architecture super-scalaire. Disons que je veux faire une longue somme comme celle-ci dans 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));
...

Ma question est de savoir comment cela est converti en multiplication et addition simultanées? Les données peuvent-elles être dépendantes? Je veux dire, le CPU peut-il faire _mm_add_ps(sum, _mm_mul_ps(a1, b1)) simultanément ou les registres utilisés dans la multiplication et add doivent-ils être indépendants?

Enfin, comment cela s'applique-t-il à FMA (avec Haswell)? _mm_add_ps(sum, _mm_mul_ps(a1, b1)) est-il automatiquement converti en une seule instruction FMA ou micro-opération?

40
user2088790

Le compilateur est autorisé à fusionner un ajout et une multiplication séparés, même si cela modifie le résultat final (en le rendant plus précis).

Un FMA n'a qu'un seul arrondi (il conserve effectivement une précision infinie pour le résultat de multiplication temporaire interne), tandis qu'un ADD + MUL en a deux.

Les normes IEEE et C le permettent lorsque #pragma STDC FP_CONTRACT ON Est en vigueur, et les compilateurs sont autorisés à avoir ON par défaut (mais pas tous). Gcc se contracte par défaut dans FMA (avec la valeur par défaut -std=gnu*, Mais pas -std=c*, Par exemple -std=c++14). Pour Clang , il n'est activé qu'avec -ffp-contract=fast. (Avec seulement #pragma Activé, uniquement dans une seule expression comme a+b*c, Pas dans des instructions C++ distinctes.).

Ceci est différent de virgule flottante stricte vs décontractée (ou en termes gcc, -ffast-math Vs -fno-fast-math) Qui permettrait d'autres types d'optimisations qui pourraient augmenter l'erreur d'arrondi en fonction de les valeurs d'entrée . Celui-ci est spécial en raison de la précision infinie du temporaire interne FMA; s'il y avait un arrondi dans le temporaire interne, cela ne serait pas autorisé dans la PF stricte.

Même si vous activez la virgule flottante détendue, le compilateur peut toujours choisir de ne pas fusionner car il peut s'attendre à ce que vous sachiez ce que vous faites si vous utilisez déjà des éléments intrinsèques.


Donc la meilleure façon de vous assurer que vous obtenez réellement les instructions FMA que vous souhaitez est d'utiliser les éléments intrinsèques fournis pour eux:

FMA3 Intrinsics: (AVX2 - Intel Haswell)

  • _mm_fmadd_pd(), _mm256_fmadd_pd()
  • _mm_fmadd_ps(), _mm256_fmadd_ps()
  • et environ un milliard d'autres variations ...

FMA4 Intrinsics: (XOP - AMD Bulldozer)

  • _mm_macc_pd(), _mm256_macc_pd()
  • _mm_macc_ps(), _mm256_macc_ps()
  • et environ un milliard d'autres variations ...
41
Mysticial

J'ai testé le code suivant dans GCC 5.3, Clang 3.7, ICC 13.0.1 et MSVC 2015 (version du compilateur 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);
}

Avec les bonnes options de compilation (voir ci-dessous), chaque compilateur générera une instruction vfmadd (par exemple vfmadd213ss) de mul_add. Cependant, seul MSVC ne parvient pas à contracter mul_addv à une seule instruction vfmadd (par exemple vfmadd213ps).

Les options de compilation suivantes sont suffisantes pour générer des instructions vfmadd (sauf avec mul_addv avec 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 ne contractera pas mul_addv à une seule instruction fma mais depuis au moins GCC 5.1, c'est le cas. Je ne sais pas quand les autres compilateurs ont commencé à faire ça.

14
Z boson