ventajas - C#: ¿Es esta clase de evaluación comparativa precisa?
metodo de distribucion forzada pdf (4)
Creé una clase simple para comparar algunos métodos míos. Pero, ¿es exacto? Soy un poco nuevo en el benchmarking, el tiempo, etcétera, así que pensé que podría pedir algunos comentarios aquí. Además, si es bueno, tal vez alguien más pueda usarlo también :)
public static class Benchmark
{
public static IEnumerable<long> This(Action subject)
{
var watch = new Stopwatch();
while (true)
{
watch.Reset();
watch.Start();
subject();
watch.Stop();
yield return watch.ElapsedTicks;
}
}
}
Puedes usarlo así:
var avg = Benchmark.This(() => SomeMethod()).Take(500).Average();
¿Cualquier retroalimentación? ¿Parece ser bastante estable y precisa, o me he perdido algo?
Como no soy un programador de C #, no puedo decir con precisión si esa clase es una implementación apropiada para contar cuánto tiempo lleva la ejecución de una función. Sin embargo, hay cosas a tener en cuenta para la repetibilidad y precisión.
No estoy al tanto de las diversas entradas y salidas del .NET Framework, pero dependiendo de cómo se compile con el código nativo, es posible que cualquier compilación afecte los resultados del benchmark. Además, si una función está o no en la memoria caché también puede hacer una diferencia. Por lo tanto, deseará pasar por encima de su función para asegurarse de que no haya ningún golpe de la compilación y de que todo esté cargado y listo. Una vez hecho esto, es posible que pueda comenzar.
Otros probablemente tendrán mejor información y conocimiento de .NET que yo.
Definitivamente debería devolver ElapsedMilliseconds en lugar de ElapsedTicks. El valor devuelto por ElapsedTicks depende de la frecuencia del cronómetro, que puede ser diferente en diferentes sistemas. No se corresponderá necesariamente con la propiedad Ticks de un objeto Timespan o DateTime.
Consulte http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.elapsedticks.aspx .
Si desea la resolución adicional de Ticks, debe devolver watch.Elapsed.Ticks
(es decir, Timestamp.Ticks) en lugar de watch.ElapsedTicks
(este podría ser uno de los errores potenciales más sutiles en .Net). Desde MSDN:
Las marcas de cronómetro son diferentes de DateTime.Ticks. Cada tilde en el valor DateTime.Ticks representa un intervalo de 100 nanosegundos. Cada tilde en el valor ElapsedTicks representa el intervalo de tiempo igual a 1 segundo dividido por la Frecuencia.
Aparte de eso, supongo que tu código está bien, aunque creo que incluirías parte de la sobrecarga de llamadas de método en tus mediciones, lo que podría ser significativo si los métodos mismos toman muy poco tiempo para ejecutarse. Además, es probable que desee excluir la primera llamada al método de su promedio calculado, pero no estoy seguro de cómo lo haría en su clase.
Un último punto, que probablemente no sea relevante para la mayoría de los usos de esta clase: Cronómetro funciona un poco rápido en comparación con la hora del sistema. En mi computadora, transcurren aproximadamente 5 segundos (es decir, segundos , no milisegundos) después de 24 horas, y en otras máquinas esta deriva puede ser aún mayor. Por lo tanto, es un poco engañoso decir que es muy preciso , cuando en realidad es muy granular . Para cronometrar los métodos de corta duración, esto obviamente no sería un problema significativo.
Y un último punto más, que sin duda es relevante: a menudo, durante la evaluación comparativa, he observado que obtendré muchos tiempos de ejecución agrupados dentro de un estrecho rango de valores (por ejemplo, 80, 80, 79, 82, etc.) , pero ocasionalmente algo más sucederá en Windows (como abrir otro programa o mi antivirus se pone en marcha o algo así) y obtendré un valor salvajemente fuera de sintonía con los demás (por ejemplo, 80, 80, 79, 271, 80, etc. .). Creo que una solución simple a este problema atípico es utilizar la mediana de sus medidas en lugar de la media . No sé si Linq lo admite automáticamente o no.
Es lo más preciso que puede obtener para un punto de referencia simple. Pero hay algunos factores que no están bajo su control:
- cargar en el sistema desde otros procesos
- estado del montón antes / durante el punto de referencia
Podría hacer algo con respecto a ese último punto, un punto de referencia es una de las raras situaciones en las que se puede defender GC.Collect
. Y puede llamar al subject
una vez para eliminar cualquier problema de JIT. Pero eso requiere llamadas a subject
para ser independiente.
public static IEnumerable<TimeSpan> This(Action subject)
{
subject(); // warm up
GC.Collect(); // compact Heap
GC.WaitForPendingFinalizers(); // and wait for the finalizer queue to empty
var watch = new Stopwatch();
while (true)
{
watch.Reset();
watch.Start();
subject();
watch.Stop();
yield return watch.Elapsed; // TimeSpan
}
}
Como bonificación, su clase debe verificar el campo System.Diagnostics.Stopwatch.IsHighResolution
. Si está apagado, solo tienes una resolución muy gruesa (20 ms).
Pero en una PC normal, con muchos servicios ejecutándose en segundo plano, nunca va a ser muy preciso.
Problemas de pareja aquí.
Primero, recuerde que la primera vez que ejecute el código, se cerrará el cierre transitivo de sus llamadas a métodos. Eso significa que es probable que la primera ejecución tenga un costo mayor que cada ejecución posterior. Dependiendo de si está comparando los tiempos "fríos" o los tiempos "calientes", esto podría marcar la diferencia. ¡He visto métodos en los que el costo de agotar el método era más alto que el de todas las demás convocatorias!
En segundo lugar, recuerde que el recolector de basura se ejecuta en otro hilo. Si está haciendo basura en una sola ejecución, entonces el costo de limpiar esa basura podría no realizarse hasta que se ejecute suebsequent. Por lo tanto, no puede contabilizar el costo total de una ejecución, al pasarlo a ejecuciones posteriores.
Ambas son indicativas de la debilidad de todas las evaluaciones comparativas: la evaluación comparativa es, por naturaleza, poco realista y, por lo tanto, de valor limitado. En el código del mundo real, el GC se ejecutará, el jitter se ejecutará, y así sucesivamente. Con frecuencia, el rendimiento de referencia no es para nada comparable al rendimiento en el mundo real porque el índice de referencia no tiene en cuenta la variabilidad de los costos del mundo real inherentes a un sistema grande. En lugar de analizar las características de perforación de forma aislada, prefiero observar las características de rendimiento de los escenarios realistas que realmente enfrentan los clientes reales.