Environment.TickCount versus DateTime.Now

¿Está bien utilizar Environment.TickCount para calcular los intervalos de tiempo?

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

Debido a que TickCount está firmado y se transferirá después de 25 días (se tardan 50 días en llegar a los 32 bits, pero debe eliminar el bit firmado si desea tener algún sentido matemático), parece que es demasiado arriesgado como para ser útil. .

Estoy usando DateTime.Now lugar. ¿Es ésta la mejor manera de hacer ésto?

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

Usa la clase Cronómetro. Hay un ejemplo decente en 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 se basa en la función WinAPI de GetTickCount () . Es en milisegundos Pero la precisión real de esto es de aproximadamente 15.6 ms. Entonces no puedes medir intervalos de tiempo más cortos (o obtendrás 0)

Nota: El valor devuelto es Int32, por lo que este contador rueda sobre cada ~ 49.7 días. No deberías usarlo para medir tales intervalos largos.

DateTime.Ticks se basa en la función WinAPI de GetSystemTimeAsFileTime (). Está en 100 nanosegundos (décimas de microsoconds). La precisión real de DateTime.Ticks depende del sistema. En XP, el incremento del reloj del sistema es de aproximadamente 15,6 ms, el mismo que en Environment.TickCount. En Windows 7 su precisión es de 1 ms (mientras que Environemnt.TickCount sigue siendo de 15.6 ms), sin embargo, si se usa un esquema de ahorro de energía (generalmente en computadoras portátiles) también puede bajar a 15.6 ms.

Cronómetro se basa en la función QueryPerformanceCounter () WinAPI (pero si su sistema no admite el contador de rendimiento de alta resolución, se usa DateTime.Ticks)

Antes de usar StopWatch, tenga en cuenta dos problemas:

  • puede no ser confiable en sistemas multiprocesador (ver MS kb895980 , kb896256 )
  • puede no ser confiable si la frecuencia de la CPU varía (lea este artículo)

Puede evaluar la precisión en su sistema con una prueba simple:

 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(); } 

¿Por qué te preocupa el vuelco? Siempre que la duración que está midiendo sea inferior a 24.9 días y calcule la duración relativa , estará bien. No importa cuánto tiempo haya estado funcionando el sistema, siempre y cuando solo se preocupe por la parte de ese tiempo de ejecución (en lugar de realizar directamente comparaciones menores o mayores que en los puntos de inicio y final). Es decir esto:

  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()); 

imprime correctamente:

  before_rollover: 2147483642 after_rollover: -2147483641 duration: 13 

No tiene que preocuparse por el bit de signo. C #, como C, deja que la CPU maneje esto.

Esta es una situación común con la que me he encontrado antes con los recuentos de tiempo en los sistemas integrados. Nunca compararía beforerollo

Probablemente desee System.Diagnostics.StopWatch .

Si está buscando la funcionalidad de Environment.TickCount pero sin la sobrecarga de crear nuevos objetos de Stopwatch , puede usar el método estático Stopwatch.GetTimestamp() (junto con Stopwatch.Frequency ) para calcular períodos de tiempo largos. Debido a que GetTimestamp() regresa long , no se desbordará durante mucho, mucho tiempo (más de 100.000 años, en la máquina que estoy utilizando para escribir esto). También es mucho más preciso que Environment.TickCount que tiene una resolución máxima de 10 a 16 milisegundos.

Utilizar

 System.Diagnostics.Stopwatch 

Tiene una propiedad llamada

 EllapsedMilliseconds 

Environment.TickCount parece ser mucho más rápido que las otras soluciones:

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

Las mediciones fueron generadas por el siguiente código:

 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(); } 

Aquí hay un resumen actualizado y actualizado de las respuestas y los comentarios más útiles en este hilo + puntos de referencia y variantes adicionales:

Lo primero es lo primero: como otros han señalado en los comentarios, las cosas han cambiado en los últimos años y con Windows “moderno” (Win XP ++) y .NET, y con hardware moderno, no hay razones o razones para no usar Stopwatch (). Ver MSDN para más detalles. Citas

“¿La precisión QPC se ve afectada por los cambios de frecuencia del procesador causados ​​por la administración de energía o la tecnología Turbo Boost?
No. Si el procesador tiene un TSC invariable , el QPC no se ve afectado por este tipo de cambios. Si el procesador no tiene un TSC invariable, QPC revertirá a un temporizador de hardware de plataforma que no se verá afectado por los cambios de frecuencia del procesador o la tecnología Turbo Boost.

¿QPC funciona de manera confiable en sistemas multiprocesador, sistema multi-core y sistemas con hiper-threading?

¿Cómo determino y valido que QPC funcione en mi máquina?
No necesita realizar dichos controles.

¿Qué procesadores tienen TSC no invariables? [..Leer más ..] ”

Pero si no necesita la precisión de Cronómetro () o al menos desea saber exactamente sobre el rendimiento de Cronómetro (estático frente a basado en instancia) y otras variantes posibles, continúe leyendo:

Asumí el punto de referencia anterior de cskwg y extendí el código para más variantes. He medido con algunos años i7 4700 MQ y C # 7 con VS 2017 (para ser más precisos, comstackdo con .NET 4.5.2, a pesar de los literales binarios, es C # 6 (usado de esto: literales de cadena y ‘usando estática ‘). Especialmente el rendimiento de Cronómetro () parece mejorar en comparación con el punto de referencia mencionado.

Este es un ejemplo de resultados de 10 millones de repeticiones en un bucle, como siempre, los valores absolutos no son importantes, pero incluso los valores relativos pueden diferir en otro hardware:

32 bit, modo de lanzamiento sin optimización:

