Instruction de relaxation du processeur et primitives C ++ 11

J’ai remarqué que de nombreux algorithmes sans locking implémentés à l’aide de primitives spécifiques à un système d’exploitation, tels que les verrous de locking décrits ici (qui utilisent des primitives atomiques spécifiques à Linux) utilisent souvent une instruction “cpu relax”. Avec GCC, cela peut être réalisé avec:

asm volatile("pause\n": : :"memory"); 

Plus précisément, cette instruction est souvent utilisée dans le corps de la boucle while en se verrouillant, en attendant qu’une variable soit définie sur une certaine valeur.

C ++ 11 ne semble fournir aucun type d’instruction portable “cpu_relax”. Y a-t-il une raison à cela? Et la déclaration “pause” accomplit-elle quelque chose d’utile?

Modifier:

Aussi, je demanderais: pourquoi le comité de normalisation C ++ 11 n’a-t-il pas décidé d’inclure un générique std::cpu_relax() ou quoi que ce soit d’autre? Est-ce trop difficile de garantir la portabilité?

L’instruction PAUSE est spécifique à x86. Son utilisation exclusive est dans les boucles d’attente à locking rotatif, où:

Améliore les performances des boucles d’attente en attente. Lors de l’exécution d’une «boucle d’attente en attente», les processeurs subissent une perte de performances lors de la sortie de la boucle car ils détectent une possible violation de l’ordre de la mémoire. L’instruction PAUSE indique au processeur que la séquence de code est une boucle d’attente en attente.

Également:

L’insertion d’une instruction de pause dans une boucle spinwait réduit considérablement la consommation d’énergie du processeur.

Où vous mettez cette instruction dans une boucle spin-lock est également spécifique à x86_64. Je ne peux pas parler pour les gens du standard C ++ 11, mais je pense qu’il est raisonnable qu’ils concluent que le bon endroit pour cette magie se trouve dans la bibliothèque appropriée … avec toutes les autres magies requirejses pour implémenter atomics, mutexes, etc. .

NB: la PAUSE ne libère pas le processeur pour permettre à un autre thread de s’exécuter. Ce n’est pas un pthread_yield() “bas niveau”. (Bien que sur les cœurs Intel Hyperthreaded, cela empêche le thread spin-lock de monopoliser le cœur.) La fonction essentielle de PAUSE semble être de désactiver les optimisations habituelles d’exécution d’instruction et de traitement en pipeline, ce qui ralentit le fil (un peu). , mais après avoir découvert que le verrou est occupé, cela réduit la vitesse à laquelle la variable de verrou est touchée, de sorte que le serveur ne bat pas le système de cache pendant que le propriétaire actuel du verrou tente de poursuivre le travail réel.

Notez que les primitives utilisées pour “lancer manuellement” les verrous rotatifs, mutex, etc. ne sont pas spécifiques au système d’exploitation, mais au processeur.

Je ne suis pas sûr que je décrirais un verrou rotatif “roulé à la main” comme étant “sans verrou”!

FWIW, la recommandation d’Intel concernant un verrou verrouillé (” Manuel de référence de l’optimisation des architectures Intel® 64 et IA-32 “) est la suivante:

  Spin_Lock: CMP lockvar, 0 // Check if lock is free. JE Get_lock PAUSE // Short delay. JMP Spin_Lock Get_Lock: MOV EAX, 1 XCHG EAX, lockvar // Try to get lock. CMP EAX, 0 // Test if successful. JNE Spin_Lock 

Clairement, on peut écrire quelque chose qui comstack à cela, en utilisant un std::atomic_flag … ou utiliser pthread_spin_lock() , qui sur ma machine est:

  pthread_spin_lock: lock decl (%rdi) jne wait xor %eax, %eax ret wait: pause cmpl $0, (%rdi) jg pthread_spin_lock jmp wait 

ce qui est difficile à reprocher, vraiment.