2013-04-10 4 views
33

J'ai appris que certaines CPU Intel/AMD peuvent se multiplier simultanément et ajouter avec SSE/AVX:
FLOPS per cycle for sandy-bridge and 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 la CPU. Je veux dire avec l'architecture super-scalaire. Disons que je veux faire une longue somme comme la suivante dans l'ESS:Comment utiliser les instructions Fused Multiply-Add (FMA) avec SSE/AVX

//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 se convertissent à multiplier simultanément et ajouter? Les données peuvent-elles être dépendantes? Je veux dire le CPU peut faire _mm_add_ps(sum, _mm_mul_ps(a1, b1)) simultanément ou les registres utilisés dans la multiplication et ajouter doivent être indépendants?

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

Répondre

35

Le compilateur est autorisé à fusionner une addition et une multiplication séparées, 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), alors qu'un ADD + MUL en a deux.

Les normes IEEE et C permettent cela lorsque #pragma STDC FP_CONTRACT ON est en vigueur, et compilers are allowed to have it ON by default (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). For Clang, il est uniquement activé avec -ffp-contract=fast. (Avec seulement le #pragma activé, seulement dans une seule expression comme a+b*c, pas à travers des instructions C++ séparées.). Ceci est différent du virgule flottante stricte ou détendue (ou en termes gcc, -ffast-math par rapport à -fno-fast-math) qui permettrait d'autres types d'optimisation that could increase the rounding error depending on the input values. Celui-ci est spécial en raison de la précision infinie du FMA interne temporaire; S'il y avait des arrondis quelconques dans le temporaire interne, cela ne serait pas autorisé dans le cadre de la PF stricte.

Même si vous activez le flottant détendu, le compilateur peut choisir de ne pas fusionner, car il peut s'attendre à ce que vous sachiez ce que vous faites si vous utilisez déjà intrinsèques.


Alors la meilleure façon pour vous assurer que vous réellement obtenir les instructions FMA que vous voulez est que vous utilisez réellement les intrinsics fournis pour eux:

FMA3 Intrinsics: (AVX2 - Intel Haswell)

  • _mm_fmadd_pd(), _ mm256_fmadd_pd()
  • _mm_fmadd_ps(), _mm256_fmadd_ps()
  • et sur un gazillion d'autres variations ...

FMA4 Intrinsics: (XOP - AMD Bulldozer)

  • _mm_macc_pd(), _mm256_macc_pd()
  • _mm_macc_ps(), _mm256_macc_ps()
  • et environ un gazillion autres variations ...
+0

Merci, cela répond plus ou moins à ma question sur FMA. Je devrais vraiment passer du temps à apprendre quelques assemblages x86. Cela répondrait probablement à la plupart de mes questions. –

+0

En ce qui concerne votre question de savoir si une multiplication et un ajout peut être fait simultanément (FMA). La réponse est non puisque l'addition utilise le résultat de la multiplication. Donc vous mangez la latence d'add + multiplier. Une instruction FMA fait les deux instructions ensemble - généralement avec la même latence qu'une muliply unique. Donc l'addition est gratuite. – Mysticial

+1

Merci, c'est ce que je pensais. Maintenant, j'ai juste besoin de comprendre comment organiser mon code de sorte que la somme que je définis ci-dessus fasse des ajouts et des multiplications indépendants simultanément (donc j'évite les latences). –

11

J'ai testé les éléments suivants code dans GCC 5.3, Clang 3.7, ICC 13.0.1 et MSVC 2015 (version du compilateur 1 9h00).

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 du compilateur (voir ci-dessous) chaque compilateur génère une instruction vfmadd (par exemple vfmadd213ss) de mul_add. Cependant, seul MSVC ne réussit pas à contracter mul_addv en 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 mul_addv à une seule instruction fma mais depuis au moins GCC 5.1, il le fait. Je ne sais pas quand les autres compilateurs ont commencé à faire ça.

+0

Voir aussi '#pragma STDC FP_CONTRACT ON'. Stephen Canon souligne qu'il ne permet la contraction que dans une seule déclaration et non dans plusieurs déclarations. (http://lists.llvm.org/pipermail/cfe-dev/2015-September/045110.html). Notez également que gcc permet la contraction uniquement avec '-std = gnu *', pas avec '-std = c11' ou quoi que ce soit. (Et puis il permet la contraction à travers les déclarations, au-delà de ce que IEEE + ISO C permettent strictement). Une autre fonction de test qui utilise des variables séparées peut valoir la peine d'être essayée. –

+0

@PeterCordes, voir ce https://stackoverflow.com/q/34436233/2542702 et la réponse de Stephen Canon. Je pense que ce que GCC est en train de faire est correct selon la réponse de Stephen (en supposant que GCC n'a pas ignoré 'STDC FP_CONTRACT' qui est malheureusement la dernière fois que j'ai vérifié). –

+0

Votre question ici ne vous demande que 'return a * b + c;', pas 'float mul = a * b; retour mul + c; Lisez attentivement le mailing list de Stephen: il mentionne que 'STDC FP_CONTRACT ON' de clang n'autorise qu'une contraction dans une expression, contrairement aux clangs' -ffp-contract = fast' qui l'activeraient aussi pour mon second exemple dans ce commentaire. C'est la raison pour laquelle clang a des paramètres séparés «on» ou «fast» pour l'option de ligne de commande. Voir mes récentes modifications à la réponse de Mysticial sur cette question. C'est plus désordonné que je ne le pensais au début :( –

Questions connexes