Quelle est la bonne façon de mesurer avec précision une durée dans Go? La plupart des applications utilisent simplement le package horaire standard et l'approche suivante:
var startTime = time.Now()
doSomeHardWork()
var duration = time.Since(startTime) // or: time.Now() - startTime
Cependant, time.Now()
renvoie l'heure système actuelle, ce qui entraîne deux failles:
L'heure système peut être délibérément plus rapide ou plus lente qu'en temps réel. Cela se produit toujours lorsque le système d'exploitation synchronise l'horloge interne avec les serveurs de temps NTP (ce qui peut arriver plusieurs fois par heure!)
De MSDN :
[Le service de temps] ajuste la fréquence d'horloge locale pour lui permettre de converger vers l'heure correcte. Si la différence de temps entre l'horloge locale et [l'échantillon de temps précis] est trop grande pour être corrigée en ajustant la fréquence d'horloge locale, le service de temps règle l'horloge locale à l'heure correcte.
Si l'heure du système change (manuellement ou en raison de l'heure d'été), il peut être possible de détecter la durée non valide et de la supprimer. Mais si l'horloge système coche, par ex. 10% plus rapide à synchroniser avec l'heure mondiale, il est pratiquement impossible à détecter. C'est le comportement prévu et la façon dont l'horloge système est conçue.
Pour cette raison, la plupart des autres langues proposent une API dédiée pour mesurer les durées:
System.nanoTime()
, System.currentTimeMillis()
serait équivalent à time.Now()
et est incorrectSystem.Diagnostics.Stopwatch
QueryPerformanceCounter
et QueryPerformanceFrequency
std::chrono::steady_clock
, ou std::chrono::high_resolution_clock
lorsque sa constante de membre is_steady
Est true
performance.now()
, alors que l'utilisation de new Date()
est incorrecteQuelle est la bonne façon de mesurer précisément le temps d'exécution dans Go?
Horloges monotones
Les systèmes d'exploitation fournissent à la fois une "horloge murale", qui peut être modifiée pour la synchronisation de l'horloge, et une "horloge monotone", qui ne l'est pas. La règle générale est que l'horloge murale est pour indiquer l'heure et l'horloge monotone est pour mesurer le temps. Plutôt que de diviser l'API, dans ce package, le Time retourné par time.Now contient à la fois une lecture d'horloge murale et une lecture d'horloge monotone; les opérations d'horodatage ultérieures utilisent la lecture d'horloge murale, mais les opérations de mesure temporelle ultérieures, en particulier les comparaisons et les soustractions, utilisent la lecture d'horloge monotone.
Par exemple, ce code calcule toujours un temps écoulé positif d'environ 20 millisecondes, même si l'horloge murale est modifiée pendant le chronométrage de l'opération:
start := time.Now() ... operation that takes 20 milliseconds ... t := time.Now() elapsed := t.Sub(start)
D'autres idiomes, tels que time.Since (début), time.Until (délai) et time.Now (). Avant (délai), sont également robustes contre les réinitialisations d'horloge murale.
À partir de Go 1.9 (sorti le 24 août 2017), Go utilise une horloge monotone pour les durées.
Celui-ci est disponible dans Go 1.9 (août 2017) avec des horloges monotones, vous n'aurez rien à faire de spécial pour en bénéficier:
https://tip.golang.org/pkg/time/#hdr-Monotonic_Clocks
Les systèmes d'exploitation fournissent à la fois une "horloge murale", qui peut être modifiée pour la synchronisation de l'horloge, et une "horloge monotone", qui ne l'est pas. La règle générale est que l'horloge murale est pour indiquer l'heure et l'horloge monotone est pour mesurer le temps. Plutôt que de diviser l'API, dans ce package, le Time retourné par time.Now contient à la fois une lecture d'horloge murale et une lecture d'horloge monotone; les opérations d'horodatage ultérieures utilisent la lecture d'horloge murale, mais les opérations de mesure temporelle ultérieures, en particulier les comparaisons et les soustractions, utilisent la lecture d'horloge monotone.
Par exemple, ce code calcule toujours un temps écoulé positif d'environ 20 millisecondes, même si l'horloge murale est modifiée pendant le chronométrage de l'opération:
start := time.Now()
... operation that takes 20 milliseconds ...
t := time.Now()
elapsed := t.Sub(start)
D'autres idiomes, tels que time.Since (début), time.Until (délai) et time.Now (). Avant (délai), sont également robustes contre les réinitialisations d'horloge murale.
Cette modification du pack time a été déclenchée par cette issue , ce qui a incité cette proposition à changer de Russ Cox:
Comparaison et soustraction des temps observés par le temps. Maintenant peut retourner des résultats incorrects si l'horloge murale du système est réinitialisée entre les deux observations. Nous proposons d'étendre la représentation time.Time pour tenir une lecture d'horloge monotone supplémentaire à utiliser dans ces calculs. Entre autres avantages, cela devrait rendre impossible une mesure de base du temps écoulé en utilisant le temps.Maintenant et le temps.Depuis signaler une durée négative ou un autre résultat non fondé sur la réalité.
Pour Go 1.8 et versions antérieures, la fonction de synchronisation correcte n'est pas à l'intérieur du package de temps, mais plutôt dans le package d'exécution :
func nanotime() int64
Afin de mesurer correctement le temps d'exécution, la procédure suivante doit être utilisée:
var startTime = runtime.nanotime()
doSomeHardWork()
var duration = runtime.nanotime() - startTime
Malheureusement, la méthode elle-même n'est pas très bien documentée. Il est apparu dans ce numéro après un longue discussion si c'était vraiment nécessaire . Pour Go 1.9 et plus récent, reportez-vous à la réponse de Kenny Grant.