performance java-8 java-11 jmh

performance - El consumo de trazas de la pila es notablemente más lento en Java 11 que en Java 8



java-8 java-11 (2)

Investigué el problema con async-profiler que puede dibujar gráficos de llamas frías demostrando dónde se gasta el tiempo de CPU.

Como señaló @AlekseyShipilev, la desaceleración entre JDK 8 y JDK 9 es principalmente el resultado de los cambios de StackWalker. Además, G1 se ha convertido en el GC predeterminado desde JDK 9. Si establecemos explícitamente -XX:+UseParallelGC (predeterminado en JDK 8), las puntuaciones serán ligeramente mejores.

Pero la parte más interesante es la desaceleración en JDK 11.
Esto es lo que muestra async-profiler (SVG pulsable).

La principal diferencia entre dos perfiles es el tamaño del bloque java_lang_Throwable::get_stack_trace_elements , que está dominado por StringTable::intern . Aparentemente, StringTable::intern toma mucho más tiempo en JDK 11.

Vamos a hacer zoom en:

Tenga en cuenta que StringTable::intern en JDK 11 llama a do_intern que a su vez asigna un nuevo objeto java.lang.String . Parece sospechoso. Nada de este tipo se ve en el perfil JDK 10. Es hora de buscar en el código fuente.

stringTable.cpp (JDK 11)

oop StringTable::intern(Handle string_or_null_h, jchar* name, int len, TRAPS) { // shared table always uses java_lang_String::hash_code unsigned int hash = java_lang_String::hash_code(name, len); oop found_string = StringTable::the_table()->lookup_shared(name, len, hash); if (found_string != NULL) { return found_string; } if (StringTable::_alt_hash) { hash = hash_string(name, len, true); } return StringTable::the_table()->do_intern(string_or_null_h, name, len, | hash, CHECK_NULL); } | ---------------- | v oop StringTable::do_intern(Handle string_or_null_h, const jchar* name, int len, uintx hash, TRAPS) { HandleMark hm(THREAD); // cleanup strings created Handle string_h; if (!string_or_null_h.is_null()) { string_h = string_or_null_h; } else { string_h = java_lang_String::create_from_unicode(name, len, CHECK_NULL); }

La función en JDK 11 primero busca una cadena en la StringTable compartida, no la encuentra, luego va a do_intern e inmediatamente crea un nuevo objeto String.

En las fuentes de JDK 10 después de una llamada a lookup_shared hubo una búsqueda adicional en la tabla principal que devolvió la cadena existente sin la creación de un nuevo objeto:

found_string = the_table()->lookup_in_main_table(index, name, len, hashValue);

Esta refactorización fue el resultado de JDK-8195097 "Hacer posible procesar StringTable fuera de un punto de seguridad".

TL; DR Al internar nombres de métodos en JDK 11, HotSpot crea objetos de cadena redundantes. Esto ha sucedido después de JDK-8195097 .

Estaba comparando el rendimiento de JDK 8 y 11 utilizando jmh 1.21 cuando encontré algunos números sorprendentes:

Java version: 1.8.0_192, vendor: Oracle Corporation Benchmark Mode Cnt Score Error Units MyBenchmark.throwAndConsumeStacktrace avgt 25 21525.584 ± 58.957 ns/op Java version: 9.0.4, vendor: Oracle Corporation Benchmark Mode Cnt Score Error Units MyBenchmark.throwAndConsumeStacktrace avgt 25 28243.899 ± 498.173 ns/op Java version: 10.0.2, vendor: Oracle Corporation Benchmark Mode Cnt Score Error Units MyBenchmark.throwAndConsumeStacktrace avgt 25 28499.736 ± 215.837 ns/op Java version: 11.0.1, vendor: Oracle Corporation Benchmark Mode Cnt Score Error Units MyBenchmark.throwAndConsumeStacktrace avgt 25 48535.766 ± 2175.753 ns/op

OpenJDK 11 y 12 se comportan de forma similar a OracleJDK 11. He omitido sus números por razones de brevedad.

Entiendo que las marcas de microbado no indican el comportamiento de desempeño de las aplicaciones de la vida real. Aun así, tengo curiosidad por saber de dónde viene esta diferencia. ¿Algunas ideas?

Aquí está el punto de referencia en su totalidad:

pom.xml :

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>jmh</groupId> <artifactId>consume-stacktrace</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>JMH benchmark sample: Java</name> <dependencies> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> <version>${jmh.version}</version> </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>${jmh.version}</version> <scope>provided</scope> </dependency> </dependencies> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <jmh.version>1.21</jmh.version> <javac.target>1.8</javac.target> <uberjar.name>benchmarks</uberjar.name> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <version>1.4.1</version> <executions> <execution> <id>enforce-versions</id> <goals> <goal>enforce</goal> </goals> <configuration> <rules> <requireMavenVersion> <version>3.0</version> </requireMavenVersion> </rules> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <compilerVersion>${javac.target}</compilerVersion> <source>${javac.target}</source> <target>${javac.target}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <finalName>${uberjar.name}</finalName> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>org.openjdk.jmh.Main</mainClass> </transformer> </transformers> <filters> <filter> <!-- Shading signed JARs will fail without this. http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar --> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> </execution> </executions> </plugin> </plugins> <pluginManagement> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>2.6.1</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-jar-plugin</artifactId> <version>3.1.0</version> </plugin> <plugin> <artifactId>maven-javadoc-plugin</artifactId> <version>3.0.0</version> </plugin> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.1.0</version> </plugin> <plugin> <artifactId>maven-site-plugin</artifactId> <version>3.7.1</version> </plugin> <plugin> <artifactId>maven-source-plugin</artifactId> <version>3.0.1</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.0</version> </plugin> </plugins> </pluginManagement> </build> </project>

src / main / java / jmh / MyBenchmark.java :

package jmh; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.infra.Blackhole; import java.io.PrintWriter; import java.io.StringWriter; import java.util.concurrent.TimeUnit; @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) public class MyBenchmark { @Benchmark public void throwAndConsumeStacktrace(Blackhole bh) { try { throw new IllegalArgumentException("I love benchmarks"); } catch (IllegalArgumentException e) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); bh.consume(sw.toString()); } } }

