web-dev-qa-db-fra.com

Comment obtenir l'horodatage de la précision des ticks en .NET / C #?

Jusqu'à présent, j'utilisais DateTime.Now pour obtenir des horodatages, mais j'ai remarqué que si vous imprimez DateTime.Now dans une boucle, vous verrez qu'elle augmente par sauts discrets d'env. 15 ms. Mais pour certains scénarios dans mon application, j'ai besoin d'obtenir l'horodatage le plus précis possible, de préférence avec une précision de tick (= 100 ns). Des idées?

Mise à jour:

Apparemment, StopWatch/QueryPerformanceCounter est le chemin à parcourir, mais il ne peut être utilisé que pour mesurer le temps, donc je pensais à appeler DateTime.Now au démarrage de l'application, puis exécutez simplement StopWatch et ajoutez simplement le temps écoulé de StopWatch à la valeur initiale renvoyée par DateTime.Now. Au moins, cela devrait me donner des horodatages relatifs précis, non? Que pensez-vous de cela (hack)?

REMARQUE:

StopWatch.ElapsedTicks est différent de StopWatch.Elapsed.Ticks! J'ai utilisé le premier en supposant 1 tick = 100 ns, mais dans ce cas 1 tick = 1/StopWatch.Frequency. Donc, pour obtenir des ticks équivalents à DateTime, utilisez StopWatch.Elapsed.Ticks. Je viens de l'apprendre à la dure.

NOTE 2:

En utilisant l'approche StopWatch, j'ai remarqué qu'elle se désynchronise avec le temps réel. Après environ 10 heures, il était en avance de 5 secondes. Donc je suppose qu'il faudrait le resynchroniser tous les X environ, où X pourrait être 1 heure, 30 minutes, 15 minutes, etc. jusqu'à 20 ms.

61
user65199

La valeur de l'horloge système qui DateTime.Now reads n'est mis à jour que toutes les 15 ms environ (ou 10 ms sur certains systèmes), c'est pourquoi les temps sont quantifiés autour de ces intervalles. Il y a un effet de quantification supplémentaire qui résulte du fait que votre code s'exécute dans un OS multithread, et donc il y a des tronçons où votre application n'est pas "vivante" et ne mesure donc pas le temps réel actuel.

Étant donné que vous recherchez une valeur d'horodatage ultra-précise (par opposition à un simple chronométrage d'une durée arbitraire), la classe Stopwatch en elle-même ne fera pas ce dont vous avez besoin. Je pense que vous devriez le faire vous-même avec une sorte d'hybride DateTime/Stopwatch. Lorsque votre application démarre, vous devez stocker le DateTime.UtcNow value (c'est-à-dire le temps de résolution brute au démarrage de votre application), puis démarrez également un objet Stopwatch, comme ceci:

DateTime _starttime = DateTime.UtcNow;
Stopwatch _stopwatch = Stopwatch.StartNew();

Ensuite, chaque fois que vous avez besoin d'une valeur DateTime haute résolution, vous l'obtiendrez comme ceci:

DateTime highresDT = _starttime.AddTicks(_stopwatch.Elapsed.Ticks);

Vous pouvez également vouloir réinitialiser périodiquement _starttime et _stopwatch, pour éviter que l'heure résultante ne soit trop éloignée de la synchronisation avec l'heure système (bien que je ne sois pas sûr que cela se produise réellement et que cela prendrait de toute façon beaucoup de temps) .

pdate: car il semble que le chronomètre ne se synchronise pas avec l'heure système (jusqu'à une demi-seconde par heure), je pense qu'il est logique de réinitialiser la classe hybride DateTime en fonction du temps qui s'écoule entre les appels pour vérifier l'heure:

public class HiResDateTime
{
    private static DateTime _startTime;
    private static Stopwatch _stopWatch = null;
    private static TimeSpan _maxIdle = 
        TimeSpan.FromSeconds(10);

    public static DateTime UtcNow
    {
        get
        {
            if ((_stopWatch == null) || 
                (_startTime.Add(_maxIdle) < DateTime.UtcNow))
            {
                Reset();
            }
            return _startTime.AddTicks(_stopWatch.Elapsed.Ticks);
        }
    }

    private static void Reset()
    {
        _startTime = DateTime.UtcNow;
        _stopWatch = Stopwatch.StartNew();
    }
}

