Précision C # DateTime.Now

97

Je viens de rencontrer un comportement inattendu avec DateTime.UtcNow lors de certains tests unitaires. Il semble que lorsque vous appelez DateTime.Now/UtcNow en succession rapide, il semble vous rendre la même valeur pendant un intervalle de temps plus long que prévu, plutôt que de capturer des incréments de millisecondes plus précis.

Je sais qu'il existe une classe Stopwatch qui serait mieux adaptée pour effectuer des mesures de temps précises, mais j'étais curieux de savoir si quelqu'un pouvait expliquer ce comportement dans DateTime? Existe-t-il une précision officielle documentée pour DateTime.Now (par exemple, précise à moins de 50 ms?)? Pourquoi DateTime.Now serait-il moins précis que ce que la plupart des horloges CPU pourraient gérer? Peut-être qu'il est simplement conçu pour le processeur le plus petit dénominateur commun?

public static void Main(string[] args)
{
    var stopwatch = new Stopwatch();
    stopwatch.Start();
    for (int i=0; i<1000; i++)
    {
        var now = DateTime.Now;
        Console.WriteLine(string.Format(
            "Ticks: {0}\tMilliseconds: {1}", now.Ticks, now.Millisecond));
    }

    stopwatch.Stop();
    Console.WriteLine("Stopwatch.ElapsedMilliseconds: {0}",
        stopwatch.ElapsedMilliseconds);

    Console.ReadLine();
}
Andy White
la source
Quel est l'intervalle pendant lequel vous récupérez la même valeur?
ChrisF
Lorsque j'ai exécuté le code ci-dessus, je n'ai obtenu que 3 valeurs uniques pour les ticks et les millisecondes, et le temps de chronométrage final était de 147 ms, il semble donc que sur ma machine, il n'est précis qu'à environ 50 ms ...
Andy White
Je devrais dire que la boucle s'est déroulée plusieurs fois, mais je n'ai vu que 3 valeurs distinctes ...
Andy White
Pour tous ceux qui viennent ici, voici le TL; DR // Utilisez la fonction QueryPerformanceCounter "Récupère la valeur actuelle du compteur de performance, qui est un horodatage haute résolution (<1us) qui peut être utilisé pour les mesures d'intervalle de temps." (Pour le code managé, la classe System.Diagnostics.Stopwatch utilise QPC comme base de temps précise.) Msdn.microsoft.com/en-us/library/windows/desktop/…
AnotherUser

Réponses:

180

Pourquoi DateTime.Now serait-il moins précis que ce que la plupart des horloges CPU pourraient gérer?

Une bonne horloge doit être à la fois précise et précise ; ceux-ci sont différents. Comme le dit la vieille blague, une horloge arrêtée est exactement exacte deux fois par jour, une horloge lente d'une minute n'est jamais précise à aucun moment. Mais une horloge lente d'une minute est toujours précise à la minute près, alors qu'une horloge arrêtée n'a aucune précision utile.

Pourquoi le DateTime devrait-il être précis à, disons une microseconde alors qu'il ne peut pas être précis à la microseconde? La plupart des gens ne disposent d'aucune source pour les signaux horaires officiels précis à la microseconde. Par conséquent, donner six chiffres après la décimale de précision , dont les cinq derniers sont des ordures, serait mentir .

N'oubliez pas que le but de DateTime est de représenter une date et une heure . Les timings de haute précision ne sont pas du tout le but de DateTime; comme vous le remarquez, c'est le but de StopWatch. Le but de DateTime est de représenter une date et une heure à des fins telles que l'affichage de l'heure actuelle à l'utilisateur, le calcul du nombre de jours jusqu'à mardi prochain, et ainsi de suite.

Bref, "quelle heure est-il?" et "combien de temps cela a-t-il pris?" sont des questions complètement différentes; n'utilisez pas un outil conçu pour répondre à une question pour répondre à l'autre.

Merci pour la question; cela fera un bon article de blog! :-)

Eric Lippert
la source
2
@Eric Lippert: Raymond Chen a un vieux mais bon sur le sujet même de la différence entre «précision» et «exactitude»: blogs.msdn.com/oldnewthing/archive/2005/09/02/459952.aspx
jason
3
Ok, bon point sur la précision par rapport à l'exactitude. Je suppose que je n'achète toujours pas vraiment l'affirmation selon laquelle DateTime n'est pas exacte parce que "ce n'est pas nécessaire". Si j'ai un système transactionnel et que je souhaite marquer une date / heure pour chaque enregistrement, il me semble intuitif d'utiliser la classe DateTime, mais il semble qu'il existe des composants d'heure plus précis / précis dans .NET, alors pourquoi DateTime serait rendu moins capable. Je suppose que je vais devoir faire un peu plus de lecture ...
Andy White
11
OK @Andy, supposons que vous ayez un tel système. Sur une machine, vous marquez une transaction comme ayant eu lieu le 1er janvier, 12: 34: 30.23498273. Sur une autre machine de votre cluster, vous marquez une transaction comme ayant eu lieu le 1er janvier, 12: 34: 30.23498456. Quelle transaction a eu lieu en premier? À moins que vous ne sachiez que les horloges des deux machines sont synchronisées à moins d'une microseconde l'une de l'autre, vous ne savez pas laquelle s'est produite en premier. La précision supplémentaire est une poubelle trompeuse . Si j'avais mon chemin, tous les DateTimes seraient arrondis à la seconde près, comme ils l'étaient dans VBScript.
Eric Lippert
14
non pas que cela résoudrait le problème que vous mentionnez, car les PC non synchronisés moyens sont généralement hors service en quelques minutes . Maintenant, si arrondir à 1 ne résout rien, pourquoi arrondir du tout? En d'autres termes, je ne suis pas votre argument pour expliquer pourquoi les valeurs absolues devraient avoir une précision inférieure à la précision des mesures de temps delta.
Roman Starkov
3
Disons que je crée un journal d'activité qui nécessite (1) de savoir quand quelque chose s'est produit en termes d'espace de calendrier (en quelques secondes) (2) de connaître très exactement l'espacement entre les événements (dans les 50 millisecondes environ). Il semble que le pari le plus sûr pour cela serait d'utiliser DateTime.Now pour l'horodatage de la première action, puis d'utiliser un chronomètre pour les actions suivantes afin de déterminer le décalage par rapport au DateTime initial. Est-ce l'approche que vous conseilleriez, Eric?
devuxer
18

