Domanda

Il caricamento normale ha la semantica di acquisizione su x86, l'archivio semplice ha la semantica di rilascio, tuttavia il compilatore può ancora riordinare le istruzioni.Sebbene le recinzioni e le istruzioni bloccate (locked xchg, locked cmpxchg) impediscano sia l'hardware che il compilatore di riordinarsi, i carichi semplici e gli archivi sono ancora necessari da proteggere con le barriere del compilatore.Visual C++ fornisce la funzione _ReadWriterBarrier(), che impedisce al compilatore di riordinare, inoltre C++ fornisce la parola chiave volatile per lo stesso motivo.Scrivo tutte queste informazioni solo per assicurarmi di ottenere tutto bene.Quindi tutto quanto scritto sopra è vero, c'è qualche motivo per contrassegnare come volatili le variabili che verranno utilizzate nelle funzioni protette con _ReadWriteBarrier()?

Per esempio:

int load(int& var)
{
    _ReadWriteBarrier();
    T value = var;
    _ReadWriteBarrier();
    return value;
}

È sicuro rendere quella variabile non volatile?Per quanto ho capito lo è, perché la funzione è protetta e il compilatore interno non può eseguire alcun riordino.D'altra parte Visual C++ fornisce un comportamento speciale per le variabili volatili (diverso da quello standard), esegue letture e scritture volatili di carichi e archivi atomici, ma il mio obiettivo è x86 e i carichi e gli archivi semplici dovrebbero essere atomici su x86 comunque, vero?

Grazie in anticipo.

È stato utile?

Soluzione

La parola chiave volatile è disponibile anche in c. "volatile" viene spesso utilizzato nel sistema incorporato, in particolare quando il valore della variabile può modificare in qualsiasi momento - senza alcuna azione prelevata dal codice - tre scenari comuni includono la lettura da un registro periferico mappato da memoria o variabili globali modificati da un routine di servizio di interruzione o quelle all'interno di un programma multi-thread.

Quindi è l'ultimo scenario in cui volatile potrebbe essere considerato simile a _readwritebarrier.

_readwritebarrier non è una funzione - _readwritebarrier non inserisce ulteriori istruzioni e non impedisce alla CPU di riorganizzare letture e scritture: impedisce solo al compilatore di riorganizzarli. _Readwritebarrier è quello di prevenire il riordino del compilatore.

MemoryBarrier è prevenire il riordino della CPU!

Un compilatore in genere riorganizza le istruzioni ... C ++ non contiene supporto integrato per i programmi multithreading in modo che il compilatore presuppone che il codice sia filettato singolo durante il riordino del codice. Con MSVC Uso _READWRITEBARRIER nel codice, in modo che il compilatore non muoverà letture e scrive attraverso di esso.

Controlla questo link per una discussione più dettagliata su quegli argomenti http://msdn.microsoft.com/en- US / Biblioteca / EE418650 (V= VS.85) .aspx

Per quanto riguarda il tuo snippet di codice - non è necessario utilizzare READWRITEBARRIER come primitivo di sincronizzazione - la prima chiamata a _readwritebarrier non è necessaria.

Quando si utilizza ReadWriteBarrier non è necessario utilizzare volatili

Hai scritto "Rende letture volatili e scrive carichi e negozi atomici" - Non penso che sia OK per dire che, atomicity e volatilità sono diversi. Le operazioni atomiche sono considerate indivisibili - ... http://www.yoda. Arachsys.com/csharp/threads/volatility.shtml

Altri suggerimenti

Nota:Non sono un esperto su questo argomento, alcune delle mie affermazioni Sono "quello che ho sentito su internet", ma penso di poter ancora chiarire alcuni malintesi.

[modificare] In generale, farei affidamento su specifiche della piattaforma come le letture atomiche x86 e la mancanza di OOOX solo in ottimizzazioni locali isolate che sono protette da un #ifdef controllando la piattaforma di destinazione, idealmente accompagnata da una soluzione portatile in #else sentiero.

