web-dev-qa-db-fra.com

Pourquoi Sleep (500) coûte-t-il plus de 500 ms?

J'ai utilisé Sleep(500) dans mon code et j'ai utilisé getTickCount() pour tester le timing. J'ai trouvé que cela avait un coût d'environ 515 ms, plus de 500. Quelqu'un sait-il pourquoi?

53
kookoo121

Parce que Sleep de l'API Win32 n'est pas une veille de haute précision et a une granularité maximale.

La meilleure façon d'obtenir un sommeil précis est de dormir un peu moins (~ 50 ms) et de faire une attente occupée. Pour trouver le temps exact dont vous avez besoin pour attendre, obtenez la résolution de l'horloge système en utilisant timeGetDevCaps et multipliez par 1,5 ou 2 pour être sûr.

63
orlp

sleep(500) garantit un sommeil de au moins 500ms.

Mais il pourrait dormir plus longtemps que cela: la limite supérieure n'est pas définie.

Dans votre cas, il y aura également un surcoût supplémentaire lors de l'appel de getTickCount().

Votre fonction non standard Sleep peut bien se comporter dans un autre domaine; mais je doute que l'exactitude soit garantie. Pour ce faire, vous avez besoin d'un matériel spécial.

30
Bathsheba

Comme vous pouvez le lire dans la documentation , la fonction WinAPI GetTickCount()

est limitée à la résolution du minuteur du système, qui est généralement comprise entre 10 millisecondes et 16 millisecondes.

Pour obtenir une mesure du temps plus précise, utilisez la fonction GetSystemDatePreciseAsFileTime

De plus, vous ne pouvez pas compter sur Sleep(500) pour dormir exactement 500 millisecondes. Il suspendra le thread pendant au moins 500 millisecondes. Le système d'exploitation continuera alors le thread dès qu'il aura un intervalle de temps disponible. Lorsqu'il existe de nombreuses autres tâches en cours d'exécution sur le système d'exploitation, il peut y avoir un retard.

18
Philipp

En général, la mise en veille signifie que votre thread passe dans un état d'attente et après 500 ms, il sera dans un état "exécutable". Ensuite, le planificateur du système d'exploitation choisit d'exécuter quelque chose en fonction de la priorité et du nombre de processus exécutables à ce moment-là. Donc, si vous avez un sommeil de haute précision et une horloge de haute précision, c'est encore un sommeil pendant au moins 500 ms, pas exactement 500 ms.

13
Notinlist

Comme les autres réponses l'ont noté, Sleep() a une précision limitée. En fait, no l'implémentation d'une fonction de type Sleep() peut être parfaitement précise, pour plusieurs raisons:

  • Il faut un certain temps pour appeler réellement Sleep(). Alors qu'une implémentation visant une précision maximale pourrait tenter de mesurer et de compenser cette surcharge, peu de soucis. (Et, dans tous les cas, la surcharge peut varier pour de nombreuses raisons, notamment l'utilisation du processeur et de la mémoire.)

  • Même si la minuterie sous-jacente utilisée par Sleep() se déclenche à exactement l'heure souhaitée, il n'y a aucune garantie que votre processus sera réellement replanifié immédiatement après le réveil. Votre processus a peut-être été échangé pendant son sommeil ou d'autres processus peuvent monopoliser le processeur.

  • Il est possible que le système d'exploitation ne peut pas réveiller votre processus à l'heure demandée, par ex. car l'ordinateur est en mode suspension. Dans un tel cas, il est fort possible que votre appel de 500 ms Sleep() finisse par prendre plusieurs heures ou jours.

