Comment obtenir le nombre de cycles CPU en x86_64 à partir de C++?
J'ai vu ce post sur DONC qui contient le code C pour obtenir la dernière version de CPU Cycle count:
PROCESSEUR nombre de cycles en fonction de profilage en C/C++ Linux x86_64
Est-il une manière que je peux utiliser ce code en C++ (windows et linux solutions de bienvenue)? Bien qu'écrit en C (et C étant un sous-ensemble de C++) je ne suis pas trop certain si ce code serait de travailler dans un projet C++ et si non, comment le traduire?
Je suis en utilisant x86-64
EDIT2:
Trouvé cette fonction, mais ne peut pas obtenir VS2010 de reconnaître l'assembleur. Dois-je inclure quoi que ce soit? (Je crois que j'ai de swap uint64_t
à long long
pour windows....?)
static inline uint64_t get_cycles()
{
uint64_t t;
__asm volatile ("rdtsc" : "=A"(t));
return t;
}
EDIT3:
De code ci-dessus, j'obtiens l'erreur:
"erreur C2400: assembleur en ligne erreur de syntaxe dans "opcode'; trouvé de données
le "type"
Quelqu'un pourrait s'il vous plaît aider?
- "C++ étant un sous-ensemble de C" - avez-vous dire que l'inverse?
- Visual Studio ne prend pas en charge l'assemblage sur x86-64.
- Je présume que tu veux dire MSVC? Je pense que j'ai le compilateur ICC installé trop et juste pour être sûr que je suis juste l'installation de MinGW
- Pour obtenir
uint64_t
vous devriez#include <stdint.h>
(en fait<cstdint>
mais votre compilateur est probablement trop vieux pour avoir ça.) - oui je voulais dire MSVC. J'ai complètement oublié que vous pouvez remplacer les compilateurs depuis je n'ai jamais essayé.
- Les gars, maintenant, je reçois l'erreur dans le edit3. J'ai inclus <stdint.h> et c'est sur Windows 7
- En outre, Visual Studio ne prend pas en charge gcc-style de l'assemblée 😉
- Vous devez être prudent avec cette. Avec un multi-core puce, le chrono sont différentes sur les différents cœurs. Si le planificateur se déplace ton thread entre les cœurs, le comte peut sauter. Certains OS ont corrigé cela. Quelques jetons, mettre les carottes en veille pour économiser de l'énergie, alors que les cœurs de l'horloge n'avance pas.
- pour clarifier, pour les autres, la lecture de ce, VS ne prend pas en charge assembly en ligne pour la version 64 bits construit, mais il prend en charge distincte de l'assemblée des fichiers sources et des utilisations ML64.EXE pour la version 64 bits de l'assemblée. J'utilise étape de génération personnalisée pour exécuter ML64.EXE plutôt que d'utiliser la valeur par défaut de la ligne de commande, à l'aide de x64.asm comme par exemple: "ml64 /c /Zi /Fo$(OutDir)\x64.obj x64.asm" (/Zi de débogage de construire, /Zi pour la libération de construire), le fichier de sortie: "$(OutDir)\x64.obj
Vous devez vous connecter pour publier un commentaire.
À partir de GCC 4.5 et plus tard, la
__rdtsc()
intrinsèque est maintenant pris en charge par les deux MSVC et GCC.Mais la comprennent qu'il faut, c'est différent:
Voici l'original de la réponse avant de la GCC 4.5.
Tiré directement de l'un de mes projets:
Ce GNU C a Prolongé asm indique au compilateur:
volatile
: les sorties ne sont pas une pure fonction des entrées (donc, il a ré-exécuter à chaque fois, de ne pas réutiliser un vieux résultat)."=a"(lo)
et"=d"(hi)
: la sortie opérandes sont fixes registres: EAX et EDX. (machine x86 contraintes). Le x86rdtsc
instruction met son résultat de 64 bits dans EDX:EAX, afin de laisser le compilateur de choisir une sortie avec"=r"
ne fonctionne pas: il n'y a pas moyen de demander à la CPU pour le résultat à aller n'importe où ailleurs.((uint64_t)hi << 32) | lo
- zéro s'étendre de 32 bits moitiés de 64 bits (car lo et hi sontunsigned
), et logiquement maj + OU dans un même 64 bits C variable. Dans le code 32 bits, c'est juste une réinterprétation; les valeurs de toujours rester dans une paire de registres 32 bits. En 64-bits de code, vous obtenez généralement un changement d' + OU asm instructions, à moins que le haut de la moitié optimise loin.(note de l'éditeur: cela pourrait être plus efficace si vous avez utilisé
unsigned long
au lieu deunsigned int
. Ensuite, le compilateur sache quelo
était déjà à zéro étendu dans RAX. Il ne sais pas que la moitié supérieure était de zéro, de sorte|
et+
sont équivalentes s'il le voulait, de fusion d'une manière différente. La valeur intrinsèque devrait en théorie vous donner le meilleur des deux mondes aussi loin que de laisser l'optimiseur de faire un bon travail.)https://gcc.gnu.org/wiki/DontUseInlineAsm si vous pouvez l'éviter. Mais j'espère que cet article est utile si vous avez besoin de comprendre de l'ancien code qui utilise l'asm inline, donc vous pouvez réécrire avec intrinsèques. Voir aussi https://stackoverflow.com/tags/inline-assembly/info
std::uint64_t x; asm volatile ("rdtsc" : "=A"(x));
est une autre façon de lireEAX
etEDX
ensemble."=A"
va chercher soit RAX ou RDX.<x86intrin.h>
définit__rdtsc()
pour les compilateurs autres que MSVC, de sorte que vous pouvez simplement#ifdef _MSC_VER
. J'ai ajouté une réponse à cette question, puisqu'elle ressemble à un bon endroit pour une canoniques surrdtsc
intrinsèques, et de pièges sur la façon d'utiliserrdtsc
.__rdtsc()
devrait être recommandée sur l'asm inline ces jours-ci, cependant, de sorte que votre réponse pourrait utiliser une mise à jour. (Ou, espérons-le, OP accepte ma tentative de réponse canonique; j'ai déjà fermé un tas de questions similaires comme des doublons de celui-ci.) Aussi, j'ai joué un peu aveclo
ethi
unsigned long
ouuintptr_t
, de sorte que le compilateur n'aurais pas à zéro étendreeax
enrax
, ce qui contribue, et de changer|
à+
qui conduit à bizarre optimisations...Votre inline asm est rompu pour x86-64.
"=A"
en mode 64 bits permet au compilateur de choisir soit RAX ou RDX, pas EDX:EAX. Voir cette Q&A pour plus deVous n'avez pas besoin asm inline pour cette. Il n'y a aucun avantage; les compilateurs ont built-ins pour
rdtsc
etrdtscp
, et (au moins de nos jours) définir un__rdtsc
intrinsèque si vous incluez le droit des en-têtes. Mais à la différence de presque tous les autres cas (https://gcc.gnu.org/wiki/DontUseInlineAsm), il n'y a pas des inconvénients graves pour l'asm, aussi longtemps que vous êtes en utilisant un bon de mise en œuvre comme @Mysticial de.Malheureusement MSVC n'est pas d'accord avec tous les autres, sur qui-tête à utiliser pour les non-SIMD intrinsèques.
Intel intriniscs guide dit
_rdtsc
(avec un tiret) est en<immintrin.h>
, mais qui ne fonctionne pas sur gcc et clang. Ils ne définissent SIMD intrinsèques dans<immintrin.h>
, de sorte que nous sommes coincés avec<intrin.h>
(MSVC) vs<x86intrin.h>
(tout le reste, y compris les récentes CPI). Pour compat avec MSVC, Intel et la documentation de gcc et clang définir à la fois un trait de soulignement et de deux soulignent versions de la fonction.Fait amusant: le double-trait de soulignement version renvoie un entier non signé entier de 64 bits, alors que Intel documents
_rdtsc()
que le retour (signé)__int64
.Compile avec tous les 4 des principaux compilateurs: gcc/clang/CPI/MSVC, pour 32 ou 64 bits. Voir les résultats sur la Godbolt compilateur explorer, y compris un couple de test appelants.
Ces intrinsèques étaient nouveaux dans gcc4.5 (à partir de 2010) et clang3.5 (à partir de 2014). gcc4.4 et clang 3.4 sur Godbolt ne compile pas, mais gcc4.5.3 (avril 2011) ne. Vous pouvez voir l'asm inline dans l'ancien code, mais vous pouvez et devez le remplacer avec
__rdtsc()
. Les compilateurs plus d'une décennie vieille habitude de faire du plus lent que le code gcc6, gcc7, ou gcc8, et ont de moins en moins de messages d'erreur utiles.La MSVC intrinsèque a (je pense) existe beaucoup plus de temps, parce que MSVC n'a jamais soutenu inline asm x86-64. ICC13 a
__rdtsc
dansimmintrin.h
, mais ne possède pas dex86intrin.h
à tous. Plus récente de la CPI ontx86intrin.h
, au moins la façon dont Godbolt installe de Linux qu'ils font.Vous voulez définir comme signé
long long
, surtout si vous voulez soustraire eux et de les convertir à flotteur.int64_t
-> float/double est plus efficace queuint64_t
sur x86 sans AVX512. Aussi, les petits résultats négatifs pourrait être possible en raison de la CPU migrations si Tsc ne sont pas parfaitement synchronisées, et qui a probablement fait plus de sens que d'énormes nombres non signés.BTW, clang a aussi un portable
__builtin_readcyclecounter()
qui fonctionne sur n'importe quelle architecture. (Renvoie toujours zéro sur des architectures sans un compteur de cycle.) Voir la clang/LLVM langue-extension docsPour en savoir plus sur à l'aide de
lfence
(oucpuid
) pour améliorer la reproductibilité desrdtsc
et de contrôler exactement les instructions sont /ne sont pas dans la durée chronométrée par le blocage de l'exécution, voir @HadiBrais réponse sur clflush pour invalider le cache ligne via la fonction C et les commentaires pour un exemple de la différence qu'il fait.Voir aussi Est LFENCE la sérialisation sur les processeurs AMD? (TL:DR oui, avec le Spectre d'atténuation activé, sinon les noyaux laisser pertinentes MSR unset de sorte que vous devriez utiliser
cpuid
à sérialiser.) Il a toujours été défini comme une partie de la sérialisation sur Intel.Comment faire pour Référence Code des Temps d'Exécution sur le processeur Intel® IA-32 et IA-64
Jeu D'Instructions Architectures, d'un processeur Intel blanc-papier à partir de 2010.
rdtsc
compte référence cycles, pas de cœur de PROCESSEUR cycles d'horlogeIl compte, à une fréquence fixe, indépendamment de turbo /économie d'énergie, donc si vous voulez uop-par l'horloge de l'analyse, l'utilisation de compteurs de performance.
rdtsc
est exactement corrélée avec l'horloge murale (à l'exception de l'horloge du système des ajustements, c'est donc un parfait timesource poursteady_clock
). Il répond à la CPU fréquence nominale, c'est à dire l'annonce d'autocollant de fréquence. (Ou près de que. par exemple, 2592 MHz sur un core i7-6700HQ 2.6 GHz Skylake.)Si vous l'utilisez pour microbenchmarking, inclure une période d'échauffement d'abord vous assurer que votre CPU est déjà au max de la vitesse de l'horloge avant de commencer à chronométrer. (Et éventuellement désactiver le turbo et dire à votre système d'exploitation à préférer max vitesse d'horloge afin d'éviter les changements de fréquence du PROCESSEUR lors de votre microbenchmark). Ou mieux, utiliser une bibliothèque qui vous donne accès à de l'équipement des compteurs de performance, ou un truc comme perf stat pour une partie du programme si votre chronométré région est assez long que vous pouvez joindre un
perf stat -p PID
.Que généralement vous aurez envie de garder l'horloge du CPU fixe pour microbenchmarks, cependant, sauf si vous voulez voir comment les différentes charges obtiendrez Skylake à l'horloge quand liées à la mémoire ou quoi que ce soit. (Notez que la bande passante de la mémoire /temps de latence est généralement fixe, à l'aide d'une horloge différente que les carottes. Au ralenti, la vitesse d'horloge, d'une L2 ou L3 cache miss prend beaucoup moins de noyau de cycles d'horloge.)
constant_tsc
), qui ne s'arrête pas lorsque l'horloge s'arrête (nonstop_tsc
). Aussi quelques conseils, par exemple, ne pas prendre le temps de le dire, prendre la médiane (il y aura de très hautes valeurs aberrantes).cli
) et de la virtualisation desrdtsc
sous une VM. Et bien sûr, des trucs de base comme des interruptions étant possible, afin de répéter votre timing de nombreuses fois et jeter les valeurs aberrantes.Déterminer TSC fréquence sur Linux. Par programmation à l'interrogation de la TSC de fréquence est difficile et peut-être pas possible, en particulier dans l'espace utilisateur, ou peut donner un résultat moins bon que de procéder à l'étalonnage. L'étalonnage à l'aide d'un autre temps connus-source prend du temps. Voir cette question pour en savoir plus sur la façon dont il est difficile de convertir TSC de nanosecondes (et qu'il serait bien si vous pouviez demander au système d'exploitation que le taux de conversion est, parce que l'OS a déjà fait lors de l'initialisation).
Si vous êtes microbenchmarking avec RDTSC pour des fins d'optimisation, votre meilleur pari est d'utiliser les tiques et les ignorer même essayer de les convertir à la nanoseconde. Sinon, utilisez une haute résolution de la bibliothèque de l'heure fonction comme
std::chrono
ouclock_gettime
. Voir plus rapide équivalent de gettimeofday pour une discussion /comparaison des fonctions d'horodatage ou de la lecture partagée timestamp à partir de la mémoire pour éviterrdtsc
entièrement si votre exigence de précision est assez faible pour une minuterie d'interruption ou le fil de mise à jour.Voir aussi Calculer l'heure du système à l'aide de rdtsc sur la recherche de la fréquence du quartz, et le multiplicateur.
C'est pas garanti que le Tsc de tous les coeurs sont en synchronisation. Donc, si votre fil migre vers un autre PROCESSEUR core entre
__rdtsc()
, il peut y avoir un supplément de l'inclinaison. (La plupart des Systèmes d'exploitation de la tentative de synchronisation de la Tsc de tous les cœurs, si, donc, normalement, ils vont être très proche.) Si vous utilisezrdtsc
directement, vous voulez probablement à la broche de votre programme ou de fil d'une base, par exemple, avectaskset -c 0 ./myprogram
sur Linux.CPU TSC opération d'extraction en particulier dans le multicœur multi-processeur de l'environnement dit que Nehalem et les nouveaux ont le TSC synchronisés et verrouillées ensemble, pour tous les cœurs dans un package (c'est à dire invariant TSC). Mais multi-socket systèmes peut encore être un problème. Même les systèmes plus anciens (comme avant Core2 en 2007) pourrait avoir un TSC qui s'arrête lorsque l'horloge de base s'arrête, ou que c'est lié à l'âme la fréquence de l'horloge à la place de cycles de référence. (Processeurs récents ont toujours constante-TSC et non-stop-TSC.) Voir @amdn sa réponse sur cette question pour plus de détails.
Quelle est la qualité de l'asm de l'aide de l'intrinsèque?
C'est à peu près aussi bon que vous obtiendrez à partir de @Mysticial GNU C asm inline, ou mieux, car il sait que les bits de poids de RAX sont remis à zéro. La principale raison pour laquelle vous voulez garder asm inline est pour compat avec croustillant vieux compilateurs.
Un non en ligne de la version de la
readTSC
fonction elle-même compile avec MSVC pour x86-64 comme ceci:Pour 32-bit conventions d'appel qui retournent des entiers 64 bits dans
edx:eax
, c'est justerdtsc
/ret
. Non pas que cela importe, vous voulez toujours ce à la ligne.Dans un test de l'appelant qui l'utilise deux fois et soustrait à la fois un intervalle:
Tous les 4 compilateurs faire pratiquement le même code. C'est du CCG 32 bits de sortie:
C'est MSVC x86-64 sortie (avec nom-demangling appliquée). gcc/clang/CPI émettent un code identique.
Tous les 4 compilateurs utilisent
or
+mov
au lieu delea
de combiner le bas et le haut moitiés dans un registre différent. Je suppose que c'est une sorte de conserves de séquence qu'ils ne parviennent pas à optimiser.Mais écrire un shift/lea en asm inline-même n'est guère meilleure. Vous auriez priver le compilateur de la possibilité d'ignorer le haut de 32 bits de la suite dans EDX, si vous êtes timing un si court intervalle que vous ne gardez un résultat sur 32 bits. Ou si le compilateur décide de stocker l'heure de début de la mémoire, il peut utiliser deux 32-bit magasins au lieu de shift/ou /mov. Si 1 extra uop dans le cadre de votre emploi du temps vous dérange, vous feriez mieux d'écrire l'ensemble de votre microbenchmark dans le plus pur à l'asm.
Cependant, nous pouvons peut-être obtenir le meilleur des deux mondes avec une version modifiée de @Mysticial code:
Sur Godbolt, cela ne donne parfois mieux asm que
__rdtsc()
pour gcc/clang/CPI, mais d'autres fois il astuces des compilateurs en utilisant un supplément de registre pour enregistrer lo et le salut séparément, de sorte que clang peut optimiser en((end_hi-start_hi)<<32) + (end_lo-start_lo)
. J'espère que si il y a de vrais registre de pression, compilateurs combiner plus tôt. (gcc et de la CCI, encore sauver lo/hi séparément, mais ne pas optimiser ainsi.)Mais 32-bit gcc8 fait désordre, la compilation de même juste le
rdtsc()
fonction elle-même, avec unadd/adc
avec des zéros au lieu de simplement retourner le résultat dans edx:eax comme clang n'. (gcc6 et les versions antérieures n'ok avec|
au lieu de+
, mais certainement préfèrent le__rdtsc()
intrinsèque si vous vous souciez de code 32 bits-gen de gcc).tsc
n'est pas nécessairement tique à la "vignette " fréquence", mais plutôt à la tsc de la fréquence. Sur certaines machines, ce sont les mêmes, mais sur plusieurs machines (comme les Skylake client et dérivés uarchs), ils sont souvent pas. Par exemple, mon i7-6700HQ autocollant de fréquence 2600 MHz, mais le tsc fréquence est de 2592 MHz. Ils ne sont probablement pas le même dans les cas les différentes horloges ils sont basés sur ne peuvent pas être faites à la ligne exactement à la même fréquence lors de la mise à l'échelle de la fréquence par un nombre entier. De nombreux outils ne prennent pas en compte cette différence menant à de petites erreurs.dmesg | grep tsc
de voir les deux valeurs. Je reçoistsc: Detected 2600.000 MHz processor ... tsc: Detected 2592.000 MHz TSC
. Vous pouvez également utiliserturbostat
de le montrer.VC++ utilise un tout autre syntaxe pour assembly en ligne, mais seulement dans les versions 32 bits. Le compilateur 64 bits ne prend pas en charge assembly en ligne à tous.
Dans ce cas, c'est probablement aussi bien --
rdtsc
a (au moins) deux problème majeur quand il s'agit de code de timing des séquences. En premier (comme la plupart des instructions), il peut être exécutée en dehors de l'ordre, donc si vous essayez de le temps d'une courte séquence de code, lerdtsc
avant et après que le code pourrait à la fois être exécutée avant, ou deux après, ou qu'avez-vous (je suis assez sûr que les deux s'exécute toujours dans l'ordre à l'égard les uns des autres, mais, au moins, la différence ne sera jamais négatif).Deuxième, sur un multi-core (ou multiprocesseur) un système de rdtsc peut exécuter sur un seul core/processeur et l'autre sur un autre core/processeur. Dans un tel cas, un résultat négatif est tout à fait possible.
En règle générale, si vous souhaitez une précision de la minuterie sous Windows, vous allez être mieux d'utiliser
QueryPerformanceCounter
.Si vraiment vous insistez sur l'utilisation
rdtsc
, je crois que vous aurez à faire dans un module séparé entièrement écrit en langage d'assemblage (ou utiliser un compilateur intrinsèque), puis lié avec C ou C++. Je n'ai jamais écrit que le code pour le mode 64 bits, mais en mode 32 bits, il ressemble à quelque chose comme ceci:Je sais que cela semble étrange, mais c'est en fait la droite. Vous exécutez CPUID parce que c'est une sérialisation d'instruction (ne peut pas être exécutée en dehors de la commande) et est disponible en mode utilisateur. Vous lancez trois fois avant de commencer à chronométrer parce que Intel documents le fait que la première exécution est/peut courir à une vitesse différente que le deuxième (et ce qu'ils recommandent est de trois, de sorte que trois c'est).
Puis vous exécutez votre code sous test, un autre cpuid à force de sérialisation, et la finale rdtsc pour obtenir le temps après le code fini.
Avec que, vous voulez utiliser tous les moyens à votre système d'exploitation fournitures de forcer tous à courir sur un processus d'/core. Dans la plupart des cas, vous aussi vous voulez forcer le code de l'alignement-des changements dans l'alignement peut conduire à assez de différences substantielles dans l'exécution de spee.
Enfin, vous voulez exécuter un certain nombre de fois-et c'est toujours possible, ça va être interrompue au milieu de choses (par exemple, un commutateur de tâche), de sorte que vous devez être préparé à la possibilité d'une exécution en prenant un peu plus longue que le reste -- par exemple, 5 des pistes qui prennent ~40-43 cycles d'horloge chacun, et un sixième qui prend 10000+ cycles d'horloge. Clairement, dans ce dernier cas, vous venez de jeter les valeurs aberrantes -- ce n'est pas à partir de votre code.
Résumé: la gestion d'exécuter l'instruction rdtsc lui-même (ou presque) est le cadet de vos soucis. Il y a un peu plus de vous besoin à faire avant de pouvoir obtenir des résultats de
rdtsc
qui signifient réellement quelque chose.QueryPerformanceCounter
(qui est un mince voile surrdtsc
) souffre du même problème que vous avez identifié sur des/les systèmes multiprocesseurs. Mais je pense que j'ai aussi trouvé de la documentation que ce problème est un vrai problème sur les premiers systèmes parce que la plupart des Bios n'ai même pas tenter de synchroniser les compteurs sur les différents cœurs, mais la plupart des Bios récents (peut-être pas de comptage de pas cher indésirable machine Bios) ne font que de l'effort, de sorte qu'ils peuvent être désactivé en seulement quelques compte maintenant.Pour Windows, Visual Studio fournit un moyen pratique de "compilateur intrinsèque" (c'est à dire une fonction spéciale, qui le compilateur comprend) qui exécute l'instruction RDTSC pour vous et vous donne le résultat: