16

La causalità in JMM sembra essere la parte più confusa di tutto ciò. Ho alcune domande relative alla causalità di JMM e comportamenti consentiti nei programmi concorrenti.Perché questo comportamento è consentito nel modello di memoria Java?

Come ho capito, l'attuale JMM proibisce sempre i loop di causalità. (Ho ragione?)

Ora, secondo il documento JSR-133, pagina 24, Fig.16, abbiamo un esempio in cui:

Inizialmente x = y = 0

filettatura 1:

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

filettatura 2:

r2 = y; 
x = r2; 

Intuitivamente, r1 = r2 = r3 = 42 sembra impossibile. Tuttavia, non è solo menzionato come possibile, ma anche "permesso" in JMM.

Per la possibilità, la spiegazione dal documento che non riesco a capire è:

Un compilatore potrebbe determinare che gli unici valori mai assegnati a x sono 0 e 42. Da questo, il compilatore potrebbe dedurre che, nel punto in cui eseguiamo r1 = x, sia avevamo appena eseguita una scrittura di 42 a x, o avevamo appena letto x e visto il valore 42. In entrambi i casi, sarebbe legale per una lettura di x per vedere il valore 42. Potrebbe quindi cambiare r1 = x a r1 = 42; ciò consentirebbe di trasformare y = r1 in y = 42 ed eseguito in precedenza, determinando il comportamento in questione. In questo caso, la scrittura su viene inoltrata per la prima volta a .

La mia domanda è, che tipo di ottimizzazione del compilatore è davvero? (Sono un compilatore-ignorante.) Dal momento che 42 è scritto solo condizionatamente, quando l'istruzione if è soddisfatta, come può il compilatore decidere di andare con la scrittura di x?

In secondo luogo, anche se il compilatore fa questa ottimizzazione speculativa, e si impegna y = 42 e poi finalmente fa r3 = 42, non è una violazione dei loop di causalità, in quanto non v'è alcuna causa ed effetto distinzione sinistra adesso?

In effetti c'è un esempio nello stesso documento (pagina 15, Figura 7) in cui un loop causale simile è menzionato come inaccettabile.

Quindi, come mai questo ordine di esecuzione è legale in JMM?

risposta

6

Come spiegato, gli unici valori mai scritti x sono 0 e 42. filettatura 1:

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

Pertanto il compilatore JIT può riscrivere r1 = x come r1 = 42, e in seguito y = 42.Il punto è, Thread 1 sarà sempre, incondizionatamente write 42 to . La variabile r3 è infatti ridondante e potrebbe essere completamente eliminata dal codice macchina. Quindi il codice nell'esempio fornisce solo l'aspetto di una freccia causale da x a , ma un'analisi dettagliata mostra che non esiste in effetti alcuna causalità. La conseguenza sorprendente è che la scrittura su può essere eseguita in anticipo.

Una nota generale sull'ottimizzazione: suppongo che tu abbia familiarità con le penalità relative alle prestazioni coinvolte nella lettura dalla memoria principale. Questo è il motivo per cui il compilatore JIT è deciso a rifiutarsi di farlo quando possibile, e in questo esempio si scopre che in realtà non ha bisogno di leggere x per sapere cosa scrivere a .

Una nota generale sulla notazione: r1, r2, r3 sono variabili locali (potrebbero essere in pila o in registri della CPU); x, sono variabili condivise (queste sono nella memoria principale). Senza tener conto di ciò, gli esempi non hanno senso.

1

Non vale nulla che lo javac non ottimizzi il codice in misura significativa. Il JIT ottimizza il codice ma è piuttosto prudente nel riordinare il codice. La CPU può riordinare l'esecuzione e lo fa in piccola parte.

Forzare la CPU a non eseguire l'ottimizzazione del livello di istruzione è piuttosto costoso, ad es. può rallentarlo di un fattore 10 o più. AFAIK, i progettisti Java volevano specificare il minimo di garanzie necessarie che avrebbero funzionato in modo efficiente sulla maggior parte delle CPU.

3

compilatore può eseguire alcune analisi e ottimizzazioni e termina con seguente codice per Filettatura1:

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

Per l'esecuzione thread singolo, questo codice è equivalente al codice originale e quindi è legale. Quindi, se il codice di Thread2 viene eseguito tra il passo 1 e il passo 2 (che è ben possibile), allora anche a r3 viene assegnato 42.

L'intera idea di questo esempio di codice è di dimostrare la necessità di una sincronizzazione corretta.

+0

@Alexei Questo spiega un po '. Ma, il compilatore non dovrebbe renderlo 'r3 = 0' invece di' r3 = 42'? O stanno solo mostrando 'una possibilità'! – gaganbm

+2

Il compilatore non rende 'r3 = 42', lascia solo' r3 = x' intatto. L'ottimizzazione del compilatore non viene sempre eseguita alla profondità massima. Se c'è una minima possibilità che l'ottimizzazione possa violare la correttezza, viene abbandonata. Nel codice indicato, non ci sono tali circostanze, ma possono apparire se è presente un altro codice. Inoltre, il compilatore può decidere che 'r3 = 0' ha lo stesso prezzo di' r3 = x'. –