Instructions SSE: quel CPU peut effectuer des opérations de mémoire atomique 16B?

considère un accès mémoire simple (une seule lecture ou une seule écriture, pas lire+écrire) instruction SSE sur un CPU x86. L'instruction accède à 16 octets (128 bits) de mémoire et l'emplacement de la mémoire accessible est aligné à 16 octets.

le document "Intel® 64 Architecture Memory Ordering White Paper" indique que pour les "Instructions qui lisent ou écrivent un quadword (8 octets) dont l'adresse est alignée sur une limite de 8 octets", l'opération mémoire semble s'exécuter comme suit: un accès à la mémoire unique quel que soit le type de mémoire.

la question: Existe-t-il des processeurs Intel/AMD/etc x86 qui garantissent que la lecture ou l'écriture de 16 octets (128 bits) alignés à une limite de 16 octets s'exécute comme un accès à la mémoire unique? est donc, quel type particulier de CPU est - il (Core2/atome/K8/Phénom/...)? Si vous donnez une réponse (oui / non) à cette question, , veuillez également préciser la méthode qui a été utilisée pour déterminer la réponse. - Recherche de documents PDF, test de force brute, preuve mathématique, ou toute autre méthode que vous avez utilisé pour déterminer la réponse.

cette question se rapporte à des problèmes tels que http://research.swtch.com/2010/02/off-to-races.html


mise à Jour:

j'ai créé un programme de test simple en C que vous pouvez exécuter sur vos ordinateurs. S'il Vous Plaît compiler et exécuter sur votre Phénom, Athlon, Bobcat, Core2, atome, Sandy Bridge ou n'importe quel CPU compatible SSE2 que vous avez. Grâce.

// Compile with:
//   gcc -o a a.c -pthread -msse2 -std=c99 -Wall -O2
//
// Make sure you have at least two physical CPU cores or hyper-threading.

