web-dev-qa-db-fra.com

Combien de cycles CPU sont nécessaires pour chaque instruction d'assemblage?

J'ai entendu qu'il y avait un livre Intel en ligne qui décrit les cycles de processeur nécessaires pour une instruction d'assemblage spécifique, mais je ne peux pas le découvrir (après avoir essayé dur). Quelqu'un pourrait-il me montrer comment trouver le cycle du processeur s'il vous plaît?

Voici un exemple, dans le code ci-dessous, mov/lock correspond à 1 cycle CPU et xchg à 3 cycles CPU.

// This part is Platform dependent!
#ifdef WIN32
inline int CPP_SpinLock::TestAndSet(int* pTargetAddress, 
                                              int nValue)
{
    __asm
    {
        mov edx, dword ptr [pTargetAddress]
        mov eax, nValue
        lock xchg eax, dword ptr [edx]
    }
    // mov = 1 CPU cycle
    // lock = 1 CPU cycle
    // xchg = 3 CPU cycles
}

#endif // WIN32

BTW: voici l'URL du code que j'ai publié: http://www.codeproject.com/KB/threads/spinlocks.aspx

47
George2

Compte tenu du pipelining, du traitement hors service, du microcode, des processeurs multicœurs, etc., il n'y a aucune garantie qu'une section particulière du code d'assemblage prendra exactement x cycles CPU/cycle d'horloge/quels que soient les cycles.

Si une telle référence existe, elle ne pourra fournir que des généralisations générales compte tenu d'une architecture particulière, et selon la façon dont le microcode est implémenté, vous pouvez constater que le Pentium M est différent du Core 2 Duo qui est différent du dual core AMD , etc.

Notez que cet article a été mis à jour en 2000 et écrit précédemment. Même le Pentium 4 est difficile à cerner en ce qui concerne le calendrier des instructions - PIII, PII et le pentium d'origine étaient plus faciles, et les textes référencés étaient probablement basés sur les processeurs précédents qui avaient un calendrier d'instruction plus bien défini.

De nos jours, les gens utilisent généralement l'analyse statistique pour estimer le timing du code.

30
Adam Davis

Ce que les autres réponses disent qu'il est impossible de prédire avec précision les performances du code exécuté sur un processeur moderne est vrai, mais cela ne signifie pas que les latences sont inconnues ou que les connaître est inutile.

Les latences exactes des processeurs Intels et AMD sont répertoriées dans tableaux d'instructions d'Agner Fog . Voir aussi Manuel de référence de l'optimisation des architectures Intel® 64 et IA-32 , et Latences des instructions et débit pour les processeurs AMD et Intel x86 (tiré du lien uniquement supprimé de Can Berk Güder, maintenant uniquement supprimé) répondre). AMD a également des manuels pdf sur son propre site Web avec leurs valeurs officielles.

Pour (micro-) optimiser les boucles serrées, connaître les latences de chaque instruction peut beaucoup aider à planifier manuellement votre code. Le programmeur peut faire beaucoup d'optimisations que le compilateur ne peut pas faire (car le compilateur ne peut pas garantir qu'il ne changera pas la signification du programme).

Bien sûr, cela nécessite encore que vous connaissiez beaucoup d'autres détails sur le processeur, tels que la profondeur de son pipeline, le nombre d'instructions qu'il peut émettre par cycle, le nombre d'unités d'exécution, etc. Et bien sûr, ces chiffres varient pour différents processeurs. Mais vous pouvez souvent arriver à une moyenne raisonnable qui fonctionne plus ou moins pour tous les processeurs.

Il convient toutefois de noter qu'il faut beaucoup de travail pour optimiser même quelques lignes de code à ce niveau. Et il est facile de faire quelque chose qui se révèle être une pessimisation. Les processeurs modernes sont extrêmement compliqués et ils s'efforcent extrêmement d'obtenir de bonnes performances avec un mauvais code. Mais il y a aussi des cas qu'ils ne peuvent pas gérer efficacement, ou où vous - pensez vous êtes intelligent et faites du code efficace, et il s'avère que cela ralentit le CPU.

