web-dev-qa-db-fra.com

Utilisation des instructions du processeur AVX: performances médiocres sans "/ Arch: AVX"

Mon code C++ utilise SSE et maintenant je veux l'améliorer pour prendre en charge AVX quand il est disponible. Je détecte donc quand AVX est disponible et appelle une fonction qui utilise les commandes AVX. J'utilise Win7 SP1 + VS2010 SP1 et un processeur avec AVX.

Pour utiliser AVX, il est nécessaire d'inclure ceci:

#include "immintrin.h"

puis vous pouvez utiliser les fonctions intrinsèques AVX comme _mm256_mul_ps, _mm256_add_ps etc. Le problème est que par défaut, VS2010 produit du code qui fonctionne très lentement et affiche l'avertissement:

avertissement C4752: trouvé des extensions vectorielles avancées Intel (R); envisager d'utiliser/Arch: AVX

Il semble que VS2010 n'utilise pas les instructions AVX, mais les émule à la place. J'ai ajouté /Arch:AVX aux options du compilateur et a obtenu de bons résultats. Mais cette option indique au compilateur d'utiliser les commandes AVX partout où cela est possible. Donc mon code peut planter sur un CPU qui ne prend pas en charge AVX!

La question est donc de savoir comment faire le compilateur VS2010 pour produire du code AVX, mais uniquement lorsque je spécifie directement les intrinsèques AVX. Pour SSE cela fonctionne, j'utilise simplement SSE fonctions intrinsèques et il produit SSE code sans aucune option de compilation comme /Arch:SSE. Mais pour AVX, cela ne fonctionne pas pour une raison quelconque.

49
Mike

Le comportement que vous voyez est le résultat d'un changement d'état coûteux.

Voir page 102 du manuel Agner Fog:

http://www.agner.org/optimize/microarchitecture.pdf

Chaque fois que vous basculez incorrectement entre les instructions SSE et AVX), vous paierez une pénalité de cycle extrêmement élevée (~ 70).

Lorsque vous compilez sans /Arch:AVX, VS2010 générera SSE, mais utilisera toujours AVX partout où vous avez des intrinsèques AVX. Par conséquent, vous obtiendrez du code qui a les deux SSE et instructions AVX - qui auront ces pénalités de changement d'état. (VS2010 le sait, donc il émet cet avertissement que vous voyez.)

Par conséquent, vous devez utiliser tous les SSE ou tous les AVX. La spécification de /Arch:AVX Indique au compilateur d'utiliser tous les AVX.

Il semble que vous essayez de créer plusieurs chemins de code: un pour SSE et un pour AVX. Pour cela, je vous suggère de séparer votre SSE et code AVX en deux unités de compilation différentes. (Une compilée avec /Arch:AVX Et une sans) Puis reliez-les ensemble et créez un répartiteur pour choisissez en fonction du matériel sur lequel il fonctionne.

Si vous besoin pour mélanger SSE et AVX, assurez-vous d'utiliser _mm256_zeroupper() ou _mm256_zeroall() pour éviter les pénalités de changement d'état.

81
Mysticial

tl; dr

Utilisez _mm256_zeroupper(); ou _mm256_zeroall(); autour des sections de code utilisant AVX (avant ou après selon les arguments de la fonction). Utilisez uniquement l'option /Arch:AVX Pour les fichiers source avec AVX plutôt que pour un projet entier pour éviter d'interrompre la prise en charge des chemins de code SSE codés hérités.

Cause

Je pense que la meilleure explication se trouve dans l'article Intel, "Éviter les pénalités de transition AVX-SSE" ( [[# #] pdf [~ # ~] ). Le résumé déclare:

La transition entre les instructions Intel® AVX 256 bits et les anciennes instructions Intel® SSE au sein d'un programme peut entraîner des pertes de performances car le matériel doit enregistrer et restaurer les 128 bits supérieurs des registres YMM.

La séparation de votre code AVX et SSE en différentes unités de compilation peut ne PAS aider si vous basculez entre le code appelant à la fois compatible SSE et AVX fichiers d'objets activés, car la transition peut se produire lorsque des instructions AVX ou un assemblage sont mélangés avec l'un des (du papier Intel):

  • Instructions intrinsèques 128 bits
  • Assemblage en ligne SSE
  • Code à virgule flottante C/C++ qui est compilé pour Intel® SSE
  • Appels à des fonctions ou bibliothèques qui incluent l'un des éléments ci-dessus