De plus, même si Sleep() était parfaitement précis, le code que vous voulez exécuter après dormir consommera inévitablement du temps supplémentaire. Ainsi, pour effectuer une action (par exemple redessiner l'écran ou mettre à jour la logique du jeu) à intervalles réguliers, la solution standard consiste à utiliser une boucle compenséeSleep(). C'est-à-dire que vous maintenez un compteur de temps incrémentant régulièrement indiquant quand la prochaine action devrait se produit, et comparez ce temps cible avec le temps système actuel pour ajuster dynamiquement votre temps de sommeil.

Des précautions supplémentaires doivent être prises pour faire face aux sauts de temps importants et inattendus, par ex. si l'ordinateur a été temporairement suspecté ou si le compteur de ticks s'est enroulé, ainsi que la situation où le traitement de l'action finit par prendre plus de temps que disponible avant l'action suivante, entraînant un retard de la boucle.

Voici un exemple d'implémentation rapide (en pseudocode) qui devrait gérer ces deux problèmes:

int interval = 500, giveUpThreshold = 10*interval;
int nextTarget = GetTickCount();

bool active = doAction();
while (active) {
    nextTarget += interval;
    int delta = nextTarget - GetTickCount();
    if (delta > giveUpThreshold || delta < -giveUpThreshold) {
        // either we're hopelessly behind schedule, or something
        // weird happened; either way, give up and reset the target
        nextTarget = GetTickCount();
    } else if (delta > 0) {
        Sleep(delta);
    }
    active = doAction();
}

Cela garantira que doAction() sera appelée en moyenne une fois toutes les interval millisecondes, au moins tant qu'elle ne consommera pas systématiquement plus de temps que cela, et tant qu'aucun grand saut temporel ne se produit. Le temps exact entre les appels successifs peut varier, mais toute variation de ce type sera compensée à la prochaine interaction.

12
Ilmari Karonen

La résolution de la minuterie par défaut est faible, vous pouvez augmenter la résolution temporelle si nécessaire. MSDN

#define TARGET_RESOLUTION 1         // 1-millisecond target resolution

TIMECAPS tc;
UINT     wTimerRes;

if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR) 
{
    // Error; application can't continue.
}

wTimerRes = min(max(tc.wPeriodMin, TARGET_RESOLUTION), tc.wPeriodMax);
timeBeginPeriod(wTimerRes); 
2
ISanych

Il y a deux raisons générales pour lesquelles le code peut vouloir une fonction comme "sleep":

  1. Il a une tâche qui peut être effectuée à tout moment et qui est au moins à une certaine distance dans le futur.

  2. Il a une tâche qui devrait être effectuée le plus près possible d'un certain moment dans le temps, d'une certaine distance dans le futur.

Dans un bon système, il devrait y avoir des façons distinctes d'émettre ce type de demandes; Windows rend le premier plus facile que le second.

Supposons qu'il y ait un CPU et trois threads dans le système, tous faisant un travail utile jusqu'à ce que, une seconde avant minuit, l'un des threads dise qu'il n'aura rien d'utile à faire pendant au moins une seconde. À ce stade, le système consacrera l'exécution aux deux threads restants. Si, 1 ms avant minuit, l'un de ces threads décide qu'il n'aura rien d'utile à faire pendant au moins une seconde, le système basculera le contrôle sur le dernier thread restant.

À minuit, le premier thread d'origine sera disponible pour s'exécuter, mais comme le thread en cours d'exécution n'aura eu le CPU que pendant une milliseconde à ce stade, il n'y a aucune raison particulière pour que le premier thread d'origine soit considéré comme plus "digne". de temps CPU que l'autre thread qui vient de prendre le contrôle. Étant donné que la commutation des threads n'est pas gratuite, le système d'exploitation peut très bien décider que le thread qui a actuellement le CPU doit le conserver jusqu'à ce qu'il bloque quelque chose ou ait utilisé une tranche de temps entière.

Ce serait bien s'il y avait une version de "sleep" qui était plus facile à utiliser que les temporisateurs multimédias mais demanderait au système de donner au thread un boost de priorité temporaire quand il deviendra éligible pour s'exécuter à nouveau, ou mieux encore une variation de "sommeil" qui spécifierait un temps minimum et un temps "priorité-boost", pour les tâches qui doivent être effectuées dans un certain laps de temps. Cependant, je ne connais aucun système qui puisse facilement fonctionner de cette façon.

1
supercat