Istruzioni SSE: quali CPU possono eseguire operazioni di memoria atomica a 16B?

Considerare un’istruzione SSE a singola memoria (a lettura singola o singola scrittura, non lettura + scrittura) su una CPU x86. L’istruzione accede a 16 byte (128 bit) di memoria e la posizione di memoria accessibile è allineata a 16 byte.

Il documento “White paper di ordinazione della memoria di architettura Intel® 64” afferma che per “Istruzioni che leggono o scrivono una quadricromia (8 byte) il cui indirizzo è allineato su un limite di 8 byte” l’operazione di memoria sembra essere eseguita come accesso di memoria singolo indipendentemente tipo di memoria.

La domanda: Esistono CPU Intel / AMD / etc x86 che garantiscono che la lettura o la scrittura di 16 byte (128 bit) allineati a un limite di 16 byte vengano eseguiti come un singolo accesso di memoria? È così, quale particolare tipo di CPU è (Core2 / Atom / K8 / Phenom / …)? Se si fornisce una risposta (sì / no) a questa domanda, specificare anche il metodo utilizzato per determinare la risposta: ricerca del documento PDF, test forza bruta, prova matematica o qualsiasi altro metodo utilizzato per determinare la risposta.

Questa domanda riguarda problemi come http://research.swtch.com/2010/02/off-to-races.html


Aggiornare:

Ho creato un semplice programma di test in C che puoi eseguire sui tuoi computer. Compila ed esegui il tuo Phenom, Athlon, Bobcat, Core2, Atom, Sandy Bridge o qualsiasi altra CPU compatibile con SSE2 che tu abbia. Grazie.

// Compile with: // gcc -oa ac -pthread -msse2 -std=c99 -Wall -O2 // // Make sure you have at least two physical CPU cores or hyper-threading. #include  #include  #include  #include  #include  typedef int v4si __attribute__ ((vector_size (16))); volatile v4si x; unsigned n1[16] __attribute__((aligned(64))); unsigned n2[16] __attribute__((aligned(64))); void* thread1(void *arg) { for (int i=0; i<100*1000*1000; i++) { int mask = _mm_movemask_ps((__m128)x); n1[mask]++; x = (v4si){0,0,0,0}; } return NULL; } void* thread2(void *arg) { for (int i=0; i<100*1000*1000; i++) { int mask = _mm_movemask_ps((__m128)x); n2[mask]++; x = (v4si){-1,-1,-1,-1}; } return NULL; } int main() { // Check memory alignment if ( (((uintptr_t)&x) & 0x0f) != 0 ) abort(); memset(n1, 0, sizeof(n1)); memset(n2, 0, sizeof(n2)); pthread_t t1, t2; pthread_create(&t1, NULL, thread1, NULL); pthread_create(&t2, NULL, thread2, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); for (unsigned i=0; i=0; j--) printf("%d", (i>>j)&1); printf(" %10u %10u", n1[i], n2[i]); if(i>0 && i<0x0f) { if(n1[i] || n2[i]) printf(" Not a single memory access!"); } printf("\n"); } return 0; } 

La CPU che ho nel mio notebook è Core Duo (non Core2). Questa particolare CPU non supera il test, implementa la lettura / scrittura della memoria a 16 byte con una granularità di 8 byte. L’output è:

 0000 96905702 10512 0001 0 0 0010 0 0 0011 22 12924 Not a single memory access! 0100 0 0 0101 0 0 0110 0 0 0111 0 0 1000 0 0 1001 0 0 1010 0 0 1011 0 0 1100 3092557 1175 Not a single memory access! 1101 0 0 1110 0 0 1111 1719 99975389 

