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.
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.
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
[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;
}
}
}
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
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.
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).
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.
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.
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:
DateTime.UtcNow
résolutionEvidemment 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:
DateTime.UtcNow
résolution: 1 ms 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:
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.
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.