Dans chaque langage de programmation, il existe des ensembles d'opcodes recommandés par rapport aux autres. J'ai essayé de les énumérer ici, par ordre de vitesse.
- Au niveau du bit
- Addition / soustraction de nombres entiers
- Multiplication / division entière
- Comparaison
- Contrôle du flux
- Addition / soustraction de flotteur
- Multiplication / division de flotteur
Lorsque vous avez besoin de code haute performance, C ++ peut être optimisé manuellement en assembleur, pour utiliser des instructions SIMD ou un flux de contrôle plus efficace, des types de données, etc. J'essaie donc de comprendre si le type de données (int32 / float32 / float64) ou l'opération utilisée ( *
, +
, &
) influe sur la performance au niveau du processeur.
- Une seule multiplication est-elle plus lente sur le processeur qu'un ajout?
- Dans la théorie MCU, vous apprenez que la vitesse des opcodes est déterminée par le nombre de cycles CPU nécessaires pour s'exécuter. Cela signifie-t-il donc que la multiplication prend 4 cycles et que l'addition prend 2 cycles?
- Quelles sont exactement les caractéristiques de vitesse des opcodes mathématiques et de flux de contrôle de base?
- Si deux opcodes prennent le même nombre de cycles à exécuter, alors les deux peuvent être utilisés de manière interchangeable sans aucun gain / perte de performances?
- Tout autre détail technique que vous pouvez partager concernant les performances du processeur x86 est apprécié
c++
performance
optimization
Robinicks
la source
la source
Réponses:
Les guides d'optimisation d'Agner Fog sont excellents. Il a des guides, des tableaux de synchronisation des instructions et des documents sur la microarchitecture de toutes les conceptions récentes de CPU x86 (remontant jusqu'à Intel Pentium). Voir aussi quelques autres ressources liées depuis /programming//tags/x86/info
Juste pour le plaisir, je répondrai à certaines des questions (chiffres des processeurs Intel récents). Le choix des opérations n'est pas le principal facteur d'optimisation du code (sauf si vous pouvez éviter la division.)
Oui (sauf si c'est par une puissance de 2). (3-4x la latence, avec seulement un par débit d'horloge sur Intel.) Ne vous éloignez pas pour l'éviter, car c'est aussi rapide que 2 ou 3 ajouts.
Consultez les tableaux d'instructions et le guide de microarchitecture d'Agner Fog si vous voulez savoir exactement : P. Soyez prudent avec les sauts conditionnels. Les sauts inconditionnels (comme les appels de fonction) ont une petite surcharge, mais pas beaucoup.
Non, ils pourraient rivaliser pour le même port d'exécution qu'autre chose, ou ils pourraient ne pas le faire. Cela dépend des autres chaînes de dépendance sur lesquelles le processeur peut travailler en parallèle. (En pratique, il n'y a généralement pas de décision utile à prendre. Il arrive parfois que vous puissiez utiliser un décalage vectoriel ou un shuffle vectoriel, qui s'exécutent sur différents ports sur les processeurs Intel. Mais le décalage par octets de l'ensemble du registre (
PSLLDQ
etc.) fonctionne dans l'unité de lecture aléatoire.)Les documents microarch d'Agner Fog décrivent les pipelines des processeurs Intel et AMD avec suffisamment de détails pour déterminer exactement combien de cycles une boucle devrait prendre par itération, et si le goulot d'étranglement est le débit uop, une chaîne de dépendance ou la contention pour un port d'exécution. Voir certaines de mes réponses sur StackOverflow, comme celle-ci ou celle-ci .
En outre, http://www.realworldtech.com/haswell-cpu/ (et similaire pour les conceptions antérieures) est amusant à lire si vous aimez la conception de CPU.
Voici votre liste, triée pour un processeur Haswell, basée sur mes meilleures estimations. Ce n'est pas vraiment une façon utile de penser aux choses pour autre chose que le réglage d'une boucle asm. Les effets de prédiction de cache / branche dominent généralement, alors écrivez votre code pour avoir de bons modèles. Les nombres sont très ondulants et essaient de tenir compte d'une latence élevée, même si le débit n'est pas un problème, ou de générer plus d'ups qui obstruent le tuyau pour que d'autres choses se produisent en parallèle. Esp. les numéros de cache / branche sont très composés. La latence est importante pour les dépendances transportées en boucle, le débit est important lorsque chaque itération est indépendante.
TL: DR ces chiffres sont composés en fonction de ce que j'imagine pour un cas d'utilisation "typique", en ce qui concerne les compromis entre la latence, les goulots d'étranglement des ports d'exécution et le débit frontal (ou les blocages pour des choses comme les échecs de branche ). Veuillez ne pas utiliser ces chiffres pour tout type d'analyse de performance sérieuse .
décalage et rotation (nombre de const à la compilation) /
versions vectorielles de tous ces éléments (1 à 4 par cycle de débit, 1 cycle de latence)
tmp += 7
dans une boucle au lieu detmp = i*7
)sum
variable. (Je pourrais peser cela et fp mul aussi bas que 1 ou aussi haut que 5 selon le cas d'utilisation)._mm_insert_epi8
, etc.)y = x ? a : b
, ouy = x >= 0
) (test / setcc
oucmov
)%
par une constante de temps de compilation (non-puissance de 2).PHADD
ajout de valeurs dans un vecteur)J'ai totalement inventé cela sur la base de suppositions . Si quelque chose ne va pas, c'est soit parce que je pensais à un cas d'utilisation différent, soit à une erreur d'édition.
Le coût relatif des choses sur les processeurs AMD sera similaire, sauf qu'ils ont des décaleurs entiers plus rapides lorsque le nombre de décalages est variable. Les processeurs de la famille AMD Bulldozer sont bien sûr plus lents sur la plupart des codes, pour diverses raisons. (Ryzen est assez bon pour beaucoup de choses).
Gardez à l'esprit qu'il est vraiment impossible de réduire les choses à un coût unidimensionnel . Outre les erreurs de cache et les erreurs de branchement, le goulot d'étranglement dans un bloc de code peut être la latence, le débit uop total (frontend) ou le débit d'un port spécifique (port d'exécution).
Une opération "lente" comme la division FP peut être très bon marché si le code environnant maintient le CPU occupé avec d'autres travaux . (le vecteur FP div ou sqrt sont 1 uop chacun, ils ont juste une latence et un débit médiocres. Ils bloquent uniquement l'unité de division, pas le port d'exécution entier sur lequel il est activé. La division entière est de plusieurs uops.) Donc, si vous n'avez qu'une seule division FP pour chaque ~ 20 mul et ajouter, et il y a d'autres travaux à faire par le CPU (par exemple une itération de boucle indépendante), alors le "coût" de la div FP pourrait être à peu près le même qu'un FP mul. C'est probablement le meilleur exemple de quelque chose qui est à faible débit quand c'est tout ce que vous faites, mais qui se mélange très bien avec d'autres codes (lorsque la latence n'est pas un facteur), en raison du faible nombre total d'ups.
Notez que la division entière n'est pas aussi conviviale que le code environnant: Sur Haswell, c'est 9 uops, avec un par débit 8-11c, et une latence 22-29c. (La division 64 bits est beaucoup plus lente, même sur Skylake.) Ainsi, les nombres de latence et de débit sont quelque peu similaires à FP div, mais FP div n'est qu'un uop.
Pour des exemples d'analyse d'une courte séquence d'insns pour le débit, la latence et le nombre total d'ups, consultez certaines de mes réponses SO:
sum += x[i] * y[i]
en déroulant avec plusieurs accumulateurs vectoriels pour masquer la latence FMA. C'est assez technique et de bas niveau, mais il vous montre le type de sortie en langage assembleur que vous voulez que votre compilateur fasse, et pourquoi c'est important.IDK si d'autres personnes écrivent des réponses SO incluant ce type d'analyse. J'ai beaucoup plus de facilité à trouver le mien, car je sais que je vais souvent dans ce détail et je me souviens de ce que j'ai écrit.
la source
Cela dépend du CPU en question, mais pour un CPU moderne, la liste est quelque chose comme ceci:
Selon le processeur, il peut y avoir un coût considérable pour travailler avec des types de données 64 bits.
Vos questions:
if
ce que vous pouvez raisonnablement faire avec l'arithmétique.Et enfin, si vous créez un jeu, ne vous inquiétez pas trop de tout cela, mieux vous concentrer sur la création d'un bon jeu que de couper les cycles CPU.
la source
J'ai fait un test sur le fonctionnement entier qui a bouclé un million de fois sur x64_64, arrive à une brève conclusion comme ci-dessous,
ajouter --- 116 microsecondes
sous ---- 116 microsecondes
mul ---- 1036 microsecondes
div ---- 13037 microsecondes
les données ci-dessus ont déjà réduit la surcharge induite par la boucle,
la source
Les manuels du processeur Intel sont téléchargeables gratuitement sur leur site Web. Ils sont assez grands mais peuvent techniquement répondre à votre question. Le manuel d'optimisation en particulier est ce que vous recherchez, mais le manuel d'instructions contient également les temps et les latences pour la plupart des principales lignes de CPU pour les instructions simd car elles varient d'une puce à l'autre.
En général, je considérerais les branches complètes ainsi que la recherche de pointeurs (traverals de liste de liens, appeler des fonctions virtuelles) comme les meilleurs pour les tueurs de perf, mais les processeurs x86 / x64 sont très bons dans les deux, par rapport à d'autres architectures. Si vous portez sur une autre plate-forme, vous verrez à quel point ils peuvent être problématiques, si vous écrivez du code haute performance.
la source