Medición del tiempo exacto para pruebas de rendimiento [duplicar]


Esta pregunta ya tiene una respuesta aquí:

¿Cuál es la forma más exacta de ver cuánto tiempo algo, por ejemplo una llamada a un método, tomó en código?

Lo más fácil y rápido que podría adivinar es esto:

DateTime start = DateTime.Now;
{
    // Do some work
}
TimeSpan timeItTook = DateTime.Now - start;

Pero ¿qué tan exacto es esto? Hay mejores maneras?

Author: Philippe Leybaert, 2009-06-09

8 answers

Una mejor manera es usar la clase Cronómetro:

using System.Diagnostics;
// ...

Stopwatch sw = new Stopwatch();

sw.Start();

// ...

sw.Stop();

Console.WriteLine("Elapsed={0}",sw.Elapsed);
 474
Author: Philippe Leybaert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-24 11:05:00

Como otros han dicho, Stopwatch es una buena clase para usar aquí. Puede envolverlo en un método útil:

public static TimeSpan Time(Action action)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    action();
    stopwatch.Stop();
    return stopwatch.Elapsed;
}

(Tenga en cuenta el uso de Stopwatch.StartNew(). Prefiero esto a crear un cronómetro y luego llamar Start() en términos de simplicidad.) Obviamente esto incurre en el golpe de invocar a un delegado, pero en la gran mayoría de los casos eso no será relevante. Entonces escribirías:

TimeSpan time = StopwatchUtil.Time(() =>
{
    // Do some work
});

Incluso podría hacer una interfaz ITimer para esto, con implementaciones de StopwatchTimer, CpuTimer etc donde esté disponible.

 146
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-09 10:46:38

Como otros dijeron, Stopwatch debería ser la herramienta correcta para esto. Sin embargo, puede haber pocas mejoras, ver este hilo específicamente: Benchmarking de pequeñas muestras de código en C#, ¿se puede mejorar esta implementación?.

He visto algunos consejos útiles de Thomas Maierhofer aquí

Básicamente su código se ve como:

//prevent the JIT Compiler from optimizing Fkt calls away
long seed = Environment.TickCount;

//use the second Core/Processor for the test
Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2);

//prevent "Normal" Processes from interrupting Threads
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

//prevent "Normal" Threads from interrupting this thread
Thread.CurrentThread.Priority = ThreadPriority.Highest;

//warm up
method();

var stopwatch = new Stopwatch()
for (int i = 0; i < repetitions; i++)
{
    stopwatch.Reset();
    stopwatch.Start();
    for (int j = 0; j < iterations; j++)
        method();
    stopwatch.Stop();
    print stopwatch.Elapsed.TotalMilliseconds;
}

Otro enfoque es confiar en Process.TotalProcessTime para medir cuánto tiempo la CPU se ha mantenido ocupada ejecutando el mismo código / proceso, como se muestra aquí Esto puede reflejar un escenario más real, ya que ningún otro proceso afecta la medición. Hace algo como:

 var start = Process.GetCurrentProcess().TotalProcessorTime;
 method();
 var stop = Process.GetCurrentProcess().TotalProcessorTime;
 print (end - begin).TotalMilliseconds;

Una implementación desnuda y detallada de lo mismo se puede encontrar aquí.

Escribí una clase auxiliar para realizar ambos de una manera fácil de usar:

public class Clock
{
    interface IStopwatch
    {
        bool IsRunning { get; }
        TimeSpan Elapsed { get; }

        void Start();
        void Stop();
        void Reset();
    }



    class TimeWatch : IStopwatch
    {
        Stopwatch stopwatch = new Stopwatch();

        public TimeSpan Elapsed
        {
            get { return stopwatch.Elapsed; }
        }

        public bool IsRunning
        {
            get { return stopwatch.IsRunning; }
        }



        public TimeWatch()
        {
            if (!Stopwatch.IsHighResolution)
                throw new NotSupportedException("Your hardware doesn't support high resolution counter");

            //prevent the JIT Compiler from optimizing Fkt calls away
            long seed = Environment.TickCount;

            //use the second Core/Processor for the test
            Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2);

            //prevent "Normal" Processes from interrupting Threads
            Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

            //prevent "Normal" Threads from interrupting this thread
            Thread.CurrentThread.Priority = ThreadPriority.Highest;
        }



        public void Start()
        {
            stopwatch.Start();
        }

        public void Stop()
        {
            stopwatch.Stop();
        }