Si vous réinitialisez la minuterie hybride à un intervalle régulier (disons toutes les heures ou quelque chose), vous courez le risque de remettre l'heure avant la dernière heure de lecture, un peu comme un problème d'heure d'été miniature.

56
MusiGenesis

Pour obtenir un décompte à haute résolution, veuillez utiliser la méthode statique Stopwatch.GetTimestamp () -:

long tickCount = System.Diagnostics.Stopwatch.GetTimestamp();
DateTime highResDateTime = new DateTime(tickCount);

jetez un œil au code source .NET:

    public static long GetTimestamp() {
        if(IsHighResolution) {
            long timestamp = 0;    
            SafeNativeMethods.QueryPerformanceCounter(out timestamp);
            return timestamp;
        }
        else {
            return DateTime.UtcNow.Ticks;
        }   
    }

Code source ici: http://referencesource.Microsoft.com/#System/services/monitoring/system/diagnosticts/Stopwatch.cs,69c6c3137e12dab4

21
Marcus.D

[La réponse acceptée ne semble pas être thread-safe, et de son propre aveu peut reculer dans le temps provoquant des horodatages en double, d'où cette réponse alternative]

Si ce qui vous intéresse vraiment (selon votre commentaire) est en fait un horodatage unique qui est alloué dans un ordre croissant strict et qui correspond le plus possible à l'heure du système, vous pouvez essayer cette approche alternative:

public class HiResDateTime
{
   private static long lastTimeStamp = DateTime.UtcNow.Ticks;
   public static long UtcNowTicks
   {
       get
       {
           long orig, newval;
           do
           {
               orig = lastTimeStamp;
               long now = DateTime.UtcNow.Ticks;
               newval = Math.Max(now, orig + 1);
           } while (Interlocked.CompareExchange
                        (ref lastTimeStamp, newval, orig) != orig);

           return newval;
       }
   }
}
19
Ian Mercer

Ces suggestions semblent toutes trop dures! Si vous utilisez Windows 8 ou Server 2012 ou une version ultérieure, utilisez GetSystemTimePreciseAsFileTime comme suit:

[DllImport("Kernel32.dll", CallingConvention = CallingConvention.Winapi)]
static extern void GetSystemTimePreciseAsFileTime(out long filetime);

public DateTimeOffset GetNow()
{
    long fileTime;
    GetSystemTimePreciseAsFileTime(out fileTime);
    return DateTimeOffset.FromFileTime(fileTime);
}

Cela a une précision bien meilleure que DateTime.Now sans aucun effort.

Voir MSDN pour plus d'informations: http://msdn.Microsoft.com/en-us/library/windows/desktop/hh706895 (v = vs.85) .aspx

11
Jamezor

Il renvoie la date et l'heure les plus précises connues du système d'exploitation.

Le système d'exploitation fournit également une synchronisation de résolution plus élevée via QueryPerformanceCounter et QueryPerformanceFrequency (classe .NET Stopwatch). Ceux-ci vous permettent de chronométrer un intervalle mais ne vous donnent pas la date et l'heure de la journée. Vous pourriez faire valoir que ceux-ci pourraient vous donner une heure et un jour très précis, mais je ne sais pas à quel point ils biaisent sur un long intervalle.

8
Jason Kresowaty

1). Si vous avez besoin d'une précision absolue à haute résolution: vous ne pouvez pas utiliser DateTime.Now lorsqu'elle est basée sur une horloge avec un intervalle de 15 ms (sauf s'il est possible de "faire glisser" la phase).

Au lieu de cela, une source externe de temps de précision absolue de meilleure résolution (par exemple ntp), t1 ci-dessous, peut être combiné avec le minuteur haute résolution (StopWatch/QueryPerformanceCounter).


2). Si vous avez juste besoin d'une haute résolution:

Échantillon DateTime.Now (t1) une fois avec une valeur du minuteur haute résolution (StopWatch/QueryPerformanceCounter) (tt0).

Si la valeur actuelle du temporisateur haute résolution est tt alors l'heure actuelle, t, est:

t = t1 + (tt - tt0)

3). Une alternative pourrait être de démêler le temps absolu et l'ordre des événements financiers: une valeur pour le temps absolu (résolution de 15 ms, éventuellement décalée de plusieurs minutes) et une valeur pour l'ordre (par exemple, incrémenter une valeur à chaque fois et stocker cette). La valeur de début de la commande peut être basée sur une valeur globale du système ou être dérivée de l'heure absolue au démarrage de l'application.