Nel Manuale per gli sviluppatori di architetture Intel® 64 e IA-32: Vol. 3A , che al giorno d’oggi contiene le specifiche del white paper che ordina la memoria che hai citato, è detto nella sezione 8.2.3.1, come ti vedi, che

 Il modello di ordinamento della memoria Intel-64 garantisce che, per ciascuna delle seguenti opzioni 
 istruzioni per l'accesso alla memoria, l'operazione di memoria costituente sembra essere eseguita 
 come un singolo accesso alla memoria:

 • Istruzioni che leggono o scrivono un singolo byte.
 • Istruzioni che leggono o scrivono una parola (2 byte) il cui indirizzo è allineato su un 2
 limite di byte.
 • Istruzioni che leggono o scrivono una doppia parola (4 byte) il cui indirizzo è allineato
 su un limite di 4 byte.
 • Istruzioni che leggono o scrivono una quadrupla (8 byte) il cui indirizzo è allineato
 un limite di 8 byte.

 Qualsiasi istruzione bloccata (l'istruzione XCHG o un'altra lettura-modifica-scrittura
  istruzioni con un prefisso LOCK) sembra eseguirsi come un indivisibile e 
 sequenza di carichi ininterrotta seguita dal / dai negozio, indipendentemente dall'allineamento.

Ora, poiché l’elenco sopra NON contiene la stessa lingua per la doppia quadrupla (16 byte), ne consegue che l’architettura NON garantisce che le istruzioni che accedono a 16 byte di memoria siano atomiche.

Detto questo, l’ultimo paragrafo suggerisce una via d’uscita, ovvero l’istruzione CMPXCHG16B con il prefisso LOCK. È ansible utilizzare l’istruzione CPUID per capire se il processore supporta CMPXCHG16B (il bit di funzione “CX16”).

Nel documento AMD corrispondente, AMD64 Technology Manuale dell’ programmatore dell’architettura AMD64 Volume 2: Programmazione di sistema , Non riesco a trovare un linguaggio chiaro simile.

EDIT: test dei risultati del programma

