¿DateTime.Now es la mejor manera de medir el rendimiento de una función?

474

Necesito encontrar un cuello de botella y necesito medir el tiempo con la mayor precisión posible.

¿Es el siguiente fragmento de código la mejor manera de medir el rendimiento?

DateTime startTime = DateTime.Now;

// Some execution process

DateTime endTime = DateTime.Now;
TimeSpan totalTimeTaken = endTime.Subtract(startTime);
David Basarab
fuente
Por cierto, si no está buscando algo, se pueden usar contadores de rendimiento rápidos y sucios.
Jonathan C Dickinson
1
Si necesita una mayor precisión, use Stopwatch.GetTimestamp, de lo contrario, la respuesta es buena.
dbasnett
@dbasnett ¿Puedes entrar en más detalles en una respuesta?
David Basarab
En el ejemplo anterior, cambie el tiempo de inicio y finalización a largo y asígneles Stopwatch.GetTimestamp a ellos en lugar de DateTime.Now. El tiempo necesario es (fin-inicio) / Stopwatch. Frecuencia.
dbasnett
Ver también: El caso contra DateTime
Matt Johnson-Pint

Respuestas:

649

No, no es. Use el cronómetro (en System.Diagnostics)

Stopwatch sw = Stopwatch.StartNew();
PerformWork();
sw.Stop();

Console.WriteLine("Time taken: {0}ms", sw.Elapsed.TotalMilliseconds);

El cronómetro verifica automáticamente la existencia de temporizadores de alta precisión.

Vale la pena mencionar que a DateTime.Nowmenudo es un poco más lento que DateTime.UtcNowdebido al trabajo que se debe hacer con las zonas horarias , DST y demás .

DateTime.UtcNow normalmente tiene una resolución de 15 ms. Vea la publicación del blog de John Chapman sobre DateTime.Nowprecisión para un gran resumen.

Curiosidades interesantes: el cronómetro vuelve a funcionar DateTime.UtcNowsi su hardware no admite un contador de alta frecuencia. Puede verificar si el cronómetro usa hardware para lograr una alta precisión mirando el cronómetro de campo estático . IsHighResolution .

Markus Olsson
fuente
3
Colocaría uno PerformWork (); antes del cronómetro para "calentamiento".
DiVan
2
También debe agregar la recomendación de que si PerformWork()es muy corto, puede llamarlo repetidamente y calcular el promedio del lote de llamadas. Además, programe un lote completo de llamadas en lugar de iniciar / detener su Stopwatchpara evitar un efecto estroboscópico que enturbiará sus mediciones de tiempo.
devgeezer
1
El cronómetro no es seguro para hilos en multinúcleo. Ver stackoverflow.com/questions/6664538/… y stackoverflow.com/questions/1149485/…
Pavel Savara el
1
sw.ElapsedMilliseconds; también puede
Flappy
2
@Pavel, para ser claros, Microsoft recomienda el cronómetro como la mejor solución (bajo costo y alta precisión) en los modernos procesadores multinúcleo que ejecutan Windows 7 y Windows 8. msdn.microsoft.com/en-us/library/windows/ escritorio / ...
Ron
91

Si desea algo rápido y sucio, sugeriría usar el cronómetro en su lugar para un mayor grado de precisión.

Stopwatch sw = new Stopwatch();
sw.Start();
// Do Work
sw.Stop();

Console.WriteLine("Elapsed time: {0}", sw.Elapsed.TotalMilliseconds);

Alternativamente, si necesita algo un poco más sofisticado, probablemente debería considerar usar un perfilador de terceros como ANTS .

mmcdole
fuente
56

En este artículo se dice que en primer lugar es necesario comparar tres alternativas, Stopwatch, DateTime.NowY DateTime.UtcNow.

También muestra que en algunos casos (cuando el contador de rendimiento no existe) el cronómetro está usando DateTime.UtcNow + algún procesamiento adicional. Por eso es obvio que en ese caso DateTime.UtcNow es la mejor opción (porque otros lo usan + algún procesamiento)

