java concurrency compiler-optimization java-memory-model causality

¿Por qué se permite este comportamiento en el modelo de memoria de Java?



concurrency compiler-optimization (3)

Como se explicó, los únicos valores escritos en x son 0 y 42. Hilo 1:

r3 = x; // here we read either 0 or 42 if (r3 == 0) x = 42; // at this point x is definitely 42 r1 = x;

Por lo tanto, el compilador JIT puede reescribir r1 = x como r1 = 42 , y además y = 42 . El punto es que el hilo 1 siempre, incondicionalmente, escribirá 42 en y . La variable r3 es de hecho redundante y podría eliminarse completamente del código de la máquina. Entonces, el código en el ejemplo solo da la apariencia de una flecha causal de x a y , pero el análisis detallado muestra que de hecho no hay causalidad. La consecuencia sorprendente es que la escritura a y puede ser comprometida temprano.

Una nota general sobre la optimización: supongo que está familiarizado con las penalizaciones de rendimiento que conlleva la lectura de la memoria principal. Es por eso que el compilador JIT está empeñado en negarse a hacerlo siempre que sea posible, y en este ejemplo resulta que, de hecho, no necesita leer x para saber qué escribir en y .

Una nota general sobre la notación: r1 , r2 , r3 son variables locales (pueden estar en la pila o en los registros de la CPU); x , y son variables compartidas (estas están en la memoria principal). Sin tener esto en cuenta, los ejemplos no tendrán sentido.

La causalidad en JMM parece ser la parte más confusa de ella. Tengo algunas preguntas sobre la causalidad de JMM y comportamientos permitidos en programas concurrentes.

Según tengo entendido, el JMM actual siempre prohíbe los bucles de causalidad. (Estoy en lo cierto?)

Ahora, según el documento JSR-133 , página 24, Fig.16, tenemos un ejemplo donde:

Inicialmente x = y = 0

Hilo 1:

r3 = x; if (r3 == 0) x = 42; r1 = x; y = r1;

Hilo 2:

r2 = y; x = r2;

Intuitivamente, r1 = r2 = r3 = 42 parece imposible. Sin embargo, no solo se menciona como posible, sino que también se permite en JMM.

Para la posibilidad, la explicación del documento que no entiendo es:

Un compilador podría determinar que los únicos valores asignados a x son 0 y 42. A partir de eso, el compilador podría deducir que, en el punto donde ejecutamos r1 = x , ya sea que acabamos de realizar una escritura de 42 a x , o acababa de leer x y había visto el valor 42. En cualquier caso, sería legal que una lectura de x viera el valor 42. Entonces, podría cambiar r1 = x a r1 = 42 ; esto permitiría que y = r1 se transforme en y = 42 y se realice antes, dando como resultado el comportamiento en cuestión. En este caso, la escritura a y se confirma primero.

Mi pregunta es, ¿qué tipo de optimización del compilador es realmente? (Soy un compilador ignorante). Ya que 42 se escribe solo de manera condicional, cuando se cumple la sentencia if , ¿cómo puede el compilador decidir ir con la escritura de x ?

En segundo lugar, incluso si el compilador realiza esta optimización especulativa y comete y = 42 y luego finalmente hace r3 = 42 , ¿no es una violación del bucle de causalidad, ya que no hay distinción de causa y efecto ahora?

De hecho, hay un ejemplo en el mismo documento (página 15, Figura 7) donde se menciona que un bucle causal similar es inaceptable.

Entonces, ¿por qué esta orden de ejecución es legal en JMM?


El compilador puede realizar algunos análisis y optimizaciones y terminar con el siguiente código para Thread1:

y=42; // step 1 r3=x; // step 2 x=42; // step 3

Para la ejecución de un solo hilo, este código es equivalente al código original y, por lo tanto, es legal. Luego, si el código de Thread2 se ejecuta entre el paso 1 y el paso 2 (que es bien posible), entonces se asigna 42 también a r3.

La idea general de este ejemplo de código es demostrar la necesidad de una sincronización adecuada.


No vale nada que el javac no optimice el código en un grado significativo. El JIT optimiza el código, pero es bastante conservador al reordenar el código. La CPU puede reordenar la ejecución y esto lo hace, en un grado pequeño, bastante asignado.

Forzar a la CPU a no hacer la optimización del nivel de instrucción es bastante costoso, por ejemplo, puede ralentizarlo en un factor de 10 o más. AFAIK, los diseñadores de Java querían especificar el mínimo de garantías necesarias que funcionaría de manera eficiente en la mayoría de las CPU.