        public void Reset()
        {
            stopwatch.Reset();
        }
    }



    class CpuWatch : IStopwatch
    {
        TimeSpan startTime;
        TimeSpan endTime;
        bool isRunning;



        public TimeSpan Elapsed
        {
            get
            {
                if (IsRunning)
                    throw new NotImplementedException("Getting elapsed span while watch is running is not implemented");

                return endTime - startTime;
            }
        }

        public bool IsRunning
        {
            get { return isRunning; }
        }



        public void Start()
        {
            startTime = Process.GetCurrentProcess().TotalProcessorTime;
            isRunning = true;
        }

        public void Stop()
        {
            endTime = Process.GetCurrentProcess().TotalProcessorTime;
            isRunning = false;
        }

        public void Reset()
        {
            startTime = TimeSpan.Zero;
            endTime = TimeSpan.Zero;
        }
    }



    public static void BenchmarkTime(Action action, int iterations = 10000)
    {
        Benchmark<TimeWatch>(action, iterations);
    }

    static void Benchmark<T>(Action action, int iterations) where T : IStopwatch, new()
    {
        //clean Garbage
        GC.Collect();

        //wait for the finalizer queue to empty
        GC.WaitForPendingFinalizers();

        //clean Garbage
        GC.Collect();

        //warm up
        action();

        var stopwatch = new T();
        var timings = new double[5];
        for (int i = 0; i < timings.Length; i++)
        {
            stopwatch.Reset();
            stopwatch.Start();
            for (int j = 0; j < iterations; j++)
                action();
            stopwatch.Stop();
            timings[i] = stopwatch.Elapsed.TotalMilliseconds;
            print timings[i];
        }
        print "normalized mean: " + timings.NormalizedMean().ToString();
    }

    public static void BenchmarkCpu(Action action, int iterations = 10000)
    {
        Benchmark<CpuWatch>(action, iterations);
    }
}

Solo llama

Clock.BenchmarkTime(() =>
{
    //code

}, 10000000);

O

Clock.BenchmarkCpu(() =>
{
    //code

}, 10000000);

La última parte de la Clock es la parte difícil. Si desea mostrar el el tiempo final, depende de usted elegir qué tipo de tiempo desea. Escribí un método de extensión NormalizedMean que te da la media de los tiempos de lectura descartando el ruido. Quiero decir que calculo la desviación de cada tiempo de la media real, y luego descarto los valores que eran más lejanos (solo los más lentos) de la media de desviación (llamada desviación absoluta; tenga en cuenta que no es la desviación estándar a menudo escuchada), y finalmente devuelvo la media de los valores restantes. Esto significa, por ejemplo, si los valores cronometrados son { 1, 2, 3, 2, 100 } (en ms o lo que sea), descarta 100, y devuelve la media de { 1, 2, 3, 2 } que es 2. O si los tiempos son { 240, 220, 200, 220, 220, 270 }, descarta 270, y devuelve la media de { 240, 220, 200, 220, 220 } que es 220.

public static double NormalizedMean(this ICollection<double> values)
{
    if (values.Count == 0)
        return double.NaN;

    var deviations = values.Deviations().ToArray();
    var meanDeviation = deviations.Sum(t => Math.Abs(t.Item2)) / values.Count;
    return deviations.Where(t => t.Item2 > 0 || Math.Abs(t.Item2) <= meanDeviation).Average(t => t.Item1);
}

public static IEnumerable<Tuple<double, double>> Deviations(this ICollection<double> values)
{
    if (values.Count == 0)
        yield break;

    var avg = values.Average();
    foreach (var d in values)
        yield return Tuple.Create(d, avg - d);
}
 65
Author: nawfal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:55:09

Utilice el cronómetro clase

 13
Author: Mehrdad Afshari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-09 10:39:10

Sistema.Diagnostico.Cronómetro está diseñado para esta tarea.

 12
Author: Dimi Toulakis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-09 10:40:44

Cronómetro está bien, pero bucle el trabajo 10^6 veces, a continuación, dividir por 10^6. Obtendrás mucha más precisión.

 4
Author: Mike Dunlavey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-09 21:34:07

Estoy usando esto:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(myUrl);
System.Diagnostics.Stopwatch timer = new Stopwatch();

timer.Start();

HttpWebResponse response = (HttpWebResponse)request.GetResponse();

statusCode = response.StatusCode.ToString();

response.Close();

timer.Stop();

De mi blog: C # Medición Del Tiempo Para Pruebas De Rendimiento (No en Inglés)

 2
Author: altansezerayan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-12 17:30:31

Sí hay algunas funciones en el kernel de Windows

[System.Runtime.InteropServices.DllImport("KERNEL32")]
private static extern bool QueryPerformanceCounter(ref long lpPerformanceCount);

[System.Runtime.InteropServices.DllImport("KERNEL32")]
private static extern bool QueryPerformanceFrequency(ref long lpFrequency);

public static float CurrentSecond
{
    get
    {
        long current = 0;
        QueryPerformanceCounter(ref current);
        long frequency = 0;
        QueryPerformanceFrequency(ref frequency);
        return (float) current / (float) frequency;
    }
}
 -12
Author: Alin Vasile,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-17 07:52:54