¿Cómo se escribe (y ejecuta) un micro punto de referencia correcto en Java?
Estoy buscando algunos ejemplos de código y comentarios que ilustran varias cosas en las que pensar.
Ejemplo: ¿Debe el punto de referencia medir tiempo / iteración o iteraciones / tiempo, y por qué?
Relacionado: ¿Es aceptable el benchmarking de cronómetro?
java
jvm
benchmarking
jvm-hotspot
microbenchmark
John Nilsson
fuente
fuente
Respuestas:
Consejos sobre cómo escribir micro benchmarks 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 esperes demasiado de los micro-puntos de referencia; miden solo un rango limitado de características de rendimiento de JVM.
Regla 1: Incluya siempre una fase de calentamiento que ejecute su núcleo de prueba hasta el final, lo suficiente como para activar todas las inicializaciones y compilaciones antes de la (s) fase (s) de temporización. (Menos iteraciones están 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 temporización y calentamiento, para que pueda verificar que no haya salida de la Regla 2 durante la fase de temporización.
Regla 3: Tenga en cuenta la diferencia entre
-client
y-server
, y OSR y 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)
. Prefiere servidor a cliente y regular a OSR, si buscas el mejor rendimiento.Regla 4: Tenga en cuenta los efectos de 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 calentamiento (o fase de informe final), a menos que esté probando la carga de clase 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 volver a compilar el código, en base a una suposición optimista anterior de que la ruta no se iba a utilizar en absoluto. La regla 2 es su primera línea de defensa contra tales efectos.
Regla 6: Use las herramientas apropiadas para leer la mente del compilador y espere ser sorprendido por el código que produce. Inspeccione el código usted mismo antes de formar teorías sobre lo que 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. Utilícelo
-Xbatch
para serializar el compilador con la aplicación y considere la configuración-XX:CICompilerCount=1
para evitar que el compilador se ejecute en paralelo consigo mismo. Haga todo lo posible para reducir la sobrecarga del GC, establezcaXmx
(lo suficientemente grande) igualesXms
y utilíceloUseEpsilonGC
si está disponible.Regla 8: Use una biblioteca para su punto de referencia, ya que probablemente sea más eficiente y ya se haya depurado para este único propósito. Como JMH , Caliper o Bill and Paul's Excellent UCSD Benchmarks para Java .
fuente
System.nanoTime()
no se garantiza que sea más preciso queSystem.currentTimeMillis()
. Solo se garantiza que sea al menos tan preciso. Sin embargo, generalmente es mucho más preciso.System.nanoTime()
lugar deSystem.currentTimeMillis()
es que se garantiza que la primera aumentará monotónicamente. Restar los valores devueltos doscurrentTimeMillis
invocaciones en realidad puede dar resultados negativos, posiblemente porque el tiempo del sistema fue ajustado por algún demonio NTP.Sé que esta pregunta se ha marcado como respondida, pero quería mencionar dos bibliotecas que nos ayudan a escribir micro puntos de referencia.
Pinza de Google
Tutoriales de iniciación
JMH de OpenJDK
Tutoriales de iniciación
fuente
Las cosas importantes para los puntos de referencia de Java son:
System.gc()
entre iteraciones, es una buena idea ejecutarlo entre pruebas, de modo que con suerte cada prueba obtenga un espacio de memoria "limpio" para trabajar. (Sí,gc()
es más una pista que una garantía, pero es muy probable que en mi experiencia realmente se acumule basura).Estoy en el proceso de bloguear sobre el diseño de un marco de referencia en .NET. Tengo un par de publicaciones anteriores que pueden darte algunas ideas; no todo será apropiado, por supuesto, pero algunas pueden serlo.
fuente
gc
siempre liberan memoria no utilizada.System.gc()
, ¿cómo propone minimizar la recolección de basura en una prueba debido a los objetos creados en pruebas anteriores? Soy pragmático, no dogmático.jmh es una adición reciente a OpenJDK y ha sido escrito por algunos ingenieros de rendimiento de Oracle. Sin duda vale la pena echarle un vistazo.
Piezas muy interesantes de la información enterrados en las pruebas de muestra comentarios .
Ver también:
fuente
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.
fuente
Si está tratando de comparar dos algoritmos, haga al menos dos puntos de referencia para cada uno, alternando el orden. es decir:
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 ciclo sea de al menos 10 segundos más o menos. Cuantas más iteraciones, cifras más significativas en su tiempo de referencia y más confiables son los datos.
fuente
Asegúrese de utilizar de alguna manera los resultados que se calculan en código de referencia. De lo contrario, su código se puede optimizar.
fuente
Hay muchas posibles dificultades para escribir micro-puntos de referencia en Java.
Primero: debe calcular con todo tipo de eventos que toman tiempo más o menos al azar: recolección de basura, efectos de almacenamiento en caché (del sistema operativo para archivos y de la CPU para memoria), IO, etc.
Segundo: no puede confiar en la precisión de los tiempos medidos durante 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 durante milisegundos. Calentar la JVM (significa ejecutar el punto de referencia al menos una vez sin medir, para que la JVM pueda ejecutar optimizaciones). Y ejecute su punto de referencia varias veces (tal vez 5 veces) y tome el valor medio. Ejecute cada micro-punto de referencia en una nueva instancia de JVM (llame a cada nuevo punto de referencia Java nuevo) de lo contrario, los efectos de optimización de la JVM pueden influir en las pruebas posteriores. No ejecute cosas que no se ejecutan en la fase de calentamiento (ya que esto podría desencadenar carga de clase y recompilación).
fuente
También se debe tener en cuenta que también podría ser importante analizar los resultados del micro punto de referencia al comparar diferentes implementaciones. Por lo tanto, una prueba de significación debe hacer .
Esto se debe a que la implementación
A
puede ser más rápida durante la mayoría de las ejecuciones del benchmark que la implementaciónB
. PeroA
también podría tener una mayor difusión, por lo que el beneficio de rendimiento medidoA
no será de ninguna importancia en comparación conB
.Por lo tanto, también es importante escribir y ejecutar un micro benchmark correctamente, pero también analizarlo correctamente.
fuente
Para agregar al otro excelente consejo, también sería consciente de 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 están sincronizadas dinámicamente, esto puede afectar sus resultados. Por ejemplo, si tiene una aplicación de subproceso único, la velocidad de reloj máxima (con TurboBoost) es mayor que para una aplicación que utiliza todos los núcleos. Por lo tanto, esto puede interferir con las comparaciones del rendimiento de subprocesos simples 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 fundamental sobre el que tiene control directo: ¡asegúrese de medir lo correcto! Por ejemplo, si está utilizando
System.nanoTime()
para comparar un determinado código, coloque las llamadas a la tarea en lugares que tengan sentido para evitar medir cosas que no le interesan. Por ejemplo, no haga:El problema es que no está obteniendo de inmediato la hora de finalización cuando el código ha terminado. En su lugar, intente lo siguiente:
fuente
println
, no una línea de encabezado separada o algo así, ySystem.nanoTime()
debe evaluarse como el primer paso para construir la cadena arg para esa llamada. No hay nada que un compilador pueda hacer con el primero que no pueda hacer con el segundo, y ninguno de los dos lo alienta a hacer un trabajo adicional antes de grabar un tiempo de parada.http://opt.sourceforge.net/ Java Micro Benchmark: tareas de control necesarias para determinar las características comparativas de rendimiento del sistema informático en diferentes plataformas. Se puede utilizar para guiar las decisiones de optimización y comparar diferentes implementaciones de Java.
fuente