Aquí está el script específico de Windows que uso. Debería ser trivial traducirlo a otras plataformas:

set JAVA_HOME=C:/Program Files/Java/jdk1.8.0_192 call mvn -V -Djavac.target=1.8 clean install "%JAVA_HOME%/bin/java" -jar target/benchmarks.jar set JAVA_HOME=C:/Program Files/Java/jdk-9.0.4 call mvn -V -Djavac.target=9 clean install "%JAVA_HOME%/bin/java" -jar target/benchmarks.jar set JAVA_HOME=C:/Program Files/Java/jdk-10.0.2 call mvn -V -Djavac.target=10 clean install "%JAVA_HOME%/bin/java" -jar target/benchmarks.jar set JAVA_HOME=C:/Program Files/Java/oracle-11.0.1 call mvn -V -Djavac.target=11 clean install "%JAVA_HOME%/bin/java" -jar target/benchmarks.jar

Mi entorno de ejecución es:

Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-24T14:41:47-04:00) Maven home: C:/Program Files/apache-maven-3.6.0/bin/.. Default locale: en_CA, platform encoding: Cp1252 OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

Más específicamente, estoy ejecutando Microsoft Windows [Version 10.0.17763.195] .


Sospecho que esto se debe a varios cambios.

La regresión 8-> 9 ocurrió mientras se cambiaba a StackWalker para generar las trazas de pila ( bugs.openjdk.java.net/browse/JDK-8150778 ). Desafortunadamente, esto hizo que el código nativo de la máquina virtual fuera un montón de cadenas, y StringTable se convierte en el cuello de botella. Si realiza el perfil de OP, verá el perfil como en bugs.openjdk.java.net/browse/JDK-8151751 . Debería ser suficiente para perf record -g la JVM completa que ejecuta el punto de referencia, y luego ver el perf report . (Pista, pista, ¡puedes hacerlo tú mismo la próxima vez!)

Y la regresión 10-> 11 debe haber ocurrido más tarde. Sospecho que esto se debe a los preparativos de StringTable para cambiar a una tabla hash totalmente concurrente ( JDK-8195100 , que, como señala Claes, no está completamente en 11) o algo más (¿cambios de clase de intercambio de datos?).

De cualquier manera, internarse en una ruta rápida es una mala idea, y el parche para JDK-8151751 debería haber tratado con ambas regresiones.

Ver este:

8u191: 15108 ± 99 ns / op [hasta ahora todo bien]

- 54.55% 0.37% java libjvm.so [.] JVM_GetStackTraceElement - 54.18% JVM_GetStackTraceElement - 52.22% java_lang_Throwable::get_stack_trace_element - 48.23% java_lang_StackTraceElement::create - 17.82% StringTable::intern - 13.92% StringTable::intern - 4.83% Klass::external_name + 3.41% Method::line_number_from_bci

"cabeza": 22382 ± 134 ns / op [regresión]

- 69.79% 0.05% org.sample.MyBe libjvm.so [.] JVM_InitStackTraceElement - 69.73% JVM_InitStackTraceElementArray - 69.14% java_lang_Throwable::get_stack_trace_elements - 66.86% java_lang_StackTraceElement::fill_in - 38.48% StringTable::intern - 21.81% StringTable::intern - 2.21% Klass::external_name 1.82% Method::line_number_from_bci 0.97% AccessInternal::PostRuntimeDispatch<G1BarrierSet::AccessBarrier<573

Parche "head" + JDK-8151751: 7511 ± 26 ns / op [woot, incluso mejor que 8u]

- 22.53% 0.12% org.sample.MyBe libjvm.so [.] JVM_InitStackTraceElement - 22.40% JVM_InitStackTraceElementArray - 20.25% java_lang_Throwable::get_stack_trace_elements - 12.69% java_lang_StackTraceElement::fill_in + 6.86% Method::line_number_from_bci 2.08% AccessInternal::PostRuntimeDispatch<G1BarrierSet::AccessBarrier 2.24% InstanceKlass::method_with_orig_idnum 1.03% Handle::Handle