La résolution de gettimeofday () est-elle garantie à la microseconde?

97

Je porte un jeu, qui a été initialement écrit pour l'API Win32, sur Linux (enfin, le portage du port OS X du port Win32 vers Linux).

J'ai implémenté QueryPerformanceCounteren donnant les uSecondes depuis le démarrage du processus:

BOOL QueryPerformanceCounter(LARGE_INTEGER* performanceCount)
{
    gettimeofday(&currentTimeVal, NULL);
    performanceCount->QuadPart = (currentTimeVal.tv_sec - startTimeVal.tv_sec);
    performanceCount->QuadPart *= (1000 * 1000);
    performanceCount->QuadPart += (currentTimeVal.tv_usec - startTimeVal.tv_usec);

    return true;
}

Ceci, couplé au fait de QueryPerformanceFrequency()donner une constante 1000000 comme fréquence, fonctionne bien sur ma machine , me donnant une variable 64 bits qui contient uSecondsdepuis le démarrage du programme.

Alors, est-ce portable? Je ne veux pas découvrir que cela fonctionne différemment si le noyau a été compilé d'une certaine manière ou quelque chose comme ça. Cependant, je suis d'accord avec le fait qu'il ne soit pas portable vers autre chose que Linux.

Bernard
la source

Réponses:

57

Peut être. Mais vous avez de plus gros problèmes. gettimeofday()peut entraîner des timings incorrects s'il y a des processus sur votre système qui modifient la minuterie (c'est-à-dire ntpd). Sur un Linux "normal", cependant, je crois que la résolution de gettimeofday()est de 10us. Il peut sauter en avant et en arrière et dans le temps, par conséquent, en fonction des processus en cours d'exécution sur votre système. Cela rend effectivement la réponse à votre question non.

Vous devriez rechercher clock_gettime(CLOCK_MONOTONIC)les intervalles de temps. Il souffre de plusieurs problèmes en moins en raison de choses comme les systèmes multicœurs et les paramètres d'horloge externe.

Regardez également la clock_getres()fonction.

Louis Brandy
la source
1
clock_gettime n'est présent que sur les derniers Linux. l'autre système n'a que gettimeofday ()
vitaly.v.ch
3
@ vitaly.v.ch c'est POSIX donc ce n'est pas seulement Linux et "newist"? même les distributions 'Enterprise' comme Red Hat Enterprise Linux sont basées sur 2.6.18 qui a clock_gettime donc non, pas très nouveau .. (la date de la page de manuel dans RHEL est 2004-mars-12 donc elle existe depuis un certain temps) sauf si vous parler de VRAIMENT FREAKING ANCIENS noyaux WTF voulez-vous dire?
Spudd86
clock_gettime a été inclus dans POSIX en 2001. pour autant que je sache actuellement clock_gettime () implémenté sous Linux 2.6 et qnx. mais linux 2.4 est actuellement utilisé dans de nombreux systèmes de production.
vitaly.v.ch
Il a été introduit en 2001, mais pas obligatoire avant POSIX 2008.
R .. GitHub STOP HELPING ICE
2
De la FAQ Linux pour lock_gettime (voir la réponse de David Schlosnagle) "CLOCK_MONOTONIC ... est la fréquence ajustée par NTP via adjtimex (). Dans le futur (j'essaie toujours d'obtenir le correctif) il y aura un CLOCK_MONOTONIC_RAW qui ne être modifié du tout, et aura une corrélation linéaire avec les compteurs matériels. " Je ne pense pas que l'horloge _RAW soit jamais entrée dans le noyau (à moins qu'elle ne soit renommée _HR, mais mes recherches suggèrent que les efforts sont également abandonnés).
Tony Delroy
41

Synchronisation haute résolution et faible surcharge pour les processeurs Intel

Si vous utilisez du matériel Intel, voici comment lire le compteur d'instructions en temps réel du processeur. Il vous indiquera le nombre de cycles CPU exécutés depuis le démarrage du processeur. C'est probablement le compteur le plus fin que vous puissiez obtenir pour mesurer les performances.

Notez qu'il s'agit du nombre de cycles CPU. Sous Linux, vous pouvez obtenir la vitesse du processeur à partir de / proc / cpuinfo et la diviser pour obtenir le nombre de secondes. Le convertir en un double est assez pratique.

Quand je lance ça sur ma boîte, je reçois

11867927879484732
11867927879692217
it took this long to call printf: 207485

Voici le guide du développeur Intel qui donne des tonnes de détails.

#include <stdio.h>
#include <stdint.h>

inline uint64_t rdtsc() {
    uint32_t lo, hi;
    __asm__ __volatile__ (
      "xorl %%eax, %%eax\n"
      "cpuid\n"
      "rdtsc\n"
      : "=a" (lo), "=d" (hi)
      :
      : "%ebx", "%ecx");
    return (uint64_t)hi << 32 | lo;
}

