Moyen le plus rapide pour faire horizontale float somme vectorielle sur x86
Vous avez un vecteur de trois (ou quatre) flotte. Quel est le moyen le plus rapide pour faire la somme d'eux?
Est ESS (movaps, mélanger, ajouter, movd) toujours plus vite que x87? Sont l'horizontale-ajouter des instructions SSE4.2-il la peine? Quel est le coût de déplacement de la FPU, puis faddp, faddp? Quelle est la manière la plus rapide spécifique de la séquence d'instruction?
"D'essayer d'arranger les choses de sorte que vous pouvez somme de quatre vecteurs à la fois" ne sera pas acceptée comme une réponse. 🙂
- Si horizontal ajoute de la performance sont critiques pour vous, alors vous pourriez bien être à la veille d'SIMD de codage dans une moins de manière optimale - poster une partie de code qui montre comment et où vous en avez besoin pour ce faire.
- Produit scalaire pour les angles entre les vecteurs, principalement. Remarque la dernière phrase.
- J'ai lu la dernière phrase, mais je pense toujours qu'il peut y avoir une meilleure façon.
- Je sais qu'il ya une meilleure façon, et c'est "exécuter des boucles de quatre éléments à un moment de sorte que vous pouvez paralléliser tout". La question est de savoir quel est le meilleur que nous pouvons faire à l'exclusion de cette façon (ce qui est compliqué et abrutissant)?
- Il peut y avoir plus d'une "meilleure façon" mais si vous ne publiez pas n'importe quel code, alors il est difficile de donner de l'aide.
- laissez-nous continuer cette discussion dans le chat
- OK, je vais garder un œil sur le chat...
- Il n'y a pas de "façon la plus rapide ... sur x86". Différents x86 processeurs d'exécution différents caractéristiques. Ce processeur ciblez-vous? Est votre "vecteur de trois flotteurs" en mémoire d'abord, ou de manière contiguë dans un ESS registre, ou quelque part d'autre?
Vous devez vous connecter pour publier un commentaire.
Voici quelques versions à l'écoute basée sur Agner de la Brume microarch guide's microarch guide et instruction des tables. Voir aussi le x86 la balise wiki. Ils doivent être efficaces sur n'importe quel CPU, sans grands goulets d'étranglement. (par exemple, j'ai évité les choses n'aide uarch un peu, mais être lent sur un autre uarch). Code de taille est également réduit.
La commune 2x
hadd
idiome n'est bon que pour le code de la taille, pas de vitesse sur les Processeurs. Il y a des cas d'utilisation pour elle (voir ci-dessous), mais ce n'est pas l'un d'eux.J'ai aussi inclus un AVX version. Tout type de réduction horizontale avec AVX /AVX2 devrait commencer avec une
vextractf128
et "verticale" de l'opération afin de réduire à un XMM (__m128
) vecteur.Voir l'asm sortie de tout ce code sur le Godbolt Compilateur Explorer. Voir aussi mon améliorations à Agner Brouillard C++ de la Classe Vector de la Bibliothèque
horizontal_add
fonctions. (fil message board, et le code sur github). J'ai utilisé du RPC macros pour sélectionner optimale mélange de code de taille pour SSE2, SSE4, et AVX, et pour évitermovdqa
quand AVX n'est pas disponible.Il y a des différences à prendre en compte:
haddps
, donc c'est très pertinent ici.Lorsque l'horizontale ajouter est peu fréquent:
Processeurs sans uop-cache peut favoriser 2x
haddps
: C'est slowish quand il fonctionne, mais ce n'est pas souvent. Seulement 2 des instructions de minimiser l'impact sur le code environnant (I$ taille).Processeurs avec une uop-cache sera probablement favorable à quelque chose qui prend moins d'uop, même si c'est plus de mode d'emploi /plus x86 code de taille. Total uop cache-lignes est ce que nous voulons minimiser, ce qui n'est pas aussi simple que de minimiser total uop (prises branches et 32B limites de toujours commencer une nouvelle uop ligne de cache).
De toute façon, avec cela dit, horizontal sommes un beaucoup, donc voici ma tentative de soin les membres de certaines versions qui compile bien. Pas étalonnés sur tout matériel réel, ou même soigneusement testé. Il peut y avoir des bugs dans le shuffle constantes ou quelque chose.
Si vous réalisez une de secours /version de base de votre code, n'oubliez pas que seuls les anciens Cpu va l'exécuter; Processeurs récents exécuter vos AVX version, ou SSE4.1 ou quoi que ce soit.
Vieux Processeurs comme K8, et Core2(merom) et plus tôt seulement ont 64bit shuffle unités. Core2 a 128 unités d'exécution pour la plupart des instructions, mais pas pour le mélange. (Pentium M et K8 gérer tous les 128b vecteur instructions que deux 64bit moitiés).
Mélange comme
movhlps
que de déplacer des données en 64 bits morceaux (pas de brassage à l'intérieur de 64bit moitiés) sont rapides, trop.Sur de vieux Processeurs avec une lente mélange:
movhlps
(Merom: 1uop) est nettement plus rapide queshufps
(Merom: 3uops). Sur le Pentium-M, moins cher quemovaps
. Aussi, il s'exécute dans la FP de domaine sur Core2, à éviter le contournement des retards provenant d'autres remaniements.unpcklpd
est plus rapide queunpcklps
.pshufd
est lent,pshuflw
/pshufhw
sont rapides (parce qu'ils viennent de battre un 64bit de la moitié)pshufb mm0
(MMX) est rapide,pshufb xmm0
est lente.haddps
est très lent (6uops sur Merom et Pentium M)movshdup
(Merom: 1uop) est intéressant: C'est la seule 1uop insn qui mélange à l'intérieur de 64 éléments.shufps
sur Core2(y compris les Penryn) apporte des données dans l'entier domaine, entraînant une déviation délai pour obtenir retour à la FP des unités d'exécution pouraddps
, maismovhlps
est entièrement dans le FP de domaine.shufpd
s'étend aussi dans le flotteur de domaine.movshdup
s'exécute dans l'entier domaine, mais est seulement une uop.AMD K10, Intel Core2(Penryn/Wolfdale), et tous les Processeurs exécutent tous les xmm mélange comme un seul uop. (Mais le contournement de retard avec
shufps
sur Penryn, évité avecmovhlps
)Sans AVX, évitant les gaspillages de
movaps
/movdqa
instructions nécessite un choix judicieux de mélange. Seulement quelques remaniements de travail comme une copie-et-shuffle, plutôt que de modifier la destination. Mélange qui combinent des données à partir de deux entrées (commeunpck*
oumovhlps
) peut être utilisé avec une variable tmp qui ne sont plus nécessaires au lieu de_mm_movehl_ps(same,same)
.Certains de ces effets peuvent être effectuées plus rapidement (enregistrer une MOVAPS) mais plus laid /moins "propre" par une fausse arg pour une utilisation en tant que destination pour une première lecture aléatoire. Par exemple:
SSE1 (aka ESS):
J'ai signalé un clang bug sur pessimizing le mélange. Il a sa propre représentation interne de brassage, et qui tourne en arrière dans le mélange. gcc plus souvent utilise les instructions qui correspond intrinsèques que vous avez utilisé.
Souvent clang fait mieux que la gcc, dans le code où l'instruction n'est pas de la main-d'écoute, ou de la constante de propagation peut simplifier les choses, même lorsque l'intrinsèques sont optimales pour les non-constants. Dans l'ensemble c'est une bonne chose que les compilateurs de travail comme un compilateur approprié pour intrinsèques, pas juste un assembleur. Les compilateurs peuvent souvent générer de bons asm de scalaire C qui n'a même pas essayer de travailler comme bon asm serait. Finalement compilateurs traiter intrinsèques comme juste un autre C de l'opérateur en tant qu'entrée pour l'optimiseur.
SSE3
Ce qui a plusieurs avantages:
ne nécessite pas d'
movaps
copies pour contourner destructrice mélange (sans AVX):movshdup xmm1, xmm2
destination est en écriture seule, de sorte qu'il créetmp
de sortir d'une impasse vous inscrire pour nous. C'est aussi pourquoi j'ai utilisémovehl_ps(tmp, sums)
au lieu demovehl_ps(sums, sums)
.code petite taille. Le brassage des instructions sont de petite taille:
movhlps
est de 3 octets,movshdup
est de 4 octets (le même queshufps
). Pas dans l'immédiat l'octet est nécessaire, donc, avec AVX,vshufps
est de 5 octets, maisvmovhlps
etvmovshdup
sont à la fois 4.J'ai pu en sauver un autre octet avec
addps
au lieu deaddss
. Depuis ce ne sera pas utilisée à l'intérieur de boucles internes, l'énergie supplémentaire pour passer le supplément de transistors est probablement négligeable. FP exceptions de la partie supérieure de 3 éléments ne sont pas un risque, parce que tous les éléments de détenir des FP de données. Cependant, clang/LLVM, en fait, "comprend" vecteur mélange, et émet un code de meilleure qualité si elle sait que seul le bas de l'élément de matière.Comme le SSE1 version, l'ajout de l'étrange éléments eux-mêmes peuvent causer des FP exceptions près (comme dépassement de capacité) qui ne se produirait pas le contraire, mais cela ne devrait pas être un problème. Denormals sont lents, mais autant que je me souvienne de produire un +Inf résultat n'est pas sur la plupart des uarches.
SSE3 de l'optimisation de code-taille
Si le code de la taille est votre préoccupation principale, deux
haddps
(_mm_hadd_ps
) instructions fera l'affaire (Paul R de la réponse). C'est aussi le plus facile à taper et à retenir. Il est pas rapide, cependant. Même Intel Skylake encore décode chaquehaddps
à 3 uop, avec 6 cycle de latence. Donc, même si elle sauve de la machine-octets de code (L1 I-cache), il prend plus de place dans les plus précieux uop-cache. Véritable cas d'utilisation pourhaddps
: une transposition-et-somme problème, ou faire quelques mise à l'échelle à une étape intermédiaire dans ce SSEatoi()
de la mise en œuvre.AVX:
Cette version enregistre un octet de code vs Marat, la réponse à la question AVX.
Double-precision:
Le stockage de la mémoire et à l'arrière pour éviter ALU uop. C'est bien si le shuffle port de la pression, ou ALU uop, en général, sont un goulot d'étranglement. (Notez qu'il n'a pas besoin de
sub rsp, 8
ou quoi que ce soit parce que le x86-64 SysV ABI fournit une zone rouge que les gestionnaires de signaux ne seront pas sur.)Certaines personnes stocker dans un tableau et la somme de tous les éléments, mais les compilateurs, généralement, ne se rendent pas compte que le faible élément de la matrice est toujours là dans un registre avant de le stocker.
Entier:
pshufd
est un outil pratique de copie et de lecture aléatoire. Bits et d'octets, les changements sont malheureusement en place, etpunpckhqdq
met la moitié haute de la destination dans la moitié basse du résultat, à l'opposé de la façon dontmovhlps
pouvez extraire le haut de la moitié dans un autre registre.À l'aide de
movhlps
pour la première étape pourrait être bon sur certains Processeurs, mais seulement si nous avons un scratch reg.pshufd
est un choix sécuritaire et rapide sur tout ce qui est après Merom.Sur certains Processeurs, il est sûr à utiliser FP mélange sur les données entier. Je n'avais pas le faire, puisque, sur les Processeurs modernes qui en plus d'économiser 1 ou 2 octets de code, avec aucun gain de vitesse (autres que la taille du code/effets d'alignement).
movaps
avant lashufps
peut également être éliminé si vous utilisezpshufd
en changeant_mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 3, 0, 1));
à_mm_castsi128_ps(_mm_shuffle_epi32(_mm_castps_si128(v), _MM_SHUFFLE(2, 3, 0, 1)));
. Cependant, que peut-être ajoute une certaine latence. godbolt.org/g/0trqRYvpermilps
au lieu demovsldup
,movshdup
,movhlps
etmovlhps
quand AVX est disponible? C'est une victoire surshufps
et ressemble clang essaie également à l'émettre à la place de ceux qui sont mentionnés.C5 ..
au lieu deC4 .. ..
). Deux-source mélange comme VSHUFPS et VMOVHLPS ne sont pas plus lent que d'une source de mélange comme VPSHUFD ou VPERMILPS. Si il y a une différence dans la consommation d'énergie, c'est probablement négligeable.__AVX512F__
macro. 🙂SSE1 (aka SSE)
section de la ligne de_mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 3, 0, 1)); // [ C D | B A ]
? Je suppose que vous vouliez dire[ C D | A B ]
?v = _mm_hadd_ps(v, v); v = _mm_hadd_ps(v, v);
? Je Vous Remercie.SSE2
Tous les quatre:
r1+r2+r3:
J'ai trouvé ces à environ la même vitesse que le double
HADDPS
(mais je n'ai pas mesuré de trop près).Vous pouvez le faire en deux
HADDPS
instructions SSE3:Cela met la somme de tous les éléments.
ANDPS
, qui est une instruction (le masque étant constante, bien sûr).Je n'hésiterais pas à donner SSE 4.2 de l'essayer. Si vous faites cela plusieurs fois (je suppose que vous êtes, si la performance est un problème), vous pouvez pré-charger un registre avec (1,1,1,1), puis faire plusieurs dot4(my_vec(s), one_vec) sur elle. Oui, il fait un superflu de multiplier, mais ceux-ci sont relativement bon marché ces jours-ci et une op est susceptible d'être dominé par l'horizontale de dépendances, qui peut être plus optimisé dans le nouveau ESS dot fonction du produit. Vous devez tester pour voir si elle est plus performante que la double horizontale ajouter Paul R posté.
Je suggère également la comparant à droite scalaire (ou scalaires ESS) code - assez étrangement, il est souvent plus rapide (généralement parce qu'intérieurement il est sérialisé mais bien en pipeline à l'aide de registre de dérivation, où les particuliers horizontale instructions peuvent ne pas être rapide pathed (encore)), sauf si vous exécutez SIMT-comme code, il semble que vous ne l'êtes pas (sinon, vous n'quatre points produits).
dpps
est de 4 uop, 13c de la latence. (Mais un par 1,5 c, débit).haddps
est 3uops, 6c de latence. (un par 2c débit). Magasin et scalaire n'est pas trop mauvaise, parce qu'elle ne coûte pas beaucoup d'uop, mais c'est assez mauvais pour le temps de latence par rapport à Kornel de réponse. Scalaire de la fpo ont le même temps de latence comme vecteur de la fpo, cependant. Votre "étroitement canalisée à l'aide de registre bypass" la spéculation n'est pas correct. Tout, sauf de la div est entièrement canalisée, mais vous avez raison, horizontal instructions ne sont pas rapide pathed. Ils sont décodés à l'intérieur shuffle uop.