java multithreading jvm jit jvm-hotspot

java - ¿Hay alguna reordenación de instrucciones realizada por el compilador JIT de punto de acceso que se puede reproducir?



multithreading jvm (1)

Como sabemos, algunos JIT permiten reordenar para la inicialización de objetos, por ejemplo,

someRef = new SomeObject();

se puede descomponer en los siguientes pasos:

objRef = allocate space for SomeObject; //step1 call constructor of SomeObject; //step2 someRef = objRef; //step3

El compilador JIT puede reordenar como sigue:

objRef = allocate space for SomeObject; //step1 someRef = objRef; //step3 call constructor of SomeObject; //step2

a saber, step2 y step3 pueden ser reordenadas por el compilador JIT. Aunque esto es una reordenación teóricamente válida , no pude reproducirla con Hotspot (jdk1.7) bajo la plataforma x86.

Entonces, ¿hay alguna reordenación de instrucciones realizada por el comipler JIT de punto de acceso que se pueda reproducir?

Actualización : hice la test en mi máquina (Linux x86_64, JDK 1.8.0_40, i5-3210M) usando el siguiente comando:

java -XX:-UseCompressedOops -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand="print org.openjdk.jcstress.tests.unsafe.UnsafePublication::publish" -XX:CompileCommand="inline, org.openjdk.jcstress.tests.unsafe.UnsafePublication::publish" -XX:PrintAssemblyOptions=intel -jar tests-custom/target/jcstress.jar -f -1 -t .*UnsafePublication.* -v > log.txt

Y puedo ver que la herramienta reporta algo como:

[1] 5 ACEPTABLE El objeto se publica, al menos 1 campo es visible.

Eso significaba que un hilo de observador vio una instancia no inicializada de MyObject .

Sin embargo, NO vi el código de ensamblaje generado como el de @ Ivan:

0x00007f71d4a15e34: mov r11d,DWORD PTR [rbp+0x10] ;getfield x 0x00007f71d4a15e38: mov DWORD PTR [rax+0x10],r11d ;putfield x00 0x00007f71d4a15e3c: mov DWORD PTR [rax+0x14],r11d ;putfield x01 0x00007f71d4a15e40: mov DWORD PTR [rax+0x18],r11d ;putfield x02 0x00007f71d4a15e44: mov DWORD PTR [rax+0x1c],r11d ;putfield x03 0x00007f71d4a15e48: mov QWORD PTR [rbp+0x18],rax ;putfield o

Parece que no hay compilador reordenando aquí.

Update2 : @Ivan me corrigió. Utilicé un comando JIT incorrecto para capturar el código de ensamblaje. Después de corregir este error, puedo buscar debajo del código de ensamblaje:

0x00007f76012b18d5: mov DWORD PTR [rax+0x10],ebp ;*putfield x00 0x00007f76012b18d8: mov QWORD PTR [r8+0x18],rax ;*putfield o ; - org.openjdk.jcstress.tests.unsafe.generated.UnsafePublication_jcstress$Runner_publish::call@94 (line 156) 0x00007f76012b18dc: mov DWORD PTR [rax+0x1c],ebp ;*putfield x03

Aparentemente, el compilador hizo el reordenamiento que causó una publicación insegura.


Puedes reproducir cualquier compilador reordenando. La pregunta correcta es: qué herramienta usar para esto. Para ver la reordenación del compilador, debe seguir hasta el nivel de ensamblaje con JITWatch (ya que usa la salida del registro de ensamblado de HotSpot) o JMH con LinuxPerfAsmProfiler.

Consideremos el siguiente punto de referencia basado en JMH:

public class ReorderingBench { public int[] array = new int[] {1 , -1, 1, -1}; public int sum = 0; @Benchmark public void reorderGlobal() { int[] a = array; sum += a[1]; sum += a[0]; sum += a[3]; sum += a[2]; } @Benchmark public int reorderLocal() { int[] a = array; int sum = 0; sum += a[1]; sum += a[0]; sum += a[3]; sum += a[2]; return sum; } }