Modifier En consultant le manuel d'optimisation d'Intel, le tableau C-13: la première colonne est de type instruction, puis il y a un certain nombre de colonnes pour la latence pour chaque CPUID. Le CPUID indique à quelle famille de processeurs les numéros s'appliquent et sont expliqués ailleurs dans le document. La latence spécifie combien de cycles il faut avant que le résultat de l'instruction ne soit disponible, c'est donc le nombre que vous recherchez.

Les colonnes de débit indiquent combien de ce type d'instructions peuvent être exécutées par cycle.

En recherchant xchg dans ce tableau, nous voyons que selon la famille de CPU, cela prend 1-3 cycles et un mov prend 0,5-1. Ce sont pour les formulaires d'enregistrement à enregistrement des instructions, pas pour un lock xchg avec de la mémoire, ce qui est beaucoup plus lent. Et plus important encore, latence extrêmement variable et impact sur le code environnant (beaucoup plus lent en cas de conflit avec un autre noyau), donc ne regarder que dans le meilleur des cas est une erreur. (Je n'ai pas recherché la signification de chaque CPUID, mais je suppose que le .5 concerne le Pentium 4, qui exécutait certains composants de la puce à double vitesse, ce qui lui permet de faire les choses en demi-cycles)

Cependant, je ne vois pas vraiment pourquoi vous prévoyez d'utiliser ces informations, mais si vous connaissez la famille de CPU exacte sur laquelle le code s'exécute, puis l'addition de la latence vous indique le nombre minimum de cycles requis pour exécuter cette séquence d'instructions .

22
jalf

Les processeurs modernes sont des bêtes complexes, utilisant pipelining , exécution superscalaire , et exécution dans le désordre parmi d'autres techniques qui rendent l'analyse des performances difficile. . mais pas impossible !

Bien que vous ne puissiez plus simplement additionner les latences d'un flux d'instructions pour obtenir le temps d'exécution total, vous pouvez toujours obtenir une analyse (souvent) très précise du comportement d'un morceau de code (en particulier une boucle) comme décrit ci-dessous et dans d'autres ressources liées.

Horaires d'instruction

Tout d'abord, vous avez besoin des horaires réels. Celles-ci varient selon l'architecture du processeur, mais la meilleure ressource actuellement pour les timings x86 est Agner Fog's tables d'instructions . Couvrant pas moins de trente différentes microarchitecures, ces tableaux listent l'instruction latence, qui est le temps minimum/typique qu'une instruction prend des entrées prêtes à sortir disponibles . Selon les mots d'Agner:

Latence: C'est le délai que l'instruction génère dans une chaîne de dépendances. Les nombres sont des valeurs minimales. Les échecs de cache, le désalignement et les exceptions peuvent augmenter considérablement le nombre d'horloge. Lorsque l'hyperthreading est activé, l'utilisation des mêmes unités d'exécution dans l'autre thread entraîne des performances inférieures. Les nombres dénormaux, les NAN et l'infini n'augmentent pas la latence. L'unité de temps utilisée est les cycles d'horloge de base, et non les cycles d'horloge de référence donnés par le compteur d'horodatage.

Ainsi, par exemple, l'instruction add a une latence d'un cycle, donc une série d'instructions dépendantes, comme indiqué, aura une latence de 1 cycle par add:

add eax, eax
add eax, eax
add eax, eax
add eax, eax  # total latency of 4 cycles for these 4 adds

Notez que cela ne signifie pas que les instructions add ne prendront qu'un cycle chacune. Par exemple, si les instructions d'ajout n'étaient pas dépendantes , il est possible que sur les puces modernes, les 4 instructions d'ajout puissent s'exécuter indépendamment dans le même cycle:

add eax, eax
add ebx, ebx
add ecx, ecx
add edx, edx # these 4 instructions might all execute, in parallel in a single cycle