Medido: GetTickCount64 () [ms]: 275
Medido: Environment.TickCount [ms]: 45
Medido: DateTime.UtcNow.Ticks [ms]: 167
Medido: Cronómetro: .ElapsedTicks [ms]: 277
Medido: cronómetro: .Minisegundos eliminados [ms]: 548
Medido: Cronómetro estático.GetTimestamp [ms]: 193
Medido: cronómetro + conversión a DateTime [ms]: 551
Compare eso con DateTime.Now.Ticks [ms]: 9010

32 bit, modo de lanzamiento, optimizado:

Medido: GetTickCount64 () [ms]: 198
Medido: Environment.TickCount [ms]: 39
Medido: DateTime.UtcNow.Ticks [ms]: 66 (!)
Medido: Cronómetro: .ElapsedTicks [ms]: 175
Medido: Cronómetro: .Minisegundos eliminados [ms]: 491
Medido: Cronómetro estático.GetTimestamp [ms]: 175
Medido: cronómetro + conversión a DateTime [ms]: 510
Compare eso con DateTime.Now.Ticks [ms]: 8460

64 bit, modo de lanzamiento sin optimización:

Medido: GetTickCount64 () [ms]: 205
Medido: Environment.TickCount [ms]: 39
Medido: DateTime.UtcNow.Ticks [ms]: 127
Medido: Cronómetro: .ElapsedTicks [ms]: 209
Medido: Cronómetro: .Minisegundos eliminados [ms]: 285
Medido: Cronómetro estático.GetTimestamp [ms]: 187
Medido: cronómetro + conversión a DateTime [ms]: 319
Compare eso con DateTime.Now.Ticks [ms]: 3040

64 bit, modo de lanzamiento, optimizado:

Medido: GetTickCount64 () [ms]: 148
Measured: Environment.TickCount [ms]: 31 (¿Vale la pena?)
Medido: DateTime.UtcNow.Ticks [ms]: 76 (!)
Medido: Cronómetro: .ElapsedTicks [ms]: 178
Medido: Cronómetro: .Minisegundos eliminados [ms]: 226
Medido: Cronómetro estático.GetTimestamp [ms]: 175
Medido: cronómetro + conversión a DateTime [ms]: 246
Compare eso con DateTime.Now.Ticks [ms]: 3020

Puede ser muy interesante, que la creación de un valor de DateTime para imprimir el tiempo de Cronómetro parece tener casi ningún costo . Interesante en una forma más académica que práctica es que el cronómetro estático es ligeramente más rápido (como se esperaba). Algunos puntos de optimización son bastante interesantes. Por ejemplo, no puedo explicar por qué Stopwatch.ElapsedMilliseconds solo con 32 bits es tan lento en comparación con sus otras variantes, por ejemplo, la estática. Esto y DateTime.Now más del doble de su velocidad con 64 bit.

Puedes ver: solo para millones de ejecuciones, el tiempo de Stopwatch comienza a importar. Si este es realmente el caso (pero cuidado con la micro-optimización demasiado pronto), puede ser interesante que con GetTickCount64 (), pero especialmente con DateTime.UtcNow , tengas un temporizador de 64 bits (largo) con menos precisión que el Cronómetro, pero más rápido , para que no tenga que meterse con el entorno “feo” de 32 bits. TickCount.

Como era de esperar, DateTime.Now es de lejos el más lento de todos.

Si lo ejecuta, el código recupera también su precisión actual del cronómetro y más.

Aquí está el código de referencia completo:

 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(); } 

Deberías usar la clase Cronómetro en su lugar.

Uso Environment.TickCount porque:

  1. La clase Cronómetro no está en el Marco Compacto.
  2. Cronómetro utiliza el mismo mecanismo de tiempo subyacente como TickCount, por lo que los resultados no serán más o menos precisos.
  3. El problema integral con TickCount es poco probable que sea golpeado (tendría que dejar su computadora encendida durante 27 días y luego tratar de medir un tiempo que simplemente abarca el momento de envoltura), e incluso si lo hizo golpearlo, el resultado sería un período de tiempo muy negativo (por lo que se destacaría).

Dicho esto, también recomendaría usar Stopwatch, si está disponible para ti. O puede tomar aproximadamente 1 minuto y escribir una clase parecida a un cronómetro que envuelva Environment.TickCount.

Por cierto, no veo nada en la documentación de Cronómetro que mencione el problema de envolver con el mecanismo del temporizador subyacente, por lo que no me sorprendería en absoluto que Cronómetro padezca el mismo problema. Pero, de nuevo, no me preocuparía por eso.

Iba a decir envolverlo en una clase de cronómetro, pero Grzenio ya dijo lo correcto, así que le daré un impulso. Tal encapsulamiento factoriza la decisión en cuanto a qué camino es mejor, y esto puede cambiar con el tiempo. Recuerdo que me sorprendió lo caro que puede ser obtener tiempo en algunos sistemas, por lo que tener un lugar que pueda implementar la mejor técnica puede ser muy importante.

Para el tiempo de una sola toma, es incluso más simple escribir

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

Supongo que el envoltorio cósmicamente improbable en TickCount es aún menos preocupante para StopWatch, dado que el campo ElapsedTicks es largo. En mi máquina, StopWatch es de alta resolución, con 2.4e9 tics por segundo. Incluso a ese ritmo, tomaría más de 121 años desbordar el campo de las garrapatas. Por supuesto, no sé lo que está pasando bajo las sábanas, así que tómalo con un grano de sal. Sin embargo, noto que la documentación para StopWatch ni siquiera menciona el problema envolvente, mientras que el documento para TickCount sí lo hace.