(Test del programma modificato per aumentare le #iterazioni di un fattore 10)

Su Xeon X3450 (x86-64):

 0000 999998139 1572
 0001 0 0
 0010 0 0
 0011 0 0
 0100 0 0
 0101 0 0
 0110 0 0
 0111 0 0
 1000 0 0
 1001 0 0
 1010 0 0
 1011 0 0
 1100 0 0
 1101 0 0
 1110 0 0
 1111 1861 999998428

Su Xeon 5150 (32 bit):

 0000 999243100 283087
 0001 0 0
 0010 0 0
 0011 0 0
 0100 0 0
 0101 0 0
 0110 0 0
 0111 0 0
 1000 0 0
 1001 0 0
 1010 0 0
 1011 0 0
 1100 0 0
 1101 0 0
 1110 0 0
 1111 756900 999716913

Su un Opteron 2435 (x86-64):

 0000 999995893 1901
 0001 0 0
 0010 0 0
 0011 0 0
 0100 0 0
 0101 0 0
 0110 0 0
 0111 0 0
 1000 0 0
 1001 0 0
 1010 0 0
 1011 0 0
 1100 0 0
 1101 0 0
 1110 0 0
 1111 4107 999998099

Questo significa che Intel e / o AMD garantiscono che gli accessi alla memoria a 16 byte siano atomici su queste macchine? IMHO, non è così. Non è nella documentazione un comportamento architettonico garantito, e quindi non si può sapere se su questi particolari processori gli accessi alla memoria a 16 byte siano realmente atomici o se il programma di test non riesca semplicemente ad triggersrli per un motivo o per un altro. E quindi fare affidamento su di esso è pericoloso.

EDIT 2: Come far fallire il programma di test

Ha! Sono riuscito a far fallire il programma di test. Sullo stesso Opteron 2435 come sopra, con lo stesso binario, ma ora eseguendolo tramite lo strumento “numactl” specificando che ogni thread gira su un socket separato, ho ottenuto:

 0000 999998634 5990
 0001 0 0
 0010 0 0
 0011 0 0
 0100 0 0
 0101 0 0
 0110 0 0
 0111 0 0
 1000 0 0
 1001 0 0
 1010 0 0
 1011 0 0
 1100 0 1 Non un singolo accesso alla memoria!
 1101 0 0
 1110 0 0
 1111 1366 999994009

Quindi cosa implica questo? Bene, Opteron 2435 può, o no, garantire che gli accessi alla memoria a 16 byte siano atomici per gli accessi intra-socket, ma almeno il protocollo di coerenza della cache in esecuzione sull’interconnessione HyperTransport tra i due socket non fornisce tale garanzia.

EDIT 3: ASM per le funzioni del thread, su richiesta di “GJ.”

Ecco l’asm generato per le funzioni di thread per la versione GCC 4.4 x86-64 utilizzata nel sistema Opteron 2435:

 .globl thread2 .type thread2, @function thread2: .LFB537: .cfi_startproc movdqa .LC3(%rip), %xmm1 xorl %eax, %eax .p2align 5,,24 .p2align 3 .L11: movaps x(%rip), %xmm0 incl %eax movaps %xmm1, x(%rip) movmskps %xmm0, %edx movslq %edx, %rdx incl n2(,%rdx,4) cmpl $1000000000, %eax jne .L11 xorl %eax, %eax ret .cfi_endproc .LFE537: .size thread2, .-thread2 .p2align 5,,31 .globl thread1 .type thread1, @function thread1: .LFB536: .cfi_startproc pxor %xmm1, %xmm1 xorl %eax, %eax .p2align 5,,24 .p2align 3 .L15: movaps x(%rip), %xmm0 incl %eax movaps %xmm1, x(%rip) movmskps %xmm0, %edx movslq %edx, %rdx incl n1(,%rdx,4) cmpl $1000000000, %eax jne .L15 xorl %eax, %eax ret .cfi_endproc 

e per completezza, .LC3 che è il dato statico contenente il vettore (-1, -1, -1, -1) utilizzato da thread2:

 .LC3: .long -1 .long -1 .long -1 .long -1 .ident "GCC: (GNU) 4.4.4 20100726 (Red Hat 4.4.4-13)" .section .note.GNU-stack,"",@progbits 

Si noti inoltre che questa è la syntax AT & T ASM, non la syntax Intel di cui i programmatori Windows potrebbero avere maggiore familiarità. Infine, questo è con march = native che rende GCC preferisce MOVAPS; ma non importa, se uso march = core2 userà MOVDQA per la memorizzazione in x, e posso ancora riprodurre i guasti.

Il “Manuale del programmatore di architettura AMD Volume 1: Programmazione dell’applicazione” dice nella sezione 3.9.1: ” CMPXCHG16B può essere utilizzato per eseguire accessi atomici a 16 byte in modalità 64 bit (con alcune restrizioni di allineamento).”

Tuttavia, non vi è alcun commento sulle istruzioni SSE. In effetti, in 4.8.3 c’è un commento sul fatto che il prefisso LOCK “causa un’eccezione di codice non valido quando viene utilizzato con istruzioni di supporto a 128 bit”. Mi sembra quindi abbastanza convincente che i processori AMD NON garantiscano accessi atomici a 128 bit per le istruzioni SSE e l’unico modo per fare un accesso atomico a 128 bit è usare CMPXCHG16B .

Il manuale per gli sviluppatori del software Intel 64 e IA-32 Volume 3A: Guida alla programmazione del sistema, Parte 1 “afferma in 8.1.1″ Un’istruzione x87 o un’istruzione SSE che accede a dati più grandi di una quad può essere implementata utilizzando più accessi di memoria. ” Questo è abbastanza convincente che le istruzioni SSE a 128 bit non siano garantite dall’ISA. Il volume 2A dei documenti Intel dice di CMPXCHG16B : “Questa istruzione può essere utilizzata con un prefisso LOCK per consentire l’esecuzione dell’istruzione atomicamente.”

Inoltre, i produttori di CPU non hanno pubblicato garanzie scritte di operazioni SSE atomiche a 128 bit per specifici modelli di CPU, quando questo è il caso.

C’è in realtà un avvertimento nel Manuale di architettura Intel Vol 3A. Sezione 8.1.1 (maggio 2011), sotto la sezione delle operazioni atomiche garantite:

Un’istruzione x87 o istruzioni SSE che accedono a dati più grandi di una quadrupla possono essere implementate utilizzando più accessi di memoria. Se tale istruzione memorizza in memoria, alcuni degli accessi potrebbero essere completati (scrittura in memoria) mentre un altro causa l’errore dell’operazione per motivi architettonici (ad esempio, a causa di una voce della tabella di pagina contrassegnata come “non presente”). In questo caso, gli effetti degli accessi completati potrebbero essere visibili al software anche se l’istruzione generale ha causato un errore. Se l’invalidazione TLB è stata ritardata (vedere la Sezione 4.10.4.4), tali errori di pagina possono verificarsi anche se tutti gli accessi sono sulla stessa pagina.

quindi le istruzioni SSE non sono garantite per essere atomiche, anche se l’architettura sottostante utilizza un singolo accesso di memoria (questo è uno dei motivi per cui è stata introdotta la scherma della memoria).

Combinalo con questa affermazione del Manuale di ottimizzazione Intel, Sezione 13.3 (aprile 2011)

Le istruzioni AVX e FMA non introducono nuove operazioni di memoria atomica garantite.

e il fatto che nessuna delle operazioni di caricamento o archiviazione per SIMD garantisca l’atomicità, possiamo arrivare alla conclusione che Intel non supporta ancora alcuna forma di SIMD atomico (ancora).

Come un bit in più, se la memoria è divisa lungo linee di cache o limiti di pagina (quando si usano cose come movdqu che consentono l’accesso non allineato), i seguenti processori non eseguiranno accessi atomici, indipendentemente dall’allineamento, ma i processori successivi saranno (di nuovo da Intel Manuale di architettura):

Processori Intel Core 2 Duo, Intel® Atom ™, Intel Core Duo, Pentium M, Pentium 4, Intel Xeon, P6, Pentium e Intel486. I processori Intel Core 2 Duo, Intel Atom, Intel Core Duo, Pentium M, Pentium 4, Intel Xeon e P6

L’ISA x86 non garantisce l’atomicità per qualcosa di più grande di 8B, quindi le implementazioni sono libere di implementare il supporto SSE / AVX come fa Pentium III / Pentium M / Core Duo: i dati interni sono gestiti a metà a 64 bit. Un negozio a 128 bit viene fatto come due negozi a 64 bit. Il percorso dati da / per la cache è largo solo 64b nella microarchitettura Yonah (Core Duo). (fonte: doc microar di Agner Fog ).

Le implementazioni più recenti hanno internamente percorsi di dati più ampi e gestiscono le istruzioni 128b come un singolo op. Core 2 Duo (conroe / merom) è stato il primo microarch discendente Intel P6 con percorsi dati 128b. (IDK su P4, ma per fortuna è abbastanza vecchio per essere totalmente irrilevante.)

Questo è il motivo per cui l’OP rileva che le operazioni a 128 bit non sono atomiche su Intel Core Duo (Yonah), ma altri poster trovano che siano atomici su progetti successivi di Intel, a partire da Core 2 (Merom).

I diagrammi di questo articolo di Realworldtech su Merom vs. Yonah mostrano il percorso a 128 bit tra la cache di dati ALU e L1 in Merom (e P4), mentre Yonah a bassa potenza ha un percorso dati a 64 bit. Il percorso dei dati tra la cache L1 e L2 è 256b in tutti e 3 i progetti.

Il salto successivo nella larghezza del percorso dati è arrivato con Intel Haswell, con carichi / negozi AVX / AVX2 da 256b (32B) e un percorso 64Byte tra la cache L1 e L2. Mi aspetto che 256b carichi / negozi siano atomici in Haswell, Broadwell e Skylake, ma non ne ho uno da testare. Ho dimenticato se Skylake ha allargato nuovamente i percorsi in preparazione per l’AVX512 in Skylake-EP (la versione server), o se forse l’implementazione iniziale di AVX512 sarà come l’AVX di SnB / IvB e avremo 512b carichi / negozi che occupano una porta carico / archivio per 2 cicli.


Come sottolinea janneb nella sua eccellente risposta sperimentale, il protocollo di coerenza della cache tra i socket in un sistema multi-core potrebbe essere più ristretto di quello che si ottiene in una CPU con cache condivisa di ultimo livello. Non vi è alcun requisito di architettura per l’atomicità per larghi carichi / negozi, quindi i progettisti sono liberi di renderli atomici all’interno di un socket ma non atomici tra i socket se questo è conveniente. IDK quanto è ampio il percorso dei dati logici inter-socket per la famiglia Bulldozer di AMD o per Intel. (Dico “logico”, perché anche se i dati vengono trasferiti in blocchi più piccoli, potrebbe non modificare una riga della cache finché non viene completamente ricevuta).


Trovare articoli simili sulle CPU AMD dovrebbe consentire di trarre conclusioni ragionevoli sul fatto che gli ops 128b siano atomici o meno. Basta controllare le tabelle di istruzioni è un aiuto:

K8 decodifica il movaps reg, [mem] in 2 m-ops, mentre K10 e la famiglia di bulldozer lo decodificano a 1 m-op. Il bobcat a bassa potenza di AMD lo decodifica in 2 operazioni, mentre il jaguar decodifica i video 128b in 1 m-op. (Supporta AVX1 simile alle CPU della famiglia bulldozer: 256 insns (anche gli ope ALU) sono divisi in due operandi 128b Intel SnB divide solo 256b carichi / negozi, pur avendo ALU a larghezza piena.)

janneb Opteron 2435 è una CPU Istanbul a 6 core, che fa parte della famiglia K10 , quindi questa conclusione atomica single-m-op -> appare accurata all’interno di un singolo socket.

Intel Silvermont esegue carichi / negozi a 128 b con un singolo uop e un throughput di uno per orologio. Questo è lo stesso dei carichi interi / negozi, quindi è probabilmente atomico.

EDIT: Negli ultimi due giorni ho fatto diversi test sui miei tre PC e non ho riprodotto alcun errore di memoria, quindi non posso dire nulla di più preciso. Forse questo errore di memoria dipende anche dal sistema operativo.

EDIT: Sto programmando in Delphi e non in C ma dovrei capire C. Quindi ho tradotto il codice, qui hai le procedure dei thread in cui la parte principale è fatta in assembler:

 procedure TThread1.Execute; var n :cardinal; const ConstAll0 :array[0..3] of integer =(0,0,0,0); begin for n := 0 to 100000000 do asm movdqa xmm0, dqword [x] movmskps eax, xmm0 inc dword ptr[n1 + eax *4] movdqu xmm0, dqword [ConstAll0] movdqa dqword [x], xmm0 end; end; { TThread2 } procedure TThread2.Execute; var n :cardinal; const ConstAll1 :array[0..3] of integer =(-1,-1,-1,-1); begin for n := 0 to 100000000 do asm movdqa xmm0, dqword [x] movmskps eax, xmm0 inc dword ptr[n2 + eax *4] movdqu xmm0, dqword [ConstAll1] movdqa dqword [x], xmm0 end; end; 

Risultato: nessun errore sul mio PC quad core e nessun errore sul mio PC dual core come previsto!

  1. PC con CPU Intel Pentium4
  2. PC con CPU Intel Core2 Quad Q6600
  3. PC con CPU Intel Core2 Duo P8400

Puoi mostrare come il debuger vede il tuo codice di procedura thread? Per favore…

Molte risposte sono state postate finora e quindi molte informazioni sono già disponibili (come anche molta confusione). Vorrei pubblicare i dati del manuale di Intel relativi alle operazioni atomiche garantite dall’hardware …

Negli ultimi processori Intel della famiglia di ponti nehalem e sabbiosi, è garantita la lettura o la scrittura su una quadricromia allineata a 64 bit.

Anche le letture o le scritture non allineate a 2, 4 o 8 byte sono garantite per essere atomiche, a condizione che siano memorizzate nella cache e si inseriscano in una linea cache.

Detto questo, il test pubblicato in questa domanda passa sul processore intel i5 basato su bridge di sabbia.