¿Cómo escribo un micro-benchmark correcto en Java?
¿Cómo se escribe (y ejecuta) un micro-benchmark correcto en Java?
Estoy buscando algunos ejemplos de código y comentarios que ilustren varias cosas en las que pensar.
Ejemplo: ¿Debería el punto de referencia medir el tiempo / iteración o iteraciones / tiempo, y por qué?
Relacionado: ¿Es aceptable la evaluación comparativa del cronómetro?
Respuestas
Consejos sobre cómo escribir micro evaluaciones comparativas de los creadores de Java HotSpot :
Regla 0: lea un documento de buena reputación sobre JVM y micro-benchmarking. Uno bueno es Brian Goetz, 2005 . No espere demasiado de los micro-benchmarks; miden solo un rango limitado de características de rendimiento de JVM.
Regla 1: Incluya siempre una fase de preparación que ejecute su kernel de prueba hasta el final, suficiente para activar todas las inicializaciones y compilaciones antes de la (s) fase (s) de tiempo. (Menos iteraciones está bien en la fase de calentamiento. La regla general es varias decenas de miles de iteraciones de bucle interno).
Regla 2: Siempre ejecuta con -XX:+PrintCompilation
, -verbose:gc
, etc., para que pueda verificar que el compilador y otras partes de la JVM no están haciendo un trabajo inesperado durante su fase de temporización.
Regla 2.1: Imprima mensajes al principio y al final de las fases de cronometraje y calentamiento, para que pueda verificar que no hay salida de la Regla 2 durante la fase de cronometraje.
Regla 3: tenga en cuenta la diferencia entre -client
y -server
, y OSR y las compilaciones regulares. La -XX:+PrintCompilation
bandera informa compilaciones OSR con un signo en para denotar el punto de entrada no inicial, por ejemplo: Trouble$1::run @ 2 (41 bytes)
. Prefiera el servidor al cliente, y regular a OSR, si busca el mejor rendimiento.
Regla 4: tenga en cuenta los efectos de la inicialización. No imprima por primera vez durante su fase de temporización, ya que la impresión carga e inicializa las clases. No cargue nuevas clases fuera de la fase de preparación (o fase de informe final), a menos que esté probando la carga de clases específicamente (y en ese caso cargue solo las clases de prueba). La regla 2 es su primera línea de defensa contra tales efectos.
Regla 5: Tenga en cuenta los efectos de desoptimización y recompilación. No tome ninguna ruta de código por primera vez en la fase de temporización, porque el compilador puede desechar y recompilar el código, basándose en una suposición optimista anterior de que la ruta no se usaría en absoluto. La regla 2 es su primera línea de defensa contra tales efectos.
Regla 6: Utilice las herramientas adecuadas para leer la mente del compilador y espere sorprenderse con el código que produce. Inspeccione el código usted mismo antes de formar teorías sobre qué hace que algo sea más rápido o más lento.
Regla 7: Reduce el ruido en tus medidas. Ejecute su punto de referencia en una máquina silenciosa y ejecútelo varias veces, descartando valores atípicos. Úselo -Xbatch
para serializar el compilador con la aplicación y considere la posibilidad de configurar -XX:CICompilerCount=1
para evitar que el compilador se ejecute en paralelo consigo mismo. Haga todo lo posible para reducir la sobrecarga de GC, establezca Xmx
(lo suficientemente grande) iguales Xms
y utilícelo UseEpsilonGCsi está disponible.
Regla 8: use una biblioteca para su punto de referencia, ya que probablemente sea más eficiente y ya se depuró con este único propósito. Como JMH , Caliper o los excelentes puntos de referencia UCSD de Bill y Paul para Java .
Sé que esta pregunta ha sido marcada como respondida, pero quería mencionar dos bibliotecas que nos ayudan a escribir micro benchmarks.
Caliper de Google
Tutoriales de introducción
- http://codingjunkie.net/micro-benchmarking-with-caliper/
- http://vertexlabs.co.uk/blog/caliper
JMH de OpenJDK
Tutoriales de introducción
- Evitar errores de evaluación comparativa en la JVM
- Uso de JMH para Java Microbenchmarking
- Introducción a JMH
Las cosas importantes para los puntos de referencia de Java son:
- Calentar el JIT primera ejecutando el código varias veces antes de los plazos que
- Asegúrese de ejecutarlo durante el tiempo suficiente para poder medir los resultados en segundos o (mejor) decenas de segundos
- Si bien no puede llamar
System.gc()
entre iteraciones, es una buena idea ejecutarlo entre pruebas, para que cada prueba obtenga un espacio de memoria "limpio" con el que trabajar. (Sí,gc()
es más una pista que una garantía, pero es muy probable que realmente se acumule basura en mi experiencia). - Me gusta mostrar iteraciones y tiempo, y una puntuación de tiempo / iteración que se puede escalar de modo que el "mejor" algoritmo obtenga una puntuación de 1.0 y otros se puntúen de manera relativa. Esto significa que puede ejecutar todos los algoritmos durante mucho tiempo, variando tanto el número de iteraciones como el tiempo, pero aún obteniendo resultados comparables.
Estoy en el proceso de escribir en un blog sobre el diseño de un marco de evaluación comparativa en .NET. Tengo un par de publicaciones anteriores que pueden darle algunas ideas; no todo será apropiado, por supuesto, pero algunas pueden serlo.
jmh es una adición reciente a OpenJDK y ha sido escrito por algunos ingenieros de rendimiento de Oracle. Ciertamente vale la pena echarle un vistazo.
El jmh es un arnés de Java para construir, ejecutar y analizar puntos de referencia nano / micro / macro escritos en Java y otros lenguajes dirigidos a la JVM.
Piezas de información muy interesantes enterradas en los comentarios de las pruebas de muestra .
Ver también:
- Evitar errores de evaluación comparativa en la JVM
- Discusión sobre las principales fortalezas de jmh .
¿Debería el punto de referencia medir el tiempo / iteración o iteraciones / tiempo, y por qué?
Depende de lo que intente probar.
Si está interesado en la latencia , use tiempo / iteración y si está interesado en el rendimiento , use iteraciones / tiempo.
Si está tratando de comparar dos algoritmos, haga al menos dos puntos de referencia para cada uno, alternando el orden. es decir:
for(i=1..n)
alg1();
for(i=1..n)
alg2();
for(i=1..n)
alg2();
for(i=1..n)
alg1();
He encontrado algunas diferencias notables (5-10% a veces) en el tiempo de ejecución del mismo algoritmo en diferentes pases.
Además, asegúrese de que n sea muy grande, de modo que el tiempo de ejecución de cada bucle sea de al menos 10 segundos más o menos. Cuantas más iteraciones, más cifras significativas en su tiempo de referencia y más confiables son los datos.
Asegúrese de utilizar de alguna manera los resultados que se calculan en código comparativo. De lo contrario, su código se puede optimizar.
Existen muchas trampas posibles para escribir micro-benchmarks en Java.
Primero: Hay que calcular con todo tipo de eventos que toman tiempo más o menos aleatorio: recolección de basura, efectos de caché (de SO para archivos y de CPU para memoria), IO, etc.
Segundo: no puede confiar en la precisión de los tiempos medidos para intervalos muy cortos.
Tercero: la JVM optimiza su código mientras se ejecuta. Por lo tanto, diferentes ejecuciones en la misma instancia de JVM serán cada vez más rápidas.
Mis recomendaciones: Haga que su punto de referencia se ejecute unos segundos, eso es más confiable que un tiempo de ejecución de milisegundos. Caliente la JVM (significa ejecutar el punto de referencia al menos una vez sin medir, que la JVM puede ejecutar optimizaciones). Y ejecute su punto de referencia varias veces (tal vez 5 veces) y tome el valor mediano. Ejecute cada micro-benchmark en una nueva instancia de JVM (llame para cada nuevo Java de benchmark) de lo contrario, los efectos de optimización de la JVM pueden influir en las pruebas en ejecución posteriores. No ejecute cosas que no se ejecuten en la fase de calentamiento (ya que esto podría desencadenar la carga de clases y la recompilación).
También debe tenerse en cuenta que también podría ser importante analizar los resultados del micro benchmark al comparar diferentes implementaciones. Por lo tanto , debe realizarse una prueba de significancia .
Esto se debe a que la implementación A
puede ser más rápida durante la mayoría de las ejecuciones del punto de referencia que la implementación B
. Pero A
también puede tener un diferencial más alto, por lo que el beneficio de rendimiento medido de A
no será de importancia en comparación con B
.
Por lo tanto, también es importante escribir y ejecutar un micro benchmark correctamente, pero también analizarlo correctamente.
Para agregar a los otros excelentes consejos, también tendré en cuenta lo siguiente:
Para algunas CPU (por ejemplo, la gama Intel Core i5 con TurboBoost), la temperatura (y el número de núcleos que se utilizan actualmente, así como su porcentaje de utilización) afecta la velocidad del reloj. Dado que las CPU se sincronizan dinámicamente, esto puede afectar sus resultados. Por ejemplo, si tiene una aplicación de un solo subproceso, la velocidad máxima del reloj (con TurboBoost) es más alta que para una aplicación que usa todos los núcleos. Por lo tanto, esto puede interferir con las comparaciones de rendimiento de subprocesos únicos y múltiples en algunos sistemas. Tenga en cuenta que la temperatura y los voltajes también afectan el tiempo que se mantiene la frecuencia Turbo.
Quizás un aspecto más fundamentalmente importante sobre el que tiene control directo: ¡asegúrese de que está midiendo lo correcto! Por ejemplo, si está utilizando System.nanoTime()
para comparar un fragmento de código en particular, coloque las llamadas a la tarea en lugares que tengan sentido para evitar medir cosas que no le interesan. Por ejemplo, no lo haga:
long startTime = System.nanoTime();
//code here...
System.out.println("Code took "+(System.nanoTime()-startTime)+"nano seconds");
El problema es que no obtiene inmediatamente la hora de finalización cuando el código ha finalizado. En su lugar, intente lo siguiente:
final long endTime, startTime = System.nanoTime();
//code here...
endTime = System.nanoTime();
System.out.println("Code took "+(endTime-startTime)+"nano seconds");
http://opt.sourceforge.net/Java Micro Benchmark: tareas de control necesarias para determinar las características de rendimiento comparativas del sistema informático en diferentes plataformas. Se puede utilizar para orientar las decisiones de optimización y comparar diferentes implementaciones de Java.