Cette solution serait plus robuste car elle ne dépend pas de l'implémentation matérielle sous-jacente des horloges/minuteries (qui peut varier entre les systèmes).

4
Peter Mortensen

C'est trop de travail pour quelque chose d'aussi simple. Insérez simplement un DateTime dans votre base de données avec le commerce. Ensuite, pour obtenir un ordre commercial, utilisez votre colonne d'identité qui doit être une valeur incrémentielle.

Si vous insérez dans plusieurs bases de données et essayez de vous réconcilier après coup, vous ferez un mauvais calcul de l'ordre commercial en raison d'une légère variation dans les temps de votre base de données (même incréments de ns comme vous l'avez dit)

Pour résoudre le problème de plusieurs bases de données, vous pouvez exposer un seul service auquel chaque transaction accède pour obtenir une commande. Le service peut renvoyer un DateTime.UtcNow.Ticks et un numéro commercial incrémentiel.

Même en utilisant l'une des solutions ci-dessus, toute personne effectuant des transactions à partir d'un emplacement sur le réseau avec plus de latence peut éventuellement placer les transactions en premier (monde réel), mais elles sont entrées dans le mauvais ordre en raison de la latence. Pour cette raison, le commerce doit être considéré comme placé dans la base de données, pas sur les consoles des utilisateurs.

2
Nathaniel

La précision de 15 ms (en fait, elle peut être de 15 à 25 ms) est basée sur la résolution de la minuterie Windows 55 Hz/65 Hz. Il s'agit également de la période de base TimeSlice. Sont affectés Windows.Forms.Timer , Threading.Thread.Sleep , Threading.Timer , etc.

Pour obtenir des intervalles précis, vous devez utiliser la classe Chronomètre . Il utilisera la haute résolution si disponible. Essayez les instructions suivantes pour le savoir:

Console.WriteLine("H = {0}", System.Diagnostics.Stopwatch.IsHighResolution);
Console.WriteLine("F = {0}", System.Diagnostics.Stopwatch.Frequency);
Console.WriteLine("R = {0}", 1.0 /System.Diagnostics.Stopwatch.Frequency);

J'obtiens R = 6E-08 sec, ou 60 ns. Cela devrait suffire à votre objectif.

2
Henk Holterman

J'ajouterais ce qui suit concernant MusiGenesis Réponse pour le calendrier de resynchronisation.

Signification: quelle heure dois-je utiliser pour resynchroniser (le _maxIdle dans MusiGenesis réponse)

Vous savez qu'avec cette solution, vous n'êtes pas parfaitement précis, c'est pourquoi vous vous resynchronisez. Mais ce que vous voulez implicitement, c'est la même chose que Ian Mercer solution:

un horodatage unique alloué dans un ordre croissant strict

Par conséquent, la durée entre deux resynchronisations (_maxIdle Permet de l'appeler SyncTime ) devrait être fonction de 4 choses:

  • les DateTime.UtcNow résolution
  • le rapport de précision que vous souhaitez
  • le niveau de précision que vous souhaitez
  • Une estimation du taux de désynchronisation de votre machine

Evidemment la première contrainte sur cette variable serait:

rapport désynchronisé <= rapport de précision

Par exemple: je ne veux pas que ma précision soit pire que 0,5 s/h ou 1 ms/jour etc ... (en mauvais anglais: je ne veux pas me tromper plus que 0,5 s/h = 12 s/jour).

Vous ne pouvez donc pas obtenir une meilleure précision que ce que le chronomètre vous offre sur votre PC. Cela dépend de votre taux de désynchronisation, qui peut ne pas être constant.

Une autre contrainte est le temps minimum entre deux resynchronisations:

Synctime> = DateTime.UtcNow résolution

Ici, la précision et la précision sont liées car si vous utilisez une haute précision (par exemple pour stocker dans une base de données) mais une précision inférieure, vous risquez de casser l'instruction Ian Mercer , c'est l'ordre croissant strict.

Remarque: Il semble que DateTime.UtcNow peut avoir une résolution par défaut plus grande que 15 ms (1 ms sur ma machine) Suivez le lien: Haute précision DateTime.UtcNow

Prenons un exemple:

Imaginez le taux de désynchronisation commenté ci-dessus.

Après environ 10 heures, il était en avance de 5 secondes.

Disons que je veux une précision microsec. Ma minuterie res est de 1 ms (voir la remarque ci-dessus)

Donc point par point:

  • les DateTime.UtcNow résolution: 1 ms
  • rapport de précision> = rapport désynchronisé, permet de prendre le plus précis possible: rapport de précision = rapport désynchronisé
  • le niveau de précision souhaité: 1 microsec
  • Une estimation du taux de désynchronisation de votre machine: 0,5 s/heure (c'est aussi ma précision)

Si vous réinitialisez toutes les 10 secondes, imaginez-vous à 9,999 secondes, 1 ms avant la réinitialisation. Ici, vous passez un appel pendant cet intervalle. Le temps que votre fonction tracera est en avance de: 0,5/3600 * 9,999 s éq 1,39 ms. Vous afficheriez un temps de 10.000390sec. Après avoir coché UtcNow, si vous effectuez un appel dans les 390 secondes micro, votre numéro sera inférieur au précédent. C'est pire si ce rapport désynchronisé est aléatoire en fonction de la charge du processeur ou d'autres choses.

Supposons maintenant que je mette SyncTime à sa valeur minimale> Je resynchronise toutes les 1 ms

Faire la même réflexion me mettrait en avance de 0,139 microsec <inférieur à la précision que je veux. Par conséquent, si j'appelle la fonction à 9,999 ms, donc 1 microsec avant la réinitialisation, je tracerai 9,999. Et juste après je tracerai 10.000. J'aurai une bonne commande.

Voici donc l'autre contrainte: rapport de précision x SyncTime <niveau de précision, disons pour être sûr car le nombre peut être arrondi à ce rapport de précision x SyncTime <niveau de précision/2 est bon.

Le problème est résolu.

Un récapitulatif rapide serait donc:

  • Récupérez la résolution de votre minuterie.
  • Calculez une estimation du taux de désynchronisation.
  • rapport de précision> = estimation du rapport non synchronisé, Meilleure précision = rapport non synchronisé
  • Choisissez votre niveau de précision en tenant compte des éléments suivants:
  • timer-resolution <= SyncTime <= PrecisionLevel/(2 * precision-ratio)
  • La meilleure précision que vous pouvez obtenir est la résolution de la minuterie * 2 * un rapport désynchronisé

Pour le rapport ci-dessus (0,5/h), le SyncTime correct serait de 3,6 ms, donc arrondi à 3 ms .

Avec le rapport ci-dessus et la résolution de la minuterie de 1 ms. Si vous voulez un niveau de précision en une seule fois (0,1 microsec), vous avez besoin d'un rapport de synchronisation non supérieur à: 180 ms/heure.

Dans sa dernière réponse à sa propre réponse MusiGenesis indique:

@Hermann: j'ai effectué un test similaire au cours des deux dernières heures (sans la correction de réinitialisation), et le chronomètre basé sur le chronomètre ne fonctionne que 400 ms plus tard après 2 heures, de sorte que le décalage lui-même semble être variable (mais encore assez sévère). Je suis assez surpris que l'inclinaison soit si mauvaise; Je suppose que c'est pourquoi le chronomètre est dans System.Diagnostics. - MusiGenesis

La précision du chronomètre est donc proche de 200 ms/heure, presque 180 ms/heure. Y a-t-il un lien pour expliquer pourquoi notre numéro et ce numéro sont si proches? Je ne sais pas. Mais cette précision nous suffit pour atteindre la précision de tick

Le meilleur niveau de précision: pour l'exemple ci-dessus, il est 0,27 microsec.

Cependant, que se passe-t-il si je l'appelle plusieurs fois entre 9,999 ms et la resynchronisation.

2 appels à la fonction pourraient se retrouver avec le même TimeStamp renvoyé, le temps serait de 9,999 pour les deux (car je ne vois pas plus de précision). Pour contourner cela, vous ne pouvez pas toucher le niveau de précision car il est lié à SyncTime par la relation ci-dessus. Vous devez donc implémenter les solutions Ian Mercer pour ces cas.

N'hésitez pas à commenter ma réponse.

1
user3091460

Si vous avez besoin de l'horodatage pour effectuer des tests de performance, utilisez StopWatch qui a une bien meilleure précision que DateTime.Now.

0
Piotr Czapla