Sin embargo, como resultado, el contador casi siempre existe: consulte la explicación sobre el contador de rendimiento de alta resolución y su existencia relacionada con el cronómetro .NET. .

Aquí hay un gráfico de rendimiento. Observe cómo el bajo costo de rendimiento que UtcNow ha comparado con las alternativas:

Ingrese la descripción de la imagen aquí

El eje X es el tamaño de los datos de muestra, y el eje Y es el tiempo relativo del ejemplo.

Una cosa Stopwatches mejor porque proporciona mediciones de tiempo de mayor resolución. Otro es su naturaleza más OO. Sin embargo, crear un contenedor OO UtcNowno puede ser difícil.

Valentin Kuzub
fuente
El primer enlace parece estar roto.
Peter Mortensen
1
se rompió sí ... la máquina del tiempo puede mostrarlo, supongo. Por cierto, por qué editas "los tres", creo que no es necesario aquí.
Valentin Kuzub
18

Es útil insertar su código de evaluación comparativa en una clase / método de utilidad. La StopWatchclase no necesita ser Disposedo Stoppedpor error. Entonces, el código más simple para cronometrar alguna acción es

public partial class With
{
    public static long Benchmark(Action action)
    {
        var stopwatch = Stopwatch.StartNew();
        action();
        stopwatch.Stop();
        return stopwatch.ElapsedMilliseconds;
    }
}

Código de llamada de muestra

public void Execute(Action action)
{
    var time = With.Benchmark(action);
    log.DebugFormat(“Did action in {0} ms.”, time);
}

Aquí está la versión del método de extensión

public static class Extensions
{
    public static long Benchmark(this Action action)
    {
        return With.Benchmark(action);
    }
}

Y muestra de código de llamada

public void Execute(Action action)
{
    var time = action.Benchmark()
    log.DebugFormat(“Did action in {0} ms.”, time);
}
Anthony Mastrean
fuente
1
¿Qué pasa con una mejor granularidad? Muchas cosas suceden en menos de un ms.
Henrik
Devuelve la propiedad Elapsed entonces, es un TimeSpan. Solo te estoy mostrando el patrón. Diviértete implementándolo.
Anthony Mastrean
Regreso Elapsed.TotalMillisecondspara mayor precisión. Vea esta pregunta también stackoverflow.com/questions/8894425/…
nawfal
16

La funcionalidad del cronómetro sería mejor (mayor precisión). Sin embargo, también recomendaría descargar uno de los perfiladores populares ( DotTrace y ANTS son los que más he usado ... la versión de prueba gratuita para DotTrace es completamente funcional y no molesta como algunos de los otros).

jsight
fuente
13

Utilice la clase System.Diagnostics.Stopwatch.

Stopwatch sw = new Stopwatch();
sw.Start();

// Do some code.

sw.Stop();

// sw.ElapsedMilliseconds = the time your "do some code" took.
rp.
fuente
11

Ídem Cronómetro, es mucho mejor.

Con respecto a la medición del rendimiento, también debe verificar si su "// Algún proceso de ejecución" es un proceso muy corto.

También tenga en cuenta que la primera ejecución de su "// Algún proceso de ejecución" podría ser mucho más lenta que las ejecuciones posteriores.

Por lo general, pruebo un método ejecutándolo 1000 veces o 1000000 veces en un bucle y obtengo datos mucho más precisos que ejecutarlo una vez.

Andrei Rînea
fuente
9

Todas estas son excelentes formas de medir el tiempo, pero esa es solo una forma muy indirecta de encontrar cuellos de botella.

La forma más directa de encontrar un cuello de botella en un hilo es hacerlo funcionar, y mientras está haciendo lo que sea que lo haga esperar, deténgalo con una tecla de pausa o pausa. Haz esto varias veces. Si su cuello de botella toma X% de tiempo, X% es la probabilidad de que lo atrape en el acto en cada instantánea.

Aquí hay una explicación más completa de cómo y por qué funciona.

Mike Dunlavey
fuente
7

@ Sean Chambers

