Environment.TickCount vs DateTime.Now

È mai OK usare Environment.TickCount per calcolare gli intervalli di tempo?

 int start = Environment.TickCount; // Do stuff int duration = Environment.TickCount - start; Console.WriteLine("That took " + duration " ms"); 

Dato che TickCount è firmato e sarà rollover dopo 25 giorni (ci vogliono 50 giorni per colpire tutti i 32 bit, ma devi scartare il bit firmato se vuoi avere un senso della matematica), sembra che sia troppo rischioso per essere utile .

Sto usando DateTime.Now invece. È questo il modo migliore per farlo?

 DateTime start = DateTime.Now; // Do stuff TimeSpan duration = DateTime.Now - start; Console.WriteLine("That took " + duration.TotalMilliseconds + " ms"); 

Usa la class del cronometro. C’è un esempio decente su msdn: http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx

  Stopwatch stopWatch = Stopwatch.StartNew(); Thread.Sleep(10000); stopWatch.Stop(); // Get the elapsed time as a TimeSpan value. TimeSpan ts = stopWatch.Elapsed; 

Environment.TickCount si basa sulla funzione WinAPI di GetTickCount () . È in millisecondi, ma la precisione effettiva è di circa 15,6 m. Quindi non puoi misurare intervalli di tempo più brevi (o otterrai 0)

Nota: il valore restituito è Int32, quindi questo contatore si sposta su ogni ~ 49,7 giorni. Non dovresti usarlo per misurare intervalli così lunghi.

DateTime.Ticks è basato sulla funzione WinAPI GetSystemTimeAsFileTime (). È in nanosecondi di 100 secondi (decimi di microsecondi). La precisione effettiva di DateTime.Ticks dipende dal sistema. Su XP, l’incremento dell’orologio di sistema è di circa 15,6 ms, come in Environment.TickCount. Su Windows 7 la precisione è di 1 ms (mentre Environmnt.TickCount è ancora di 15.6 ms), tuttavia se si utilizza uno schema di risparmio energetico (di solito su laptop) può scendere anche a 15,6 ms.

Il cronometro si basa sulla funzione WinAPI di QueryPerformanceCounter () , ma se il contatore delle prestazioni ad alta risoluzione non è supportato dal sistema, viene utilizzato DateTime.Ticks)

Prima di utilizzare StopWatch notate due problemi:

  • può essere inaffidabile su sistemi multiprocessore (vedi MS kb895980 , kb896256 )
  • può essere inaffidabile se la frequenza della CPU varia (leggi questo articolo)

