Medición exacta del tiempo para las pruebas de rendimiento

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

Lo más fácil y más rápido que supongo es esto:

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

Pero, ¿qué tan exacto es esto? ¿Hay mejores formas?

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

Como han dicho otros, 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 para crear un cronómetro y luego llamar a Start() en términos de simplicidad.) Obviamente, esto tiene el impacto de invocar a un delegado, pero en la gran mayoría de los casos eso ganó ‘ t 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 StopwatchTimer, etc., donde estén disponibles.

Como otros dijeron, Stopwatch debería ser la herramienta adecuada para esto. Sin embargo, se pueden hacer algunas mejoras, consulte este hilo específicamente: comparaciones de muestras de código pequeño en C #, ¿se puede mejorar esta implementación? .

He visto algunos consejos útiles de Thomas Maierhofer aquí

Básicamente su código se ve así:

 //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; 

Aquí se puede encontrar una implementación desnuda y detallada de lo mismo.

Escribí una clase de ayuda para realizar ambas 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(action, iterations); } static void Benchmark(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(action, iterations); } } 

Solo llama

 Clock.BenchmarkTime(() => { //code }, 10000000); 

o

 Clock.BenchmarkCpu(() => { //code }, 10000000); 

La última parte del Clock es la parte difícil. Si desea mostrar el tiempo final, depende de usted elegir qué tipo de tiempo desea. Escribí un método de extensión NormalizedMean que le da la media de los tiempos de lectura descartando el ruido. Me refiero a que calculo la desviación de cada tiempo respecto de la media real, y luego descarto los valores que eran más lentos (solo los más lentos) de la media de la desviación (llamada desviación absoluta; tenga en cuenta que no es la desviación estándar que a menudo se escucha) , y finalmente devuelve la media de los valores restantes. Esto significa, por ejemplo, si los valores progtwigdos 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 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> Deviations(this ICollection values) { if (values.Count == 0) yield break; var avg = values.Average(); foreach (var d in values) yield return Tuple.Create(d, avg - d); } 

Use la clase Cronómetro

System.Diagnostics.Stopwatch está diseñado para esta tarea.

El cronómetro está bien, pero repita el trabajo 10 ^ 6 veces, luego divida por 10 ^ 6. Obtendrás mucha más precisión.

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 # Time Measurement For Performance Testing (No en inglés)

Sí, hay alguna función 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; } }