La spiegazione dell'ordinamento rilassato è errata in cppreference?


13

Nella documentazione di std::memory_ordersu cppreference.com c'è un esempio di ordinamento semplificato :

Ordinazione rilassata

Le operazioni atomiche contrassegnate memory_order_relaxednon sono operazioni di sincronizzazione; non impongono un ordine tra gli accessi simultanei alla memoria. Garantiscono solo atomicità e coerenza dell'ordine di modifica.

Ad esempio, con xey inizialmente zero,

// Thread 1:
r1 = y.load(std::memory_order_relaxed); // A
x.store(r1, std::memory_order_relaxed); // B
// Thread 2:
r2 = x.load(std::memory_order_relaxed); // C
y.store(42, std::memory_order_relaxed); // D

è consentito produrre r1 == r2 == 42 perché, sebbene A sia in sequenza prima di B all'interno del thread 1 e C sia in sequenza prima di D all'interno del thread 2, nulla impedisce a D di apparire prima di A nell'ordine di modifica di y, e B da appare prima di C nell'ordine di modifica di x. L'effetto collaterale di D su y potrebbe essere visibile al carico A nella filettatura 1 mentre l'effetto collaterale di B su x potrebbe essere visibile al carico C nella filettatura 2. In particolare, ciò può verificarsi se D è completato prima che C in thread 2, dovuto al riordino del compilatore o in fase di esecuzione.

dice "C è sequenziato prima di D all'interno del thread 2".

Secondo la definizione di sequenziato prima, che si trova nell'ordine di valutazione , se A è sequenziato prima di B, la valutazione di A sarà completata prima che inizi la valutazione di B. Poiché C è sequenziato prima di D all'interno del thread 2, C deve essere completato prima dell'inizio di D, quindi la parte della condizione dell'ultima frase dell'istantanea non sarà mai soddisfatta.


La tua domanda è specifica su C ++ 11?
curioso

no, si applica anche a c ++ 14,17. So che sia il compilatore che la CPU possono riordinare C con D. Ma se si verifica il riordino, C non può essere completato prima dell'inizio di D. Quindi penso che ci sia un uso improprio della terminologia nella frase "A è in sequenza prima di B nel thread 1 e C è in sequenza prima di D nel thread 2". È più preciso dire "Nel codice, A è POSIZIONATO PRIMA di B all'interno del thread 1 e C è POSIZIONATO PRIMA di D all'interno del thread 2". Lo scopo di questa domanda è confermare questo pensiero
abigaile

Nulla è definito in termini di "riordino".
curioso

Risposte:


13

Credo che cppreference sia giusto. Penso che questo si riduca alla regola "as-if" [intro.execution] / 1 . I compilatori sono tenuti a riprodurre solo il comportamento osservabile del programma descritto dal tuo codice. Una relazione sequenziata prima viene stabilita solo tra le valutazioni dal punto di vista del thread in cui vengono eseguite queste valutazioni [intro.execution] / 15 . Ciò significa che quando due valutazioni in sequenza una dopo l'altra vengono visualizzate da qualche parte in un thread, il codice effettivamente in esecuzione in quel thread deve comportarsi come se qualunque cosa la prima valutazione abbia effettivamente influito su ciò che fa la seconda valutazione. Per esempio

int x = 0;
x = 42;
std::cout << x;

deve stampare 42. Tuttavia, il compilatore non deve effettivamente archiviare il valore 42 in un oggetto xprima di rileggere il valore da quell'oggetto per stamparlo. Potrebbe anche ricordare che l'ultimo valore da memorizzare xera 42 e quindi semplicemente stampare direttamente il valore 42 prima di fare un vero e proprio archivio del valore da 42 a x. In effetti, se si xtratta di una variabile locale, potrebbe anche tenere traccia del valore assegnato a quella variabile in qualsiasi momento e non creare nemmeno un oggetto o archiviare effettivamente il valore 42. Il thread non è in grado di dire la differenza. Il comportamento sarà sempre come se ci fosse una variabile e come se il valore 42 fosse effettivamente memorizzato in un oggetto x primaessere caricato da quell'oggetto. Ma ciò non significa che il codice macchina generato debba effettivamente archiviare e caricare qualsiasi cosa ovunque. Tutto ciò che serve è che il comportamento osservabile del codice macchina generato sia indistinguibile da come sarebbe il comportamento se tutte queste cose accadessero realmente.

Se guardiamo

r2 = x.load(std::memory_order_relaxed); // C
y.store(42, std::memory_order_relaxed); // D

