Pregunta

¿Alguna vez está bien usar Environment.TickCountcalcular lapsos de tiempo?

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

Porque TickCount está firmado y se renovará después de 25 días (se necesitan 50 días para alcanzar los 32 bits, pero debe descartar el bit firmado si quiere que las matemáticas tengan algún sentido), parece que es demasiado arriesgado para ser útil.

Estoy usando DateTime.Now en cambio.¿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");
¿Fue útil?

Solución

Usa la clase de cronómetro. Hay un ejemplo decente en msdn: http://msdn.microsoft .com / es-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;

Otros consejos

Environment.TickCount se basa en GetTickCount () WinAPI función. Es en milisegundos Pero la precisión real es de unos 15,6 ms. Por lo tanto, no puede medir intervalos de tiempo más cortos (o obtendrá 0)

Nota: El valor devuelto es Int32, por lo que este contador se reinicia cada ~ 49.7 días. No debe usarlo para medir intervalos tan largos.

DateTime.Ticks se basa en GetSystemTimeAsFileTime () WinAPI función. Está en cientos de nanosegundos (décimas de microsocondos). La precisión real de DateTime.Ticks depende del sistema. En XP, el incremento del reloj del sistema es de aproximadamente 15,6 ms, igual 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 utiliza 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 QueryPerformanceCounter () función WinAPI ( pero si su sistema no admite el contador de rendimiento de alta resolución, se utiliza DateTime.Ticks)

Antes de usar StopWatch, observe dos problemas:

  • puede no ser confiable en sistemas multiprocesador (consulte 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? Mientras la duración que esté midiendo sea inferior a 24.9 días y calcule la duración relativa , está bien. No importa cuánto tiempo haya estado funcionando el sistema, siempre y cuando solo se preocupe por su parte de ese tiempo de ejecución (en lugar de realizar directamente comparaciones menores o mayores que en los puntos inicial 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, permite que la CPU se encargue de esto.

Esta es una situación común en la que me he encontrado antes con recuentos de tiempo en sistemas integrados. Nunca compararía antes rolllover & Lt; afterrollover directamente, por ejemplo. Siempre realizaría la resta para encontrar la duración que tiene en cuenta la reinversión y luego basaría cualquier otro cálculo en la duración.

Probablemente desee System.Diagnostics.StopWatch .

Si está buscando la funcionalidad de Environment.TickCount pero sin la sobrecarga de crear nuevos objetos 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() devuelve un long, no se desbordará por mucho, mucho tiempo (más de 100,000 años, en la máquina que estoy usando para escribir esto). También es mucho más preciso que <=> que tiene una resolución máxima de 10 a 16 milisegundos.

Uso

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 medidas 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 una especie de resumen actualizado de cuáles pueden ser las respuestas y comentarios más útiles en este hilo + puntos de referencia y variantes adicionales:

Primero lo primero:Como otros han señalado en los comentarios, las cosas han cambiado en los últimos años y con Windows "modernos" (Win XP ++) y .NET, y el hardware moderno, hay pocas o ninguna razón para no usar Stopwatch().Ver MSDN para detalles.Citas:

"¿La precisión del 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 El procesador tiene un TSC invariante., el QPC no se ve afectado por este tipo de cambios.Si el procesador no tiene un TSC invariante, QPC volverá 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, sistemas multinúcleo y sistemas con hiperprocesamiento?

¿Cómo determino y valido que QPC funcione en mi máquina?
No es necesario realizar tales comprobaciones.

¿Qué procesadores tienen TSC no invariantes?.. leer más ..] "

Pero si no necesita la precisión de Stopwatch() o al menos quiere saber exactamente sobre el rendimiento de Stopwatch (estático vs.basado en instancias) y otras posibles variantes, continúa leyendo:

Tomé el punto de referencia anterior de cskwg y amplié el código para obtener más variantes.He medido con un i7 4700 MQ de algunos años y C# 7 con VS 2017 (para ser más precisos, compilado con .NET 4.5.2, a pesar de los literales binarios, es C# 6 (usado de esto:literales de cadena y 'uso estático').Especialmente el rendimiento de Stopwatch() parece haber mejorado 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 bits, modo Release sin optimización:

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

32 bits, modo Release, optimizado:

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

64 bits, modo Release sin optimización:

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

64 bits, modo Release, optimizado:

Medido:GetTickCount64() [ms]:148
Medido:Entorno.TickCount [ms]:31 (¿Aún vale la pena?)
Medido:DateTime.UtcNow.Ticks [ms]:76 (!)
Medido:Cronógrafo:.ElapsedTicks [ms]:178
Medido:Cronógrafo:.Milisegundos transcurridos [ms]:226
Medido:Cronómetro estático.GetTimestamp [ms]:175
Medido:Cronómetro+conversión a FechaHora [ms]:246
Compare eso con DateTime.Now.Ticks [ms]:3020

Puede ser muy interesante que crear un valor DateTime para imprimir la hora del cronómetro parece casi no tener costos.Interesante desde un punto de vista más académico que práctico 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.ElapsedMillisegundos sólo con 32 bits es tan lento en comparación con sus otras variantes, por ejemplo la estática.Esto y DateTime. Ahora duplican su velocidad con 64 bits.

Puedes ver:Sólo para millones de ejecuciones el tiempo del Cronómetro empieza a importar.Si este es realmente el caso (pero cuidado con la microoptimización demasiado pronto), puede ser interesante que con GetTickCount64(), pero especialmente con FechaHora.UtcNow, tienes un temporizador de 64 bits (largo) con menos precisión que el cronómetro, pero más rápido, para que no tengas que perder el tiempo con el "feo" Environment.TickCount de 32 bits.

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

Si lo ejecuta, el código recupera también la precisión actual de su 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, e.g. 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();
    }

Debe usar el Cronómetro clase en su lugar.

Uso Environment.TickCount porque:

  1. La clase de cronómetro no está en Compact Framework.
  2. El cronómetro utiliza el mismo mecanismo de sincronización subyacente que TickCount, por lo que los resultados no serán más ni menos precisos.
  3. El problema envolvente con TickCount es cósmicamente improbable de ser golpeado (tendrías que dejar tu computadora en funcionamiento durante 27 días y luego tratar de medir un tiempo que solo pasa por la envoltura -alrededor del momento), e incluso si lo golpeas, el resultado sería un enorme lapso de tiempo negativo (por lo que se destacaría).

Dicho esto, también recomendaría usar el cronómetro, si está disponible para usted. O podría 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 del cronómetro que mencione el problema envolvente con el mecanismo del temporizador subyacente, por lo que no me sorprendería en absoluto encontrar que el cronómetro sufre el mismo problema. Pero, de nuevo, no pasaría ningún tiempo preocupándome por eso.

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

Para la sincronización de una sola vez, es aún más simple escribir

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

Supongo que la envoltura cosméticamente 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, a 2.4e9 tics por segundo. Incluso a ese ritmo, llevaría más de 121 años desbordar el campo de las garrapatas. Por supuesto, no sé qué sucede debajo de las sábanas, así que tómalo con un grano de sal. Sin embargo, me doy cuenta de que la documentación de StopWatch ni siquiera menciona el problema envolvente, mientras que el documento de TickCount sí lo hace.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top