Tenga en cuenta que el acceso a la matriz no está ordenado. En mi máquina, el método con salida de ensamblador global de sum variable es:

mov 0xc(%rcx),%r8d ;*getfield sum ... add 0x14(%r12,%r10,8),%r8d ;add a[1] add 0x10(%r12,%r10,8),%r8d ;add a[0] add 0x1c(%r12,%r10,8),%r8d ;add a[3] add 0x18(%r12,%r10,8),%r8d ;add a[2]

pero para el método con variable local se cambió el patrón de acceso:

mov 0x10(%r12,%r10,8),%edx ;add a[0] <-- 0(0x10) first add 0x14(%r12,%r10,8),%edx ;add a[1] <-- 1(0x14) second add 0x1c(%r12,%r10,8),%edx ;add a[3] add 0x18(%r12,%r10,8),%edx ;add a[2]

Puedes jugar con las optimizaciones del compilador c1 c1_RangeCheckElimination

Actualizar:

Es extremadamente difícil ver solo las reordenaciones del compilador desde el punto de vista del usuario, ya que tiene que ejecutar miles de millones de muestras para detectar el comportamiento delicado. También es importante separar los problemas de compilación y hardware, por ejemplo, el hardware débilmente ordenado como POWER puede cambiar el comportamiento. Comencemos por la herramienta correcta: jcstress : un arnés experimental y un conjunto de pruebas para ayudar a la investigación sobre la corrección del soporte de concurrencia en la JVM, las bibliotecas de clases y el hardware. Here hay un reproductor donde el programador de instrucciones puede decidir emitir algunas tiendas de campo, luego publicar la referencia y luego emitir el resto de las tiendas de campo (también puede leer sobre publicaciones seguras y programación de instrucciones here ). En algunos casos, en mi máquina con Linux x86_64, JDK 1.8.0_60, i5-4300M el compilador genera el siguiente código:

mov %edx,0x10(%rax) ;*putfield x00 mov %edx,0x14(%rax) ;*putfield x01 mov %edx,0x18(%rax) ;*putfield x02 mov %edx,0x1c(%rax) ;*putfield x03 ... movb $0x0,0x0(%r13,%rdx,1) ;*putfield o

pero a veces:

mov %ebp,0x10(%rax) ;*putfield x00 ... mov %rax,0x18(%r10) ;*putfield o <--- publish here mov %ebp,0x1c(%rax) ;*putfield x03 mov %ebp,0x18(%rax) ;*putfield x02 mov %ebp,0x14(%rax) ;*putfield x01

Actualización 2:

Con respecto a la pregunta sobre los beneficios de rendimiento. En nuestro caso, esta optimización (reordenación) no trae beneficios significativos de rendimiento, es solo un efecto secundario de la implementación del compilador. HotSpot usa sea of nodes gráfico sea of nodes para modelar los datos y controlar el flujo (puede leer sobre la representación intermedia basada en gráficos here ). La siguiente imagen muestra el gráfico IR para nuestro ejemplo ( -XX:+PrintIdeal -XX:PrintIdealGraphLevel=1 -XX:PrintIdealGraphFile=graph.xml options + ideal graph visualizer ): donde las entradas a un nodo son entradas a la operación del nodo. Cada nodo define un valor basado en sus entradas y operación, y ese valor está disponible en todos los bordes de salida. Es obvio que el compilador no ve ninguna diferencia entre los nodos de almacenamiento de punteros y enteros, por lo que lo único que lo limita es la barrera de la memoria. Como resultado, para reducir la presión de registro, el tamaño del código objetivo o algo más, el compilador decide programar las instrucciones dentro del bloque básico en este orden extraño (desde el punto de vista del usuario). Puedes jugar con la programación de instrucciones en Hotspot usando las siguientes opciones (disponibles en la compilación fastdebug): -XX:+StressLCM y -XX:+StressGCM .