allora sì, C è sequenziato prima di D. Ma se visto da questo thread in isolamento, nulla che C influenzi il risultato di D. E niente che D faccia cambierebbe il risultato di C. L'unico modo in cui uno potrebbe influenzare l'altro sarebbe come conseguenza indiretta di qualcosa che accade in un altro thread. Tuttavia, specificando std::memory_order_relaxed, hai dichiarato esplicitamenteche l'ordine in cui il carico e l'archivio sono osservati da un altro thread è irrilevante. Poiché nessun altro thread può osservare il carico e memorizzarlo in un ordine particolare, non c'è niente che un altro thread possa fare per fare in modo che C e D si influenzino a vicenda in modo coerente. Pertanto, l'ordine in cui vengono effettivamente eseguiti il ​​carico e l'archivio è irrilevante. Pertanto, il compilatore è libero di riordinarli. E, come menzionato nella spiegazione sotto quell'esempio, se l'archivio da D viene eseguito prima del carico da C, allora r1 == r2 == 42 può effettivamente verificarsi ...


Quindi essenzialmente lo standard afferma che C deve accadere prima di D , ma il compilatore ritiene che non sia possibile dimostrare se C o D si siano verificati successivamente e, a causa della regola as-if, li riordina comunque, giusto?
Fureeish,

4
@Fureeish No. C deve succedere prima di D, per quanto il filo su cui accadono possa dirlo. L'osservazione da un altro contesto potrebbe non essere coerente con tale visione.
Deduplicatore l'

5
@curiousguy Questa affermazione sembra simile ai tuoi altri precedenti evangelismi in C ++ .
Corse di leggerezza in orbita il

1
@curiousguy Michael ha pubblicato una lunga spiegazione insieme a collegamenti ai capitoli pertinenti dello standard.
Corse di leggerezza in orbita

2
@curiousguy Lo standard fa etichetta una delle sue disposizioni "il come-se regola" in una nota: "Questa disposizione è talvolta chiamato il‘come-se’regola" intro.execution
Caleth

1

Talvolta è possibile che un'azione sia ordinata rispetto ad altre due sequenze di azioni, senza implicare alcun ordinamento relativo delle azioni in quelle sequenze l'una rispetto all'altra.

Supponiamo, ad esempio, che uno abbia i seguenti tre eventi:

  • memorizzare da 1 a p1
  • carica p2 in temp
  • conservare da 2 a p3

e la lettura di p2 è ordinata in modo indipendente dopo la scrittura di p1 e prima della scrittura di p3, ma non vi è alcun ordinamento particolare in cui partecipano sia p1 che p3. A seconda di ciò che viene fatto con p2, potrebbe non essere pratico per un compilatore rinviare p1 oltre p3 e ottenere comunque la semantica richiesta con p2. Supponiamo, tuttavia, che il compilatore sapesse che il codice sopra faceva parte di una sequenza più ampia:

  • store 1 to p2 [in sequenza prima del caricamento di p2]
  • [fai quanto sopra]
  • store 3 in p1 [in sequenza dopo l'altro store in p1]

In tal caso, potrebbe determinare che potrebbe riordinare l'archivio in p1 dopo il codice sopra e consolidarlo con il seguente archivio, ottenendo così il codice che scrive p3 senza prima scrivere p1:

  • impostare la temperatura su 1
  • memorizzare la temperatura in p2
  • conservare da 2 a p3
  • memorizzare da 3 a p1

Sebbene possa sembrare che le dipendenze dei dati provochino comportamenti transitori di alcune parti delle relazioni di sequenziamento, un compilatore può identificare situazioni in cui non esistono dipendenze apparenti dei dati e quindi non avrebbe gli effetti transitivi che ci si aspetterebbe.


1

Se ci sono due istruzioni, il compilatore genererà il codice in ordine sequenziale, quindi il codice per il primo verrà inserito prima del secondo. Ma cpus internamente ha condutture e sono in grado di eseguire operazioni di assemblaggio in parallelo. L'istruzione C è un'istruzione di caricamento. Durante il recupero della memoria, la pipeline elaborerà le prossime istruzioni e, dato che non dipendono dalle istruzioni di caricamento, potrebbero finire per essere eseguite prima del completamento di C (ad es. I dati per D erano nella cache, C nella memoria principale).

Se l'utente ha davvero bisogno che le due istruzioni vengano eseguite in sequenza, è possibile utilizzare operazioni di ordinamento della memoria più rigorose. In generale, agli utenti non importa se il programma è logicamente corretto.


-10

Qualunque cosa tu pensi sia ugualmente valida. Lo standard non dice cosa viene eseguito in sequenza, cosa non lo fa e come può essere confuso .

Spetta a te e ad ogni singolo programmatore, creare una semantica coerente in cima a quel casino, un lavoro degno di più dottorati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.