java - technetwork - Regresión del rendimiento al migrar de jdk1.7.0_25 a jdk1.7.0_40
oracle java (1)
Estoy migrando una aplicación por lotes Spring 3.1.2 de jdk1.7.0_25
a jdk1.7.0_40
tanto x64 como por Oracle.
Al utilizar el sistema OperatingSystemMXBean.getProcessCpuTime()
Sun como una métrica de rendimiento, los resultados muestran una disminución de 2.5x en el rendimiento (es decir, mi aplicación que se ejecuta en u25 es mucho más rápida).
- Por lo que puedo decir, esto no se debe a los changes
java.util.HashMap
yjava.util.ArrayList
changes ya que los resultados son los mismos cuando arranca u40 con las clases HashMap y ArrayList de u25 y estos cambios son simplemente demasiado pequeños para este tipo de diferencia. - Tampoco está relacionado con la regression concurrencia de HashMap, ya que la aplicación es de un solo hilo y la regresión se fijó en u40.
- Las optimizaciones de hotswap tampoco parecen ser el problema, ya que la ejecución con
-Xbatch
y-Xcomp
produce los mismos resultados (asumiendo que la compilación del servidor es la misma entre estos JDK). - Hubo una regression rendimiento con respecto a
java.lang.invoke.MethodHandles
pero eso no parece estar relacionado. A menos que la primavera 3.1.2 haga uso de ellos, de lo cual no pude encontrar evidencia. -
javac
compilación dejavac
parece sin cambios.
Algunas notas generales:
- Este problema aparece en todas las versiones de JDK 7> = u40 (así como en
la versión más reciente de JDK 8jdk1.8.0
), mientras que las versiones <u40 parecen estar bien (incluidas varias versiones de JDK 6). - El código Java antiguo (por ejemplo, ejecutar 1000 * 1000 * 1000 * some_calc) no tiene este problema de rendimiento, lo que significa que, de alguna manera, mi código o las bibliotecas usadas están haciendo algo extraño e inesperado.
- Las pruebas se realizaron utilizando la misma instancia de base de datos (MSSQL 2008 R2), pero no importa.
- Incluso si OperatingSystemMXBean no es confiable, el tiempo en la pared de las pruebas es igual de diferente.
- En ambos casos, parece que GC se inicia al mismo tiempo y por las mismas duraciones (he estado usando
+UseSerialGC
para las pruebas). - La generación de perfiles no muestra puntos de acceso nuevos inusuales, aunque en general muestra un aumento en el tiempo de ejecución en toda la aplicación.
- Probar las versiones x86 de estos Sun JDKs o OpenJDK (los he usado) no cambia el resultado.
- Todo el código probado (excepto cuando se ejecuta en JDK 6) se compiló utilizando
jdk1.7.0_40
. - El mismo escenario se ha probado en dos computadoras diferentes: x64 y x86.
¿Algún consejo o ideas?
Editado para agregar: la estructura de la aplicación es un bucle externo que ejecuta simulaciones de carlos de monte financiero: es decir, muchas fechas, cálculos, etc. Como tal, actualmente es un poco complejo y, estoy de acuerdo, no es ideal para encontrar el problema. Tendré que intentar reducirlo.
Parece que el problema se debe al trabajo realizado en JDK-7133857 , en el que java.lang.Math.pow()
y java.lang.Math.exp()
se intrinsificaron y calcularon utilizando x87.
Estos métodos se utilizan ampliamente (!) En la aplicación perfilada y, por lo tanto, su efecto considerable.
JDK-8029302 describe y soluciona el problema para la potencia de 2 entradas; y la prueba de la aplicación con jdk1.8.0_25
(en la que se solucionó el problema) muestra un rendimiento mejorado, aunque no vuelve al nivel más alto de jdk1.7.0_25
antes de que se realizara la intrinsificación.
Aquí están mis puntos de referencia de JMH y sus resultados para Math.pow()
en las tres versiones relevantes de JDK:
package org.sample;
import org.openjdk.jmh.annotations.*;
import java.lang.*;
public class MyBenchmark {
@State(Scope.Benchmark)
public static class ThreadState {
volatile double x = 0;
volatile double y = 0;
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
public double powx(ThreadState state) {
state.x++;
state.y += 0.5;
return Math.pow(state.x, state.y);
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
public double pow3(ThreadState state) {
state.x++;
return Math.pow(state.x, 3);
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
public double pow2(ThreadState state) {
state.x++;
return Math.pow(state.x, 2);
}
}
Los resultados:
CPU Intel (R) Core (TM) i5-2520M @ 2.50GHz
jdk1.7.0_25 - antes de la intrinsificación
# VM invoker: x:/@sdks/jdks/jdk1.7.0_25/jre/bin/java.exe
...
Result: 4877658.355 (99.9%) 330460.323 ops/s [Average]
Statistics: (min, avg, max) = (1216417.493, 4877658.355, 6421780.276), stdev = 1399189.700
Confidence interval (99.9%): [4547198.032, 5208118.678]
# Run complete. Total time: 00:24:48
Benchmark Mode Samples Score Score error Units
o.s.MyBenchmark.pow2 thrpt 200 40160618.138 1561135.596 ops/s
o.s.MyBenchmark.pow3 thrpt 200 3678800.153 88678.269 ops/s
o.s.MyBenchmark.powx thrpt 200 4877658.355 330460.323 ops/s
jdk1.7.0_40 - intrinsificación
# VM invoker: x:/@sdks/jdks/jdk1.7.0_40/jre/bin/java.exe
...
Result: 1860849.245 (99.9%) 94303.387 ops/s [Average]
Statistics: (min, avg, max) = (418909.582, 1860849.245, 2379936.035), stdev = 399286.444
Confidence interval (99.9%): [1766545.859, 1955152.632]
# Run complete. Total time: 00:24:48
Benchmark Mode Samples Score Score error Units
o.s.MyBenchmark.pow2 thrpt 200 9619333.987 230749.333 ops/s
o.s.MyBenchmark.pow3 thrpt 200 9240043.369 238456.949 ops/s
o.s.MyBenchmark.powx thrpt 200 1860849.245 94303.387 ops/s
jdk1.8.0_25 - intrinsificación fija
# VM invoker: x:/@sdks/jdks/jdk1.8.0_25/jre/bin/java.exe
...
Result: 1898015.057 (99.9%) 92555.236 ops/s [Average]
Statistics: (min, avg, max) = (649562.297, 1898015.057, 2359474.902), stdev = 391884.665
Confidence interval (99.9%): [1805459.821, 1990570.293]
# Run complete. Total time: 00:24:37
Benchmark Mode Samples Score Score error Units
o.s.MyBenchmark.pow2 thrpt 200 81840274.815 1979190.065 ops/s
o.s.MyBenchmark.pow3 thrpt 200 9441518.686 206612.404 ops/s
o.s.MyBenchmark.powx thrpt 200 1898015.057 92555.236 ops/s
Si estoy leyendo esto correctamente, el problema de la potencia de 2 se solucionó en JDK-8029302 y la potencia de> 2 pulgadas (acabo de probar Math.pow(x, 3)
) el rendimiento se mejoró en jdk1.7.0_40
. En cuanto a la extraña Math.pow()s
no int, como se hizo anteriormente en el powx()
pruebas powx()
, parece que todavía hay una regresión de rendimiento considerable (> 3x) al pasar de jdk1.7.0_25
a jdk1.7.0_40
.
Reemplazar Math.pow()
y Math.exp()
con sus métodos respectivos en org.apache.commons.math3.util.FastMath
resuelve completamente el problema con un aumento en el rendimiento. Esta es la solución correcta en lo que a mí respecta .
Nota: Esto habría sido algo más simple si hubiera una manera fácil (es decir, sin el requisito de -XX:-InlineIntrinsics
el JDK) para establecer el -XX:-InlineIntrinsics
.