Puoi valutare la precisione del tuo sistema con un semplice test:

 static void Main(string[] args) { int xcnt = 0; long xdelta, xstart; xstart = DateTime.UtcNow.Ticks; do { xdelta = DateTime.UtcNow.Ticks - xstart; xcnt++; } while (xdelta == 0); Console.WriteLine("DateTime:\t{0} ms, in {1} cycles", xdelta / (10000.0), xcnt); int ycnt = 0, ystart; long ydelta; ystart = Environment.TickCount; do { ydelta = Environment.TickCount - ystart; ycnt++; } while (ydelta == 0); Console.WriteLine("Environment:\t{0} ms, in {1} cycles ", ydelta, ycnt); Stopwatch sw = new Stopwatch(); int zcnt = 0; long zstart, zdelta; sw.Start(); zstart = sw.ElapsedTicks; // This minimizes the difference (opposed to just using 0) do { zdelta = sw.ElapsedTicks - zstart; zcnt++; } while (zdelta == 0); sw.Stop(); Console.WriteLine("StopWatch:\t{0} ms, in {1} cycles", (zdelta * 1000.0) / Stopwatch.Frequency, zcnt); Console.ReadKey(); } 

Perché sei preoccupato per il rollover? Finché la durata che stai misurando è inferiore a 24,9 giorni e calcoli la durata relativa , stai bene. Non importa per quanto tempo il sistema è stato in esecuzione, purché tu ti preoccupi solo della parte di quel tempo di esecuzione (al contrario di eseguire direttamente il confronto minore o maggiore rispetto ai punti iniziale e finale). Cioè questo:

  int before_rollover = Int32.MaxValue - 5; int after_rollover = Int32.MinValue + 7; int duration = after_rollover - before_rollover; Console.WriteLine("before_rollover: " + before_rollover.ToString()); Console.WriteLine("after_rollover: " + after_rollover.ToString()); Console.WriteLine("duration: " + duration.ToString()); 

stampa correttamente:

  before_rollover: 2147483642 after_rollover: -2147483641 duration: 13 

Non devi preoccuparti del bit del segno. C #, come C, lascia che sia la CPU a gestirlo.

Questa è una situazione comune in cui mi sono imbattuto prima con il conteggio del tempo nei sistemi embedded. Non vorrei mai confrontare beforerollover

Probabilmente vuoi System.Diagnostics.StopWatch .

Se stai cercando la funzionalità di Environment.TickCount ma senza il sovraccarico di creare nuovi oggetti di Stopwatch , puoi utilizzare il metodo statico Stopwatch.GetTimestamp() (insieme a Stopwatch.Frequency ) per calcolare intervalli di tempo molto lunghi. Poiché GetTimestamp() restituisce un valore long , non verrà GetTimestamp() per un tempo molto, molto lungo (oltre 100.000 anni, sulla macchina che sto usando per scrivere questo). È anche molto più preciso di Environment.TickCount che ha una risoluzione massima di 10-16 millisecondi.

Uso

 System.Diagnostics.Stopwatch 

Ha una proprietà chiamata

 EllapsedMilliseconds 

Environment.TickCount sembra essere molto più veloce delle altre soluzioni:

 Environment.TickCount 71 DateTime.UtcNow.Ticks 213 sw.ElapsedMilliseconds 1273 

Le misure sono state generate dal seguente codice:

 static void Main( string[] args ) { const int max = 10000000; // // for ( int j = 0; j < 3; j++ ) { var sw = new Stopwatch(); sw.Start(); for ( int i = 0; i < max; i++ ) { var a = Environment.TickCount; } sw.Stop(); Console.WriteLine( $"Environment.TickCount {sw.ElapsedMilliseconds}" ); // // sw = new Stopwatch(); sw.Start(); for ( int i = 0; i < max; i++ ) { var a = DateTime.UtcNow.Ticks; } sw.Stop(); Console.WriteLine( $"DateTime.UtcNow.Ticks {sw.ElapsedMilliseconds}" ); // // sw = new Stopwatch(); sw.Start(); for ( int i = 0; i < max; i++ ) { var a = sw.ElapsedMilliseconds; } sw.Stop(); Console.WriteLine( $"sw.ElapsedMilliseconds {sw.ElapsedMilliseconds}" ); } Console.WriteLine( "Done" ); Console.ReadKey(); } 

Ecco una sorta di un riepilogo aggiornato e aggiornato di quali potrebbero essere le risposte e i commenti più utili in questa discussione + ulteriori benchmark e varianti:

Prima cosa: come altri hanno sottolineato nei commenti, le cose sono cambiate negli ultimi anni e con Windows “moderno” (Win XP ++) e .NET, e l’hardware moderno non ci sono motivi per non usare Stopwatch (). Vedi MSDN per i dettagli. Citazioni:

“La precisione della QPC è influenzata dalle variazioni di frequenza del processore causate dalla tecnologia di gestione dell’alimentazione o Turbo Boost?
No. Se il processore ha un TSC invariante , il QPC non è influenzato da questo tipo di modifiche. Se il processore non ha un TSC invariante, QPC ripristinerà un timer hardware della piattaforma che non sarà influenzato dalle modifiche della frequenza del processore o dalla tecnologia Turbo Boost.

QPC funziona in modo affidabile su sistemi multiprocessore, sistemi multi-core e sistemi con hyper-threading?

Come determinare e convalidare il funzionamento di QPC sulla mia macchina?
Non è necessario eseguire tali controlli.

Quali processori hanno TSC non invarianti? [.. Leggi oltre ..] ”

Ma se non hai bisogno della precisione di Stopwatch () o almeno vuoi sapere esattamente le prestazioni di Cronometro (statico rispetto a istanze) e altre possibili varianti, continua a leggere:

Ho acquisito il benchmark di cui sopra da cskwg e ho esteso il codice per ulteriori varianti. Ho misurato con alcuni anni i7 4700 MQ e C # 7 con VS 2017 (per essere più precisi, compilato con .NET 4.5.2, nonostante i letterali binari, è C # 6 (usato di questo: stringhe letterali e ‘uso statico ‘). Soprattutto le prestazioni del cronometro () sembrano essere migliorate rispetto al benchmark citato.

Questo è un esempio di risultati di 10 milioni di ripetizioni in un ciclo, come sempre, i valori assoluti non sono importanti, ma anche i valori relativi potrebbero differire su altri hardware:

32 bit, modalità di rilascio senza ottimizzazione:

Misurato: GetTickCount64 () [ms]: 275
Misurato: Environment.TickCount [ms]: 45
Misurato: DateTime.UtcNow.Ticks [ms]: 167
Misurato: cronometro: .ElapsedTicks [ms]: 277
Misurato: cronometro: .ElapsedMilliseconds [ms]: 548
Misurato: static Stopwatch.GetTimestamp [ms]: 193
Misurato: Cronometro + conversione in DateTime [ms]: 551
Confrontalo con DateTime.Now.Ticks [ms]: 9010

32 bit, modalità Release, ottimizzata:

Misurato: GetTickCount64 () [ms]: 198
Misurato: Environment.TickCount [ms]: 39
Misurato: DateTime.UtcNow.Ticks [ms]: 66 (!)
Misurato: cronometro: .ElapsedTicks [ms]: 175
Misurato: cronometro: .ElapsedMilliseconds [ms]: 491
Misurato: Stopwatch.GetTimestamp statico [ms]: 175
Misurato: Cronometro + conversione in DateTime [ms]: 510
Confrontalo con DateTime.Now.Ticks [ms]: 8460

64 bit, modalità di rilascio senza ottimizzazione:

Misurato: GetTickCount64 () [ms]: 205
Misurato: Environment.TickCount [ms]: 39
Misurato: DateTime.UtcNow.Ticks [ms]: 127
Misurato: cronometro: .ElapsedTicks [ms]: 209
Misurato: cronometro: .ElapsedMilliseconds [ms]: 285
Misurato: Stopwatch.GetTimestamp statico [ms]: 187
Misurato: Cronometro + conversione in DateTime [ms]: 319
Confrontalo con DateTime.Now.Ticks [ms]: 3040

64 bit, modalità Release, ottimizzata:

Misurato: GetTickCount64 () [ms]: 148
Misurato: Environment.TickCount [ms]: 31 (ne vale la pena?)
Misurato: DateTime.UtcNow.Ticks [ms]: 76 (!)
Misurato: cronometro: .ElapsedTicks [ms]: 178
Misurato: cronometro: .ElapsedMilliseconds [ms]: 226
Misurato: Stopwatch.GetTimestamp statico [ms]: 175
Misurato: Cronometro + conversione in DateTime [ms]: 246
Confrontalo con DateTime.Now.Ticks [ms]: 3020

Potrebbe essere molto interessante, che la creazione di un valore DateTime per stampare il tempo del cronometro sembra non avere quasi costi . Interessante in un modo più accademico che pratico è che il cronometro statico è leggermente più veloce (come previsto). Alcuni punti di ottimizzazione sono piuttosto interessanti. Ad esempio, non posso spiegare perché Stopwatch.ElapsedMilliseconds solo con 32 bit sia così lento rispetto alle altre varianti, ad esempio quella statica. Questo e DateTime.Ora più del doppio della loro velocità con 64 bit.

Puoi vedere: Solo per milioni di esecuzioni, il tempo di Cronometro inizia a essere importante. Se questo è davvero il caso (ma attenzione alla micro-ottimizzazione troppo presto), potrebbe essere interessante il fatto che con GetTickCount64 (), ma soprattutto con DateTime.UtcNow , hai un timer a 64 bit (lungo) con meno precisione di Cronometro, ma più veloce , in modo da non dover fare i conti con l’ambiente “brutto” a 32 bit.TickCount.

Come previsto, DateTime.Now è di gran lunga il più lento di tutti.

Se lo esegui, il codice recupera anche la precisione attuale del cronometro e altro ancora.

Ecco il codice completo del benchmark:

 using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using static System.Environment; 

[…]

  [DllImport("kernel32.dll") ] public static extern UInt64 GetTickCount64(); // Retrieves a 64bit value containing ticks since system start static void Main(string[] args) { const int max = 10_000_000; const int n = 3; Stopwatch sw; // Following Process&Thread lines according to tips by Thomas Maierhofer: https://codeproject.com/KB/testing/stopwatch-measure-precise.aspx // But this somewhat contradicts to assertions by MS in: https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396#Does_QPC_reliably_work_on_multi-processor_systems__multi-core_system__and_________systems_with_hyper-threading Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1); // Use only the first core Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; Thread.CurrentThread.Priority = ThreadPriority.Highest; Thread.Sleep(2); // warmup Console.WriteLine($"Repeating measurement {n} times in loop of {max:N0}:{NewLine}"); for (int j = 0; j < n; j++) { sw = new Stopwatch(); sw.Start(); for (int i = 0; i < max; i++) { var tickCount = GetTickCount64(); } sw.Stop(); Console.WriteLine($"Measured: GetTickCount64() [ms]: {sw.ElapsedMilliseconds}"); // // sw = new Stopwatch(); sw.Start(); for (int i = 0; i < max; i++) { var tickCount = Environment.TickCount; // only int capacity, enough for a bit more than 24 days } sw.Stop(); Console.WriteLine($"Measured: Environment.TickCount [ms]: {sw.ElapsedMilliseconds}"); // // sw = new Stopwatch(); sw.Start(); for (int i = 0; i < max; i++) { var a = DateTime.UtcNow.Ticks; } sw.Stop(); Console.WriteLine($"Measured: DateTime.UtcNow.Ticks [ms]: {sw.ElapsedMilliseconds}"); // // sw = new Stopwatch(); sw.Start(); for (int i = 0; i < max; i++) { var a = sw.ElapsedMilliseconds; } sw.Stop(); Console.WriteLine($"Measured: Stopwatch: .ElapsedMilliseconds [ms]: {sw.ElapsedMilliseconds}"); // // sw = new Stopwatch(); sw.Start(); for (int i = 0; i < max; i++) { var a = Stopwatch.GetTimestamp(); } sw.Stop(); Console.WriteLine($"Measured: static Stopwatch.GetTimestamp [ms]: {sw.ElapsedMilliseconds}"); // // DateTime dt=DateTime.MinValue; // just init sw = new Stopwatch(); sw.Start(); for (int i = 0; i < max; i++) { var a = new DateTime(sw.Elapsed.Ticks); // using variable dt here seems to make nearly no difference } sw.Stop(); //Console.WriteLine($"Measured: Stopwatch+conversion to DateTime [s] with millisecs: {dt:s.fff}"); Console.WriteLine($"Measured: Stopwatch+conversion to DateTime [ms]: {sw.ElapsedMilliseconds}"); Console.WriteLine(); } // // sw = new Stopwatch(); var tickCounterStart = Environment.TickCount; sw.Start(); for (int i = 0; i < max/10; i++) { var a = DateTime.Now.Ticks; } sw.Stop(); var tickCounter = Environment.TickCount - tickCounterStart; Console.WriteLine($"Compare that with DateTime.Now.Ticks [ms]: {sw.ElapsedMilliseconds*10}"); Console.WriteLine($"{NewLine}General Stopwatch information:"); if (Stopwatch.IsHighResolution) Console.WriteLine("- Using high-resolution performance counter for Stopwatch class."); else Console.WriteLine("- Using high-resolution performance counter for Stopwatch class."); double freq = (double)Stopwatch.Frequency; double ticksPerMicroSec = freq / (1000d*1000d) ; // microsecond resolution: 1 million ticks per sec Console.WriteLine($"- Stopwatch accuracy- ticks per microsecond (1000 ms): {ticksPerMicroSec:N1}"); Console.WriteLine(" (Max. tick resolution normally is 100 nanoseconds, this is 10 ticks/microsecond.)"); DateTime maxTimeForTickCountInteger= new DateTime(Int32.MaxValue*10_000L); // tickCount means millisec -> there are 10.000 milliseconds in 100 nanoseconds, which is the tick resolution in .NET, eg used for TimeSpan Console.WriteLine($"- Approximated capacity (maxtime) of TickCount [dd:hh:mm:ss] {maxTimeForTickCountInteger:dd:HH:mm:ss}"); // this conversion from seems not really accurate, it will be between 24-25 days. Console.WriteLine($"{NewLine}Done."); while (Console.KeyAvailable) Console.ReadKey(false); Console.ReadKey(); } 

Dovresti invece usare la class Cronometro .

Io uso Environment.TickCount perché:

  1. La class di cronometro non è nel quadro compatto.
  2. Il cronometro utilizza lo stesso meccanismo di temporizzazione sottostante di TickCount, quindi i risultati non saranno più o meno accurati.
  3. Il problema del wrap-around con TickCount è improbabile che venga colpito (dovresti lasciare il tuo computer in esecuzione per 27 giorni e poi provare a misurare un tempo che capita proprio nel giro del momento di avvolgimento), e anche se lo hai fatto colpire il risultato sarebbe un enorme intervallo di tempo negativo (quindi sarebbe tipo di risaltare).

Detto questo, raccomanderei anche l’uso di Cronometro, se disponibile. Oppure potresti impiegare circa 1 minuto e scrivere una lezione simile a un cronometro che avvolge Environment.TickCount.

A proposito, non vedo nulla nella documentazione del cronometro che menzioni il problema del wrap-around con il meccanismo del timer sottostante, quindi non sarei sorpreso di scoprire che il cronometro soffre dello stesso problema. Ma ancora una volta, non vorrei perdere tempo a preoccuparmene.

Avrei intenzione di dire di avvolgerlo in una lezione di cronometro, ma Grzenio ha già detto la cosa giusta, quindi gli darò un ingrandimento. Tale incapsulamento determina la decisione su quale sia la via migliore e questo può cambiare nel tempo. Ricordo di essere rimasto scioccato dal fatto che sia costoso avere tempo per alcuni sistemi, quindi avere un posto in cui è ansible implementare la tecnica migliore può essere molto importante.

Per il tempismo one-shot, è ancora più semplice scrivere

 Stopwatch stopWatch = Stopwatch.StartNew(); ...dostuff... Debug.WriteLine(String.Format("It took {0} milliseconds", stopWatch.EllapsedMilliseconds))); 

Immagino che l’involucro cosmicamente improbabile di TickCount sia meno preoccupante per StopWatch, dato che il campo ElapsedTicks è lungo. Sulla mia macchina, StopWatch è ad alta risoluzione, a 2,4 e 29 zecche al secondo. Anche a quel ritmo, ci vorranno più di 121 anni per traboccare il campo delle zecche. Certo, non so cosa succede sotto le coperte, quindi prendilo con un pizzico di sale. Tuttavia, noto che la documentazione di StopWatch non menziona nemmeno il problema wraparound, mentre il doc per TickCount lo fa.