Cose a cui prestare attenzione

  • Atomicità delle operazioni di lettura/scrittura
  • riordino dovuto alle ottimizzazioni del compilatore (questo include un ordine diverso visto da un altro thread a causa della semplice memorizzazione nella cache dei registri)
  • esecuzione fuori ordine nella CPU

Possibili malintesi

1. Per quanto ho capito lo è, perché la funzione è protetta e il compilatore interno non può eseguire alcun riordino.
[modificare] Chiarire:IL _ReadWriteBarrier fornisce protezione contro il riordino delle istruzioni, tuttavia, è necessario guardare oltre l'ambito della funzione. _ReadWriteBarrier è stato corretto in VS 2010 in modo tale che le versioni precedenti potrebbero essere danneggiate (a seconda delle ottimizzazioni effettivamente apportate).

L'ottimizzazione non si limita alle funzioni.Esistono più meccanismi (incorporamento automatico, generazione di timecode di collegamento) che abbracciano funzioni e persino unità di compilazione (e possono fornire ottimizzazioni molto più significative rispetto alla memorizzazione nella cache dei registri con ambito ridotto).

2. Visual C++ [...] effettua letture volatili e scrive carichi e archivi atomici,
Dove l'hai trovato? MSDN dice che oltre lo standard, metterà barriere di memoria attorno alle letture e alle scritture, nessuna garanzia per le letture atomiche.

[modificare] Tieni presente che C#, Java, Delphi ecc.hanno modelli di memoria diversi e possono fornire garanzie diverse.

3. i carichi semplici e gli archivi dovrebbero comunque essere atomici su x86, giusto?
No non lo sono.Le letture non allineate non sono atomiche.Essi capita di essere atomici se sono ben allineati, un fatto su cui non farei affidamento a meno che non sia isolato e facilmente scambiabile.Altrimenti la tua "semplificazione per x86" diventa un blocco per quell'obiettivo.

[modificare] Si verificano letture non allineate:

char * c = new char[sizeof(int)+1];
load(*(int *)c);      // allowed by standard to be unaligned
load(*(int *)(c+1));  // unaligned with most allocators

#pragma pack(push,1)
struct 
{
   char c;
   int  i;
} foo;
load(foo.i);         // caller said so
#pragma pack(pop)

Questo è ovviamente tutto accademico se ricordi che il parametro deve essere allineato e controlli tutto il codice.Non scriverei più un codice del genere, perché spesso sono stato morso dalla pigrizia del passato.

4. Il caricamento semplice ha la semantica di acquisizione su x86, l'archivio semplice ha la semantica di rilascio
NO.I processori x86 non utilizzano l'esecuzione fuori ordine (o meglio, nessun OOOX visibile - credo), ma ciò non impedisce all'ottimizzatore di riordinare le istruzioni.

5. _ReadBarrier / _WriteBarrier / _ReadWriteBarrier Fai tutta la magia che non fanno - impediscono solo il riordino da parte dell'ottimizzatore.MSDN finalmente ce l'ha fatta grosso brutto avvertimento per VS2010, ma a quanto pare le informazioni si applicano a anche le versioni precedenti.


Ora, alla tua domanda.

Presumo che lo scopo dello snippet sia passare qualsiasi variabile N e caricarla (atomicamente?). La scelta semplice sarebbe una lettura interbloccata o (su Visual C++ 2005 e versioni successive) una lettura volatile.

Altrimenti avresti bisogno di una barriera sia per il compilatore che per la CPU prima della lettura, nel salotto di VC++ questo sarebbe:

int load(int& var)
{   
  // force Optimizer to complete all memory writes:
  // (Note that this had issues before VC++ 2010)
   _WriteBarrier();    

  // force CPU to settle all pending read/writes, and not to start new ones:
   MemoryBarrier();

   // now, read.
   int value = var;    
   return value;
}

No, quello _WriteBarrier ha un secondo avviso in MSDN:*Nelle versioni precedenti del compilatore Visual C++, le funzioni _ReadWriteBarrier e _WriteBarrier venivano applicate solo localmente e non influivano sulle funzioni nell'albero delle chiamate.Queste funzioni sono ora applicate a tutto l'albero delle chiamate.*


IO Speranza è corretto.stackoverflowers, correggimi se sbaglio.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top