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

J’ai appris que certains processeurs Intel / AMD peuvent se multiplier simultanément et append avec SSE / AVX:
FLOPS par cycle pour le bridge sableux et le haswell SSE2 / AVX / AVX2 .

J’aime savoir comment utiliser au mieux le code et je veux également savoir comment cela se passe en interne dans la CPU. Je veux dire avec l’architecture super-scalaire. Disons que je veux faire une longue sum comme celle-ci dans SSE:

//sum = a1*b1 + a2*b2 + a3*b3 +... where a is a scalar and b is a SIMD vector (eg from masortingx 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 processeur 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)) automatiquement converti en une seule instruction FMA ou en une micro-opération?

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 a 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 le permettent lorsque #pragma STDC FP_CONTRACT ON est #pragma STDC FP_CONTRACT ON et que les compilateurs sont autorisés à l’ #pragma STDC FP_CONTRACT 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 est activé uniquement avec -ffp-contract=fast . (Avec uniquement le #pragma activé, uniquement dans une seule expression, telle a+b*c , et non dans des instructions C ++ distinctes.).

Cela diffère de virgule flottante ssortingcte / relaxée (ou en termes gcc, -ffast-math vs -fno-fast-math ) qui autoriserait d’autres types d’optimisations susceptibles d’augmenter l’erreur d’arrondi en fonction des valeurs d’entrée . Celui-ci est spécial en raison de la précision infinie du temporaire interne du FMA; s’il y avait du tout un arrondi dans le temporaire interne, cela ne serait pas autorisé dans la ssortingcte PF.

Même si vous activez une 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 insortingnsèques.


Le meilleur moyen de vous assurer que vous obtenez bien les instructions FMA que vous voulez est d’utiliser les éléments insortingnsèques fournis pour ces instructions:

Insortingnsics FMA3: (AVX2 – Intel Haswell)

  • _mm_fmadd_pd() , _ mm256_fmadd_pd()
  • _mm_fmadd_ps() , _mm256_fmadd_ps()
  • et à propos d’un gazillion d’autres variations …

Insortingnsics FMA4: (XOP – Bulldozer AMD)

  • _mm_macc_pd() , _mm256_macc_pd()
  • _mm_macc_ps() , _mm256_macc_ps()
  • et à propos d’un gazillion d’autres variations …

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 du compilateur (voir ci-dessous), chaque compilateur générera une instruction vfmadd (par exemple, vfmadd213ss ) à partir de mul_add . Cependant, seul MSVC ne parvient pas à contracter mul_addv en une seule instruction vfmadd (par exemple, vfmadd213ps ).

Les options du compilateur suivantes sont suffisantes pour générer des instructions vfmadd (sauf avec mul_addv avec 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 ne sous- mul_addv pas mul_addv à une seule instruction fma, mais au moins GCC 5.1 le fera. Je ne sais pas quand les autres compilateurs ont commencé à le faire.