Cela signifie qu'il peut même y avoir des pénalités lors de la liaison avec code externe en utilisant SSE.

Détails

Il y a 3 états de processeur définis par les instructions AVX, et l'un des états est celui où tous les registres YMM sont divisés, permettant d'utiliser la moitié inférieure par instructions SSE . Le document Intel " Transitions d'état Intel® AVX: migration du code SSE vers AVX " fournit un diagramme de ces états:

enter image description here

En état B (mode AVX-256), tous les bits des registres YMM sont utilisés. Lorsqu'une instruction SSE est appelée, une transition vers l'état C doit se produire, et c'est là qu'il y a une pénalité. La moitié supérieure de tous les registres YMM doit être enregistrée dans un tampon interne avant que SSE puisse démarrer, même s'il s'agit de zéros. Le coût des transitions est de "l'ordre de 50 à 80 cycles d'horloge sur le matériel Sandy Bridge". Il y a aussi une pénalité allant de C -> A, comme le montre la figure 2.

Vous pouvez également trouver des détails sur la pénalité de changement d'état provoquant ce ralentissement à la page 130, Section 9.12, "Transitions entre les modes VEX et non-VEX" dans - Guide d'optimisation d'Agner Fog (de la version mise à jour le 2014-08-07), référencé dans Mystical's answer . Selon son guide, toute transition vers/depuis cet état prend "environ 70 cycles d'horloge sur Sandy Bridge". Comme l'indique le document Intel, il s'agit d'une pénalité de transition évitable.

Résolution

Pour éviter les pénalités de transition, vous pouvez soit supprimer tout le code SSE hérité, demander au compilateur de convertir toutes les instructions SSE dans leur forme codée VEX d'instructions 128 bits (si le compilateur est capable) , ou placez les registres YMM dans un état zéro connu avant de faire la transition entre AVX et le code SSE. Essentiellement, pour conserver le chemin de code SSE séparé, vous devez mettre à zéro les 128 bits supérieurs des 16 registres YMM (en émettant une instruction VZEROUPPER) après tout code utilisant des instructions AVX . La mise à zéro de ces bits force manuellement une transition vers l'état A et évite la pénalité coûteuse car les valeurs YMM n'ont pas besoin d'être stockées dans un tampon interne par le matériel. L'intrinsèque qui exécute cette instruction est _mm256_zeroupper . La description de cet intrinsèque est très informative:

Cet élément intrinsèque est utile pour effacer les bits supérieurs des registres YMM lors de la transition entre les instructions Intel® Advanced Vector Extensions (Intel® AVX) et les instructions Intel® Supplémentaire SIMD Extensions SIMD (Intel® SSE). Il n'y a aucune pénalité de transition si une application efface les bits supérieurs de tous les registres YMM (mis à '0') via VZEROUPPER, le instructions correspondantes pour cet élément intrinsèque, avant de passer des instructions Intel® Advanced Vector Extensions (Intel® AVX) aux instructions Intel® Supplemental SIMD Extensions (Intel® SSE) héritées.

Dans Visual Studio 2010+ (peut-être même plus ancien), vous obtenez ce intrinsèque avec immintrin.h.

Notez que la remise à zéro des bits avec d'autres méthodes n'élimine pas la pénalité - les instructions VZEROUPPER ou VZEROALL doivent être utilisées.

Une solution automatique implémentée par le compilateur Intel consiste à insérer un VZEROUPPER au début de chaque fonction contenant le code Intel AVX si aucun des Les arguments sont un registre YMM ou le type de données __m256/__m256d/__m256i et à la fin de fonctions si la valeur retournée n'est pas un registre YMM ou un type de données __m256/__m256d/__m256i.

Dans la nature

Cette solution VZEROUPPER est utilisée par FFTW pour générer une bibliothèque prenant en charge SSE et AVX. Voir simd-avx.h :

/* Use VZEROUPPER to avoid the penalty of switching from AVX to SSE.
   See Intel Optimization Manual (April 2011, version 248966), Section
   11.3 */
#define VLEAVE _mm256_zeroupper

Ensuite, VLEAVE(); est appelée à la fin de la fonction every utilisant intrinsèque pour les instructions AVX.

20
chappjc