Para su información, la clase .NET Timer no es para diagnósticos, genera eventos en un intervalo preestablecido, como este (desde MSDN ):

System.Timers.Timer aTimer;
public static void Main()
{
    // Create a timer with a ten second interval.
    aTimer = new System.Timers.Timer(10000);

    // Hook up the Elapsed event for the timer.
    aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);

    // Set the Interval to 2 seconds (2000 milliseconds).
    aTimer.Interval = 2000;
    aTimer.Enabled = true;

    Console.WriteLine("Press the Enter key to exit the program.");
    Console.ReadLine();
}

// Specify what you want to happen when the Elapsed event is 
// raised.
private static void OnTimedEvent(object source, ElapsedEventArgs e)
{
    Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
}

Entonces, esto realmente no ayuda a saber cuánto tiempo tomó algo, solo que ha pasado una cierta cantidad de tiempo.

El temporizador también se expone como un control en System.Windows.Forms ... puede encontrarlo en su caja de herramientas de diseño en VS05 / VS08

Adam Haile
fuente
6

Esta es la forma correcta:

using System;
using System.Diagnostics;

class Program
{
    public static void Main()
    {
        Stopwatch stopWatch = Stopwatch.StartNew();

            // some other code

        stopWatch.Stop();

        // this not correct to get full timer resolution
        Console.WriteLine("{0} ms", stopWatch.ElapsedMilliseconds);

        // Correct way to get accurate high precision timing
        Console.WriteLine("{0} ms", stopWatch.Elapsed.TotalMilliseconds);
    }
}

Para obtener más información, utilice Usar cronómetro en lugar de DataTime para obtener un contador de rendimiento preciso .

jiya jain
fuente
6

Visual Studio Team System tiene algunas características que pueden ayudar con este problema. Esencialmente, puede escribir pruebas unitarias y mezclarlas en diferentes escenarios para ejecutarlas contra su software como parte de una prueba de esfuerzo o carga. Esto puede ayudar a identificar las áreas de código que más afectan el rendimiento de sus aplicaciones.

El grupo de Patrones y Prácticas de Microsoft tiene alguna orientación en la Guía de pruebas de rendimiento del sistema de equipo de Visual Studio .

Iain
fuente
5

Acabo de encontrar una publicación en el blog de Vance Morrison sobre una clase de CodeTimer que escribió que hace que usar sea StopWatchmás fácil y que haga algunas cosas interesantes aparte .

OwenP
fuente
5

Esto no es lo suficientemente profesional:

Stopwatch sw = Stopwatch.StartNew();
PerformWork();
sw.Stop();

Console.WriteLine("Time taken: {0}ms", sw.Elapsed.TotalMilliseconds);

Una versión más confiable es:

PerformWork();

int repeat = 1000;

Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < repeat; i++)
{
   PerformWork();
}

sw.Stop();

Console.WriteLine("Time taken: {0}ms", sw.Elapsed.TotalMilliseconds / repeat);

En mi código real, agregaré GC.Collect call para cambiar el montón administrado a un estado conocido y agregar Sleep call para que se puedan separar fácilmente diferentes intervalos de código en el perfil ETW.

Bye StackOverflow
fuente
4

He hecho muy poco de este tipo de comprobación de rendimiento (tiendo a pensar "esto es lento, hazlo más rápido"), así que casi siempre he seguido con esto.

Un google revela muchos recursos / artículos para la verificación del rendimiento.

Muchos mencionan el uso de pinvoke para obtener información sobre el rendimiento. Muchos de los materiales que estudio solo mencionan realmente el uso de perfmon.

Editar:

Visto las conversaciones de StopWatch .. ¡Bien! He aprendido algo :)

Este parece un buen artículo.

Rob Cooper
fuente
4

La forma en que uso dentro de mis programas es usando la clase StopWatch como se muestra aquí.

Stopwatch sw = new Stopwatch();
sw.Start();


// Critical lines of code

long elapsedMs = sw.Elapsed.TotalMilliseconds;
Blackvault
fuente