main()
{
    unsigned long long x;
    unsigned long long y;
    x = rdtsc();
    printf("%lld\n",x);
    y = rdtsc();
    printf("%lld\n",y);
    printf("it took this long to call printf: %lld\n",y-x);
}
Mark Harrison
la source
11
Notez que le TSC peut ne pas toujours être synchronisé entre les cœurs, peut s'arrêter ou changer sa fréquence lorsque le processeur entre dans des modes de faible puissance (et vous n'avez aucun moyen de le savoir), et en général, il n'est pas toujours fiable. Le noyau est capable de détecter quand il est fiable, de détecter d'autres alternatives comme HPET et ACPI PM timer, et de sélectionner automatiquement la meilleure. C'est une bonne idée de toujours utiliser le noyau pour le timing, sauf si vous êtes vraiment sûr que le TSC est stable et monotone.
CesarB
12
Le TSC sur les plates-formes Intel Core et supérieures est synchronisé sur plusieurs processeurs et incrémentés à une fréquence constante indépendamment des états de gestion de l'alimentation. Voir le manuel du développeur de logiciels Intel, vol. 3 Section 18.10. Cependant, la vitesse à laquelle le compteur s'incrémente n'est pas la même que la fréquence du CPU. Le TSC s'incrémente à «la fréquence maximale résolue de la plate-forme, qui est égale au produit de la fréquence de bus évolutive et du rapport de bus résolu maximal» Manuel du développeur de logiciels Intel, vol. 3 Section 18.18.5. Vous obtenez ces valeurs à partir des registres spécifiques au modèle (MSR) du processeur.
sstock
7
Vous pouvez obtenir la fréquence de bus évolutive et le rapport de bus résolu maximum en interrogeant les registres spécifiques au modèle (MSR) du CPU comme suit: Fréquence de bus évolutive == MSR_FSB_FREQ [2: 0] id 0xCD, Ratio de bus résolu maximum == MSR_PLATFORM_ID [12: 8] id 0x17. Consultez l'annexe B.1 d'Intel SDM Vol.3 pour interpréter les valeurs de registre. Vous pouvez utiliser les msr-tools sous Linux pour interroger les registres. kernel.org/pub/linux/utils/cpu/msr-tools
sstock
1
Votre code ne devrait-il pas être CPUIDréutilisé après la première RDTSCinstruction et avant d'exécuter le code en cours de benchmark? Sinon, qu'est-ce qui empêche le code de référence d'être exécuté avant / en parallèle avec le premier RDTSC, et par conséquent sous-représenté dans le RDTSCdelta?
Tony Delroy
18

@Bernard:

Je dois admettre que la plupart de votre exemple est passé directement au-dessus de ma tête. Il compile et semble fonctionner, cependant. Est-ce sûr pour les systèmes SMP ou SpeedStep?

C'est une bonne question ... Je pense que le code est correct. D'un point de vue pratique, nous l'utilisons dans mon entreprise tous les jours, et nous utilisons un assez large éventail de boîtiers, allant de 2 à 8 cœurs. Bien sûr, YMMV, etc., mais cela semble être une méthode de synchronisation fiable et à faible coût (car elle ne fait pas basculer le contexte dans l'espace système).