#include <pthread.h>
#include <emmintrin.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

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<16; i++) {
                for (int j=3; j>=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;
}

le CPU que j'ai dans mon carnet est Core Duo (pas Core2). Ce CPU particulier échoue le test, il implémente une mémoire de 16 octets lue / écrite avec une granularité de 8 octets. La sortie est:

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
28
demandé sur Peter Cordes 2011-10-04 13:48:28

6 réponses

Dans le Intel® 64 et IA-32 Architectures Manuel de développement: Vol. 3A , qui contient de nos jours les spécifications du papier blanc de commande de mémoire que vous mentionnez, il est dit dans la section 8.2.3.1, comme vous le constatez vous-même, que

The Intel-64 memory ordering model guarantees that, for each of the following 
memory-access instructions, the constituent memory operation appears to execute 
as a single memory access:

• Instructions that read or write a single byte.
• Instructions that read or write a word (2 bytes) whose address is aligned on a 2
byte boundary.
• Instructions that read or write a doubleword (4 bytes) whose address is aligned
on a 4 byte boundary.
• Instructions that read or write a quadword (8 bytes) whose address is aligned on
an 8 byte boundary.

Any locked instruction (either the XCHG instruction or another read-modify-write
 instruction with a LOCK prefix) appears to execute as an indivisible and 
uninterruptible sequence of load(s) followed by store(s) regardless of alignment.

maintenant, puisque la liste ci-dessus ne contient pas la même langue pour le double mot (16 octets), il s'ensuit que l'architecture ne garantit pas que les instructions qui accèdent à 16 octets de mémoire sont atomiques.

cela dit, le dernier paragraphe fait allusion à une sortie, à savoir l'instruction CMPXCHG16B avec le préfixe de verrouillage. Vous pouvez utiliser L'instruction CPUID pour déterminer si votre processeur supporte CMPXCHG16B (le bit de fonctionnalité "CX16").

dans le document AMD correspondant, AMD64 Technology AMD64 Architecture Programmer's Manual Volume 2: System Programming , Je ne trouve pas de langage clair similaire.

modifier: résultats du programme d'essai

(programme d'essai modifié pour augmenter le nombre d'itérations d'un facteur 10)

Sur un 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

sur Xeon 5150 (32 bits):

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

sur 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

signifie-t-il que Intel et/ou AMD garantissent que les accès mémoire de 16 octets sont atomiques sur ces les machines? À mon humble avis, il ne le fait pas. Ce n'est pas dans la documentation comme comportement architectural garanti, et donc on ne peut pas savoir si sur ces processeurs particuliers 16 octets accès mémoire sont vraiment atomiques ou si le programme de test échoue simplement à les déclencher pour une raison ou une autre. Et donc compter sur elle est dangereux.

EDIT 2: Comment faire échouer le programme de test

Ha! J'ai réussi à faire échouer le programme de test. Sur le même Opteron 2435 que ci-dessus, avec le même binaire, mais maintenant l'exécution via l'outil" numactl "spécifiant que chaque thread fonctionne sur une prise séparée, j'ai eu:

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  Not a single memory access!
1101           0          0
1110           0          0
1111        1366  999994009

qu'est ce que cela implique? Eh bien, L'Opteron 2435 peut, ou ne peut pas, garantir que les accès mémoire de 16 octets sont atomiques pour les accès intra-socket, mais au moins le protocole de cohérence de cache fonctionnant sur L'interconnexion HyperTransport entre les deux sockets ne fournit pas une telle garantie.

EDIT 3: ASM pour le fil de fonctions, à la demande de "GJ."

Voici l'asm généré pour les fonctions de thread pour la version 4.4 x86-64 de GCC utilisée sur le système 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    00000000, %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    00000000, %eax
        jne     .L15
        xorl    %eax, %eax
        ret
        .cfi_endproc

et par souci d'exhaustivité, .LC3 qui est la donnée statique contenant le vecteur (-1, -1, -1, -1) utilisé par 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

notez aussi que c'est la syntaxe AT&T ASM, pas la syntaxe Intel Windows programmeurs pourraient être plus familiers avec. Enfin, c'est avec march=native que GCC préfère MOVAPS; mais cela n'a pas d'importance, si j'utilise march=core2 il utilisera MOVDQA pour stocker vers x, et je peux encore reproduire les échecs.

32
répondu janneb 2011-10-07 13:05:51

Le "AMD Architecture Programmer's Manual Volume 1: Programmation d'Application" , dit dans la section 3.9.1: " CMPXCHG16B peut être utilisé pour effectuer de 16 octets atomique accède en mode 64 bits (avec certaines alignement des restrictions)."

cependant, il n'y a aucun commentaire de ce genre au sujet des instructions relatives à L'ESS. En fait, il y a un commentaire dans la section 4.8.3 que le préfixe de verrouillage "provoque une exception-opcode invalide lorsqu'il est utilisé avec des instructions de média de 128 bits". Il semble donc assez concluant pour moi que les processeurs AMD ne garantissent pas les accès Atomic 128-bit pour les instructions SSE, et la seule façon de faire un accès atomic 128-bit est d'utiliser CMPXCHG16B .

" Intel ® 64 et IA-32 Architectures Software developer's Manual Volume 3A: Guide de Programmation Système, Partie 1 ", dit en 8.1.1 "Un x87 d'instruction ou d'un jeu d'instructions SSE, qui accède à des données plus grand qu'un quadword peut être mis en œuvre à l'aide de plusieurs de mémoire accès."C'est assez concluant que les instructions SSE de 128 bits ne sont pas garanties atomiques par L'ISA. Volume 2A des Docs Intel dit de CMPXCHG16B : "cette instruction peut être utilisée avec un préfixe de serrure pour permettre à l'instruction d'être exécutée atomiquement."

de plus, les fabricants de CPU n'ont pas publié de garanties écrites des opérations Atomic 128b SSE pour des modèles CPU spécifiques lorsque c'est le cas.

4
répondu Anthony Williams 2015-10-10 04:14:24

There is actually a warning in the Intel Architecture Manual Vol 3A. Section 8.1.1 (mai 2011), sous la section des opérations atomiques garanties:

une instruction x87 ou une instruction SSE qui accède à des données plus grandes qu'un quadword peut être implémenté en utilisant plusieurs accès mémoire. Si une telle instruction stocke à la mémoire, certains des accès peuvent complet (écrit à la mémoire) alors que l'autre provoque le fonctionnement de faute de pour des raisons architecturales (par exemple en raison d'une entrée page-table qui est la mention "non présent"). Dans ce cas, les effets de la les accès peuvent être visibles par le logiciel même si l'ensemble l'instruction a causé un défaut. Si L'invalidation du TLB a été retardée (voir Section 4.10.4.4), de telles erreurs de page peuvent se produire même si tous les accès sont à la même page.

donc les instructions SSE ne sont pas garanties d'être atomiques, même si l'architecture sous-jacente utilise un accès mémoire unique (c'est l'une des raisons pour lesquelles l'escrime mémoire a été introduite).

combinez cela avec cette déclaration du manuel D'optimisation Intel, Section 13.3 (avril 2011)""

Les instructions

AVX et FMA n'introduisent pas de nouvelles garanties atomiques les opérations de mémoire.

et que le fait qu'aucune des opérations de chargement ou de stockage pour L'atomicité SIMD ne garantit, nous pouvons en venir à la conclusion que Intel ne supporte aucune forme de SIMD atomique (encore).

en tant que bit supplémentaire, si la mémoire est divisée le long des lignes de cache ou des limites de page (en utilisant des choses comme movdqu qui permettent un accès non aligné), les processeurs suivants Non effectuer des accès atomiques, indépendamment de l'alignement, mais les processeurs plus tard (encore une fois à partir du manuel D'Architecture Intel):

Intel Core 2 Duo, Intel ® Atom™, Intel Core Duo, Pentium M, Processeur Pentium 4, Intel Xeon, P6 family, Pentium, et Intel486 processeurs. Intel Core 2 Duo, Intel Atom, Intel Core Duo, Pentium M, Pentium 4, Intel Xeon, et P6 famille de processeurs

3
répondu Necrolis 2011-10-07 11:02:58

le x86 ISA ne garantit pas l'atomicité pour tout ce qui est plus grand que 8B, de sorte que les implémentations sont libres d'implémenter le support SSE / AVX comme le fait Pentium III / Pentium M / Core Duo: les données internes sont traitées à moitié 64bit. Un magasin de 128 bits est fait comme deux magasins de 64 bits. Le chemin de données vers / depuis le cache ne fait que 64b de large dans la microarchitecture de Yonah (Core Duo). (source: Agner de la Brume microarch doc ).

mises en œuvre plus récentes do ont des chemins de données plus larges en interne, et traitent des instructions 128b comme une seule opération. Core 2 Duo (conroe / merom) a été le premier microarch descendant Intel P6 avec des chemins de données 128b. (IDK à propos de P4, mais heureusement il est assez vieux pour être totalement hors de propos.)

C'est pourquoi L'OP constate que les ops 128b ne sont pas atomiques sur Intel Core Duo (Yonah), mais d'autres posters constatent qu'ils sont atomiques sur des conceptions Intel ultérieures, à commencer par Core 2 (Merom).

les diagrammes sur ce realworldtech writeup sur Merom vs. Yonah montrent le chemin de 128bit entre ALU et L1 data-cache dans Merom (et P4), tandis que la faible puissance Yonah a un chemin de données 64bit. Le chemin de données entre l1 et L2 cache est 256b dans les 3 conceptions.

le saut suivant dans la largeur du chemin de données est venu avec le Haswell D'Intel, avec 256b (32B) AVX/AVX2 charges/magasins , et un chemin 64Byte entre les cache L1 et L2. - Je m'attendre que 256b charges / magasins sont atomiques à Haswell, Broadwell, et Skylake, mais je n'en ai pas à tester. J'oublie si Skylake a de nouveau élargi les chemins en préparation pour AVX512 dans Skylake-EP (la version serveur), ou si peut-être la mise en œuvre initiale D'AVX512 sera comme AVX de SnB/IvB, et avoir 512B charges/magasins occupent un port charge/magasin pour 2 cycles.


comme janneb le souligne dans son excellente réponse expérimentale, le protocole cache-coherency entre les sockets dans un système multi-core peut être plus étroit que ce que vous obtenez dans un CPU de cache de dernier niveau partagé. Il n'y a pas d'exigence architecturale sur l'atomicité pour de larges charges/magasins, de sorte que les concepteurs sont libres de les rendre atomiques dans une socket mais non-atomiques à travers les sockets si cela est pratique. IDK Quelle est la largeur du chemin de données logique inter-socket pour la famille Bulldozer D'AMD, ou pour Intel. (Je dis "logique", parce que même si les données sont transférées en petits morceaux, il pourrait ne pas modifier un cache ligne jusqu'à ce qu'il soit entièrement reçu.)


trouver des articles similaires sur les CPU AMD devrait permettre de tirer des conclusions raisonnables à savoir si les op 128 B sont atomiques ou non. Juste vérifier les tables d'instructions est une aide:

K8 décode movaps reg, [mem] à 2 m-ops, tandis que K10 et bulldozer-famille le décodent à 1 m-op. Le bobcat de faible puissance D'AMD le décode en 2 ops, tandis que jaguar décode 128b movaps en 1 m-op. (Il prend en charge AVX1 similaire à les CPU de la famille bulldozer: 256b les INSN (même les op ALU) sont divisés en deux op 128b. Intel SnB ne divise que 256b charges / magasins, tout en ayant des ALUs de pleine largeur.)

L'Opteron 2435 de janneb est un CPU 6-core Istanbul, qui fait partie de la famille K10 , de sorte que cette conclusion atomique mono-m-op -> semble précise à l'intérieur d'une seule socket.

Intel Silvermont fait 128b charges / magasins avec une seule uop, et un débit d'un par horloge. C'est la même chose que pour les charges entières / magasins, donc il est très probablement atomique.

2
répondu Peter Cordes 2015-10-13 02:53:32

EDIT: Au cours des deux derniers jours, j'ai fait plusieurs tests sur mes trois pc et je n'ai pas reproduit d'erreur de mémoire, donc je ne peux rien dire de plus précis. Peut-être cette erreur de mémoire dépend-elle aussi de L'OS.

EDIT: Je programme en Delphi et pas en C mais je dois comprendre C. Donc j'ai traduit le code, Voici vous avez les procédures de threads où la partie principale est faite en assembleur:

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;

Résultat: pas d'erreur sur mon PC quad core et aucune erreur sur mon dual core PC comme prévu!

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

pouvez-vous montrer comment le débogueur voit votre code de procédure de thread? S'il vous plaît...

0
répondu GJ. 2013-10-19 04:09:16

Beaucoup de réponses ont été publiées jusqu'à présent et donc beaucoup d'informations sont déjà disponibles (comme un effet secondaire beaucoup de confusion trop). J'aimerais trouver des faits tirés du manuel Intel concernant le matériel garanti pour les opérations atomiques ...

dans les derniers processeurs Intel de la famille Nehalem et sandy bridge, la lecture ou l'écriture d'un mot à la limite de 64 bits est garantie.

même non aligné 2, 4 ou 8 octets lit ou les Écritures sont garanties d'être atomiques à condition qu'elles soient en mémoire cache et s'insèrent dans une ligne de cache.

ayant dit que le test affiché dans cette question passe sur le processeur Intel i5 de sandy bridge.

-1
répondu Nitin Kunal 2011-10-10 10:46:43