Agner fournit une métrique qui capture une partie de ce parallélisme potentiel, appelée débit réciproque:

Débit réciproque: Le nombre moyen de cycles d'horloge de base par instruction pour une série d'instructions indépendantes du même type dans le même thread.

Pour add, cela est répertorié comme 0.25 ce qui signifie que jusqu'à 4 add instructions peuvent exécuter chaque cycle (donnant un débit réciproque de 1 / 4 = 0.25).

Le nombre de débits réciproques donne également un indice sur la capacité pipelining d'une instruction. Par exemple, sur les puces x86 les plus récentes, les formes courantes de l'instruction imul ont une latence de 3 cycles, et en interne, une seule unité d'exécution peut les gérer (contrairement à add qui a généralement quatre add -unités capables). Pourtant, le débit observé pour une longue série d'instructions imul indépendantes est de 1/cycle, pas 1 tous les 3 cycles comme vous pouvez vous y attendre étant donné la latence de 3. La raison en est que l'unité imul est pipelined: il peut démarrer un nouveau imul à chaque cycle, même si la multiplication précédente n'est pas terminée.

Cela signifie qu'une série d'instructions indépendantimul peut s'exécuter jusqu'à 1 par cycle, mais une série dépendanteimul les instructions s'exécuteront à seulement 1 tous les 3 cycles (puisque le prochain imul ne peut pas démarrer tant que le résultat de l'ancien n'est pas prêt).

Ainsi, avec ces informations, vous pouvez commencer à voir comment analyser les synchronisations des instructions sur les processeurs modernes.

Analyse détaillée

Pourtant, ce qui précède ne fait qu'effleurer la surface. Vous avez maintenant plusieurs façons de regarder une série d'instructions (latence ou débit) et il n'est peut-être pas clair lequel utiliser.