La précision de DateTime est quelque peu spécifique au système sur lequel il est exécuté. La précision est liée à la vitesse d'un changement de contexte, qui tend à être d'environ 15 ou 16 ms. (Sur mon système, il s'agit en fait d'environ 14 ms de mes tests, mais j'ai vu certains ordinateurs portables où la précision est plus proche de 35-40 ms.)

Peter Bromberg a écrit un article sur la synchronisation de code de haute précision en C #, qui en traite.

Reed Copsey
la source
2
Les 4 machines Win7 que j'ai eues au fil des ans ont toutes eu une précision d'environ 1 ms. Now () sleep (1) Now () entraînait toujours un changement d'environ 1 ms dans le datetime lorsque je testais.
Bengie
Je vois également la précision d'environ 1 ms.
Timo
12

Je voudrais un Datetime.Now précis :), alors j'ai concocté ceci:

public class PreciseDatetime
{
    // using DateTime.Now resulted in many many log events with the same timestamp.
    // use static variables in case there are many instances of this class in use in the same program
    // (that way they will all be in sync)
    private static readonly Stopwatch myStopwatch = new Stopwatch();
    private static System.DateTime myStopwatchStartTime;

    static PreciseDatetime()
    {
        Reset();

        try
        {
            // In case the system clock gets updated
            SystemEvents.TimeChanged += SystemEvents_TimeChanged;
        }
        catch (Exception)
        {                
        }
    }

    static void SystemEvents_TimeChanged(object sender, EventArgs e)
    {
        Reset();
    }

    // SystemEvents.TimeChanged can be slow to fire (3 secs), so allow forcing of reset
    static public void Reset()
    {
        myStopwatchStartTime = System.DateTime.Now;
        myStopwatch.Restart();
    }

    public System.DateTime Now { get { return myStopwatchStartTime.Add(myStopwatch.Elapsed); } }
}
Jimmy
la source
2
J'aime cette solution, mais je n'étais pas sûr, j'ai donc posé ma propre question ( stackoverflow.com/q/18257987/270348 ). Selon le commentaire / la réponse de Servy, vous ne devriez jamais réinitialiser le chronomètre.
RobSiklos
1
Peut-être pas dans votre contexte, mais la réinitialisation a du sens dans mon contexte - je m'assure simplement que c'est fait avant que le timing ne commence réellement.
Jimmy
Vous n'avez pas besoin de l'abonnement et vous n'avez pas besoin de réinitialiser le chronomètre. L'exécution de ce code toutes les ~ 10 ms n'est pas nécessaire et consomme du processeur. Et ce code n'est pas du tout thread-safe. Initialisez simplement myStopwatchStartTime = DateTime.UtcNow; une fois, dans le constructeur statique.
VeganHunter
1
@VeganHunter Je ne sais pas si je comprends bien votre commentaire, mais vous semblez penser que TimeChanged est appelé toutes les ~ 10 ms? Ce n'est pas le cas.
Jimmy
@Jimmy, vous avez raison. Mon mauvais, j'ai mal compris le code. L'événement SystemEvents.TimeChanged est appelé uniquement si l'utilisateur modifie l'heure système. C'est un événement rare.
VeganHunter
6

À partir de MSDN, vous trouverez DateTime.Nowune résolution approximative de 10 millisecondes sur tous les systèmes d'exploitation NT.

La précision réelle dépend du matériel. Une meilleure précision peut être obtenue en utilisant QueryPerformanceCounter.

Kevin Montrose
la source
5

Pour ce que ça vaut, à moins de vérifier la source .NET, Eric Lippert a fourni un commentaire sur cette question SO en disant que DateTime n'est précise qu'à environ 30 ms. Le raisonnement pour ne pas être précis à la nanoseconde, selon ses mots, est que cela «n'a pas besoin de l'être».

Scott Anderson
la source
4
Et ça pourrait être pire. Dans VBScript, la fonction Now () arrondit le résultat renvoyé à la seconde près, despie le fait que la valeur retournée a une précision disponible suffisante pour être précise à la microseconde. En C #, la structure s'appelle DateTime; il est destiné à représenter une date et une heure pour les domaines non scientifiques typiques du monde réel, comme la date d'expiration de votre assurance-vie ou le temps écoulé depuis votre dernier redémarrage. Il n'est pas destiné à un chronométrage de haute précision inférieur à la seconde.
Eric Lippert
3

À partir de la documentation MSDN :

La résolution de cette propriété dépend de la minuterie système.

Ils affirment également que la résolution approximative sur Windows NT 3.5 et versions ultérieures est de 10 ms :)

Tomas Vana
la source
1

La résolution de cette propriété dépend de la minuterie système, qui dépend du système d'exploitation sous-jacent. Il a tendance à être compris entre 0,5 et 15 millisecondes.

Par conséquent, les appels répétés à la propriété Now dans un court intervalle de temps, comme dans une boucle, peuvent renvoyer la même valeur.

Lien MSDN

Iman
la source