Généralement, son fonctionnement est:

  • déclarez que le bloc de code est assembleur (et volatil, donc l'optimiseur le laissera seul).
  • exécutez l'instruction CPUID. En plus d'obtenir des informations sur le processeur (avec lesquelles nous ne faisons rien), il synchronise le tampon d'exécution du processeur afin que les délais ne soient pas affectés par une exécution dans le désordre.
  • exécuter l'exécution de rdtsc (horodatage de lecture). Cela récupère le nombre de cycles machine exécutés depuis la réinitialisation du processeur. Il s'agit d'une valeur de 64 bits, donc avec les vitesses actuelles du processeur, elle sera bouclée tous les 194 ans environ. Fait intéressant, dans la référence originale du Pentium, ils notent qu'il s'enroule tous les 5800 ans environ.
  • les deux dernières lignes stockent les valeurs des registres dans les variables hi et lo, et les placent dans la valeur de retour 64 bits.

Notes spécifiques:

  • une exécution dans le désordre peut entraîner des résultats incorrects, nous exécutons donc l'instruction "cpuid" qui, en plus de vous donner des informations sur le processeur, synchronise également toute exécution d'instruction dans le désordre.

  • La plupart des systèmes d'exploitation synchronisent les compteurs sur les processeurs lorsqu'ils démarrent, la réponse est donc bonne en quelques nano-secondes.

  • Le commentaire d'hibernation est probablement vrai, mais dans la pratique, vous ne vous souciez probablement pas des délais à travers les limites d'hibernation.

  • concernant speedstep: les nouveaux processeurs Intel compensent les changements de vitesse et renvoie un nombre ajusté. J'ai fait une analyse rapide de certaines des boîtes de notre réseau et n'ai trouvé qu'une seule boîte qui ne l'avait pas: un Pentium 3 exécutant un ancien serveur de base de données. (ce sont des boîtes Linux, j'ai donc vérifié avec: grep constant_tsc / proc / cpuinfo)

  • Je ne suis pas sûr des processeurs AMD, nous sommes principalement une boutique Intel, même si je sais que certains de nos gourous des systèmes de bas niveau ont fait une évaluation AMD.

J'espère que cela satisfait votre curiosité, c'est un domaine de programmation intéressant et (à mon humble avis) sous-étudié. Vous savez quand Jeff et Joel parlaient de savoir si un programmeur devait ou non connaître C? Je leur criais: "Hé, oubliez ce truc de haut niveau en C ... l'assembleur est ce que vous devez apprendre si vous voulez savoir ce que fait l'ordinateur!"

Mark Harrison
la source
1
... Les gens du noyau ont essayé de faire en sorte que les gens arrêtent d'utiliser rdtsc pendant un certain temps ... et évitent généralement de l'utiliser dans le noyau parce que ce n'est que peu fiable.
Spudd86
1
Pour référence, la question que j'ai posée (dans une réponse séparée - avant les commentaires) était: "Je dois admettre que la plupart de votre exemple est passé directement au-dessus de ma tête. Il compile et semble fonctionner, cependant. Est-ce sûr pour Systèmes SMP ou SpeedStep? "
Bernard
9

Donc, il dit explicitement microsecondes, mais indique que la résolution de l'horloge système n'est pas spécifiée. Je suppose que la résolution dans ce contexte signifie comment le plus petit montant il sera jamais incrémenté?

La structure de données est définie comme ayant des microsecondes comme unité de mesure, mais cela ne signifie pas que l'horloge ou le système d'exploitation est réellement capable de mesurer cela finement.

Comme d'autres personnes l'ont suggéré, gettimeofday()c'est mauvais car le réglage de l'heure peut entraîner un décalage de l'horloge et perturber votre calcul. clock_gettime(CLOCK_MONOTONIC)est ce que vous voulez, et clock_getres()vous indiquera la précision de votre horloge.

Joe Shaw
la source
Alors, que se passe-t-il dans votre code lorsque gettimeofday () saute en avant ou en arrière avec l'heure d'été?
mpez0
3
clock_gettime n'est présent que sur les derniers Linux. l'autre système n'a que gettimeofday ()
vitaly.v.ch
8

La résolution réelle de gettimeofday () dépend de l'architecture matérielle. Les processeurs Intel ainsi que les machines SPARC offrent des minuteries haute résolution qui mesurent les microsecondes. D'autres architectures matérielles retombent sur la minuterie du système, qui est généralement réglée sur 100 Hz. Dans de tels cas, la résolution temporelle sera moins précise.

J'ai obtenu cette réponse de la mesure du temps et des minuteries à haute résolution, partie I

CodageSansCommentaires
la source
6

Cette réponse mentionne des problèmes de réglage de l'horloge. Vos problèmes de garantie des unités de graduation et les problèmes avec l'heure d'ajustement sont résolus en C ++ 11 avec la <chrono>bibliothèque.

L'horloge std::chrono::steady_clockest garantie de ne pas être ajustée, et en outre, elle avancera à une vitesse constante par rapport au temps réel, de sorte que les technologies comme SpeedStep ne doivent pas l'affecter.

Vous pouvez obtenir des unités de type sécurisé en les convertissant à l'une des std::chrono::durationspécialisations, telles que std::chrono::microseconds. Avec ce type, il n'y a aucune ambiguïté sur les unités utilisées par la valeur de graduation. Cependant, gardez à l'esprit que l'horloge n'a pas nécessairement cette résolution. Vous pouvez convertir une durée en attosecondes sans avoir une horloge aussi précise.

bames53
la source
4

D'après mon expérience et ce que j'ai lu sur Internet, la réponse est «non», ce n'est pas garanti. Cela dépend de la vitesse du processeur, du système d'exploitation, de la saveur de Linux, etc.

CodageSansCommentaires
la source
3

La lecture du RDTSC n'est pas fiable dans les systèmes SMP, puisque chaque CPU maintient son propre compteur et que chaque compteur n'est pas garanti d'être synchronisé par rapport à une autre CPU.

Je pourrais suggérer d'essayer clock_gettime(CLOCK_REALTIME). Le manuel posix indique que cela doit être implémenté sur tous les systèmes conformes. Il peut fournir un nombre de nanosecondes, mais vous voudrez probablement vérifier clock_getres(CLOCK_REALTIME)sur votre système pour voir quelle est la résolution réelle.

Doug
la source
clock_getres(CLOCK_REALTIME)ne donnera pas la vraie résolution. Il renvoie toujours "1 ns" (une nanoseconde) lorsque les hrtimers sont disponibles, vérifiez le include/linux/hrtimer.hfichier pour define HIGH_RES_NSEC 1(plus sur stackoverflow.com/a/23044075/196561 )
osgx