Mise à jour de la double opération atomique

En Java, la mise à jour des variables doubles et longues peut ne pas être atomique, les variables double / long étant traitées comme deux variables distinctes de 32 bits.

http://java.sun.com/docs/books/jls/second_edition/html/memory.doc.html#28733

En C ++, si j’utilise un compilateur Intel Processor + Microsoft Visual C ++ 32 bits, la mise à jour de la double opération (8 octets) est-elle atomique?

Je ne trouve pas beaucoup de précisions sur ce comportement.

Quand je dis “variable atomique”, voici ce que je veux dire:

Fil A essayant d’écrire 1 dans la variable x. Le fil B essaye d’écrire 2 dans la variable x.

Nous obtiendrons la valeur 1 ou 2 de la variable x, mais pas une valeur indéfinie.

Ceci est spécifique au matériel et dépend de l’architecture. Pour x86 et x86_64, les écritures ou lectures sur 8 octets sont garanties comme étant atomiques, si elles sont alignées. Citant le livre blanc sur les commandes de mémoire d’architecture Intel:

L’ordre de la mémoire d’Intel 64 garantit que, pour chacune des instructions d’access à la mémoire suivantes, l’opération de mémoire constitutive semble s’exécuter sous la forme d’un access mémoire unique, quel que soit le type de mémoire:

  1. Instructions qui lisent ou écrivent un seul octet.

  2. Instructions qui lisent ou écrivent un mot (2 octets) dont l’adresse est alignée sur une limite de 2 octets.

  3. Instructions qui lisent ou écrivent un double mot (4 octets) dont l’adresse est alignée sur une limite de 4 octets.

  4. Instructions qui lisent ou écrivent un mot-clé (8 octets) dont l’adresse est alignée sur une limite de 8 octets.

Toutes les instructions verrouillées (l’instruction xchg implicitement verrouillée et les autres instructions de lecture-modification-écriture avec un préfixe de locking) constituent une séquence indivisible et ininterruptible de charges suivies de mémoires, quels que soient le type de mémoire et l’alignement.

Il est prudent de supposer que la mise à jour d’un double n’est jamais atomique, même si sa taille est identique à celle d’un int avec garantie atomique. La raison en est que si le chemin de traitement est différent car il s’agit d’un type de données non critique et coûteux. Par exemple, même les barrières de données indiquent généralement qu’elles ne s’appliquent pas aux données / opérations en virgule flottante en général.

Visual C ++ allumera les types primitifs (voir l’ article ) et, bien que cela garantisse que ses bits ne seront pas tronqués lors de l’écriture en mémoire (l’alignement de 8 octets est toujours dans une ligne de cache de 64 ou 128 bits), le rest dépend de la façon dont le processeur traite données atomiques dans son cache et si la lecture / vidage d’une ligne de cache est interrompable. Donc, si vous cherchez dans Intel docs le type de kernel que vous utilisez et que cela vous donne cette garantie, alors vous êtes en sécurité.

Si les spécifications Java sont si conservasortingces, c’est parce qu’elles sont supposées fonctionner de la même manière sur un vieux 386 et sur Corei7. Ce qui est bien sûr délirant mais une promesse est une promesse, donc elle promet moins 🙂

La raison pour laquelle je dis que vous devez consulter la documentation du processeur est que votre processeur peut être un vieux 386, ou similaire :-)) N’oubliez pas que sur un processeur 32 bits, votre bloc de 8 octets prend 2 “. “accéder de manière à être à la merci des mécanismes d’access au cache.

Le vidage de la ligne de cache offrant une garantie de cohérence des données bien supérieure ne s’applique qu’à un CPU raisonnablement récent bénéficiant de la garantie Intel-ian (cohérence de cache automatique).

Je ne pensais pas que dans n’importe quelle architecture, la commutation thread / contexte interromprait la mise à jour d’un registre à mi-chemin, laissant ainsi par exemple 18 bits mis à jour des 32 bits à mettre à jour. Idem pour la mise à jour d’un emplacement mémoire (à condition qu’il s’agisse d’une unité d’access de base de 8,16,32,64 bits, etc.).

Alors, a-t-on répondu à cette question? J’ai couru un programme de test simple en changeant un double:

 #include 

 int main (int argc, char ** argv)
 {
     double i = 3,14159265358979323;
     i + = 84626,433;
 }

Je l’ai compilé sans optimisations (gcc -O0) et toutes les opérations d’affectation sont effectuées avec des instructions d’assembleur uniques telles que fldl .LC0 et faddp %st, %st(1) . ( i += 84626.433 se fait bien entendu en deux opérations, faddp et fstpl ).

Un fil peut-il vraiment être interrompu dans une seule instruction telle que faddp ?

Sur un multicœur, outre le fait d’être atomique, vous devez vous soucier de la cohérence du cache, afin que le thread en lecture voie la nouvelle valeur dans son cache lorsque le rédacteur a été mis à jour.