En outre, il existe d'autres limites non capturées par les chiffres ci-dessus, telles que le fait que certaines instructions sont en concurrence pour les mêmes ressources au sein du CPU, et des restrictions dans d'autres parties du pipeline du CPU (comme le décodage d'instructions) qui peuvent entraîner une baisse le débit global que vous calculeriez simplement en regardant la latence et le débit. Au-delà de cela, vous avez des facteurs "au-delà des ALU" tels que l'accès à la mémoire et la prédiction de branche: des sujets entiers en eux-mêmes - vous pouvez surtout bien les modéliser, mais cela prend du travail. Par exemple, voici un article récent où la réponse couvre en détail la plupart des facteurs pertinents.

Couvrir tous les détails augmenterait la taille de cette réponse déjà longue d'un facteur 10 ou plus, je vais donc vous indiquer les meilleures ressources. Agner Fog a un Optimizing Asemblyguide qui couvre en détail l'analyse précise d'une boucle avec une douzaine d'instructions. Voir " 12.7 Un exemple d'analyse des goulots d'étranglement dans les boucles vectorielles" qui commence à la page 95 dans la version actuelle du PDF.

L'idée de base est de créer une table, avec une ligne par instruction et de marquer les ressources d'exécution que chacun utilise. Cela vous permet de voir les goulots d'étranglement de débit. En outre, vous devez examiner la boucle pour les dépendances transportées, pour voir si l'une d'entre elles limite le débit (voir " 12.16 Analyse des dépendances" pour un complexe Cas).

Si vous ne voulez pas le faire à la main, Intel a publié Intel Architecture Code Analyzer , qui est un outil qui automatise cette analyse. Il n'a actuellement pas été mis à jour au-delà de Skylake, mais les résultats sont encore largement raisonnables pour Kaby Lake car la microarchitecture n'a pas beaucoup changé et donc les délais restent comparables. Cette réponse va dans beaucoup de détails et fournit un exemple de sortie, et le guide de l'utilisateur n'est pas à moitié mauvais (bien qu'il soit obsolète par rapport aux versions les plus récentes) .

Autres sources

Agner fournit généralement des timings pour les nouvelles architectures peu de temps après leur sortie, mais vous pouvez également consulter instlatx64 pour des timings organisés de manière similaire dans le InstLatX86 et InstLatX64 résultats. Les résultats couvrent beaucoup d'anciennes puces intéressantes et les nouvelles puces apparaissent généralement assez rapidement. Les résultats sont en grande partie cohérents avec ceux d'Agner, à quelques exceptions ici et là. Vous pouvez également trouver la latence de la mémoire et d'autres valeurs sur cette page.

Vous pouvez même obtenir les résultats de synchronisation directement d'Intel dans leur Manuel d'optimisation IA32 et Intel 64 dans Annexe C: LATENCE D'INSTRUCTIONS ET DÉBIT . Personnellement, je préfère la version d'Agner car ils sont plus complets, arrivent souvent avant la mise à jour du manuel Intel et sont plus faciles à utiliser car ils fournissent une feuille de calcul et une version PDF PDF.

Enfin, le wiki de balise x86 possède une multitude de ressources sur l'optimisation x86, y compris des liens vers d'autres exemples sur la façon d'effectuer une analyse précise de cycle de séquences de code.

Si vous souhaitez approfondir le type d '"analyse de flux de données" décrit ci-dessus, je recommanderais A Whirlwind Introduction to Data Flow Graphs .

19
BeeOnRope

Mesurer et compter les cycles CPU n'a plus de sens sur le x86.

Tout d'abord, demandez-vous pour quel processeur vous comptez les cycles? Core-2? un Athlon? Pentium-M? Atome? Tous ces CPU exécutent du code x86 mais tous ont des temps d'exécution différents. L'exécution varie même entre différentes étapes du même processeur.

Le dernier x86 où le comptage de cycles était logique était le Pentium-Pro.

Considérez également qu'à l'intérieur du CPU, la plupart des instructions sont transcodées en microcode et exécutées dans le désordre par une unité d'exécution interne qui ne ressemble même pas à distance à un x86. Les performances d'une instruction CPU unique dépendent de la quantité de ressources disponibles dans l'unité d'exécution interne.

Ainsi, le temps pour une instruction dépend non seulement de l'instruction elle-même mais aussi du code environnant.

Quoi qu'il en soit: vous pouvez estimer l'utilisation des ressources de débit et la latence des instructions pour différents processeurs. Les informations pertinentes peuvent être trouvées sur les sites Intel et AMD.

Agner Fog a un très joli résumé sur son site web. Voir les tableaux d'instructions pour la latence, le débit et le nombre d'uop. Voir la microarchictecture PDF pour apprendre à les interpréter.

http://www.agner.org/optimize

Mais notez que xchg- avec mémoire n'a pas de performances prévisibles, même si vous ne regardez qu'un seul modèle de CPU. Même dans le cas sans conflit avec la ligne de cache déjà chaude dans le cache L1D, le fait d'être une barrière de mémoire complète signifie que son impact dépend beaucoup des charges et des stockages vers d'autres adresses dans le code environnant.


BTW - puisque votre exemple de code est un bloc de construction de base de données sans verrouillage: avez-vous envisagé d'utiliser les fonctions intégrées du compilateur? Sur win32, vous pouvez inclure intrin.h et utiliser des fonctions telles que _InterlockedExchange.

Cela vous donnera un meilleur temps d'exécution car le compilateur peut aligner les instructions. L'assembleur en ligne force toujours le compilateur à désactiver les optimisations autour du code asm.

13
Nils Pipenbrinck

verrouiller xchg eax, dword ptr [edx]

Notez que le verrou verrouillera la mémoire pour la récupération de mémoire pour tous les cœurs, cela peut prendre 100 cycles sur certains cœurs multiples et une ligne de cache devra également être vidée. Il bloquera également le pipeline. Je ne m'inquiéterais donc pas du reste.

Les performances optimales reviennent donc au réglage de vos algorithmes dans les régions critiques.

Remarque sur un seul cœur, vous pouvez l'optimiser en retirant le verrou, mais il est nécessaire pour les multicœurs.

6
ben