web-dev-qa-db-fra.com

coût de fonctionnement atomique

Quel est le coût de l'opération atomique (comparatif et permutable ou addition/décrémentation atomique)? Combien de cycles consomme-t-il? Suspendra-t-il d'autres processeurs sur SMP ou NUMA, ou bloquera-t-il les accès à la mémoire? Videra-t-il le tampon de réorganisation dans le processeur hors service?

Quels seront les effets sur le cache?

Je m'intéresse aux processeurs modernes et populaires: x86, x86_64, PowerPC, SPARC, Itanium.

77
osgx

J'ai cherché des données réelles pour les derniers jours et je n'ai rien trouvé. Cependant, j'ai fait quelques recherches, qui comparent le coût des opérations atomiques avec les coûts des ratés de cache.

Le coût du préfixe x86 LOCK, ou CAS, avant PentiumPro (comme décrit dans la doc), est un accès à la mémoire (comme un cache raté), + l'arrêt des opérations de mémoire par d'autres processeurs, + tout conflit avec d'autres processeurs essayant de VERROUILLER le autobus. Cependant, depuis PentiumPro, pour la mémoire en écriture différée (c'est-à-dire pouvant être mise en cache) (toute la mémoire utilisée par une application, sauf si vous parlez directement avec le matériel), au lieu de bloquer toutes les opérations de mémoire, seule la ligne de cache pertinente est bloquée (sur la base du lien publié ci-dessus).

En fait, le cas CAS peut être plus compliqué, comme expliqué sur cette page , sans temporisation mais avec une description perspicace par un ingénieur de confiance.

Avant d'entrer dans trop de détails, je dirai qu'une opération VERROUILLÉE coûte un échec de cache + le conflit possible avec un autre processeur sur la même ligne de cache, tandis que CAS + la charge précédente (qui est presque toujours requise sauf sur les mutex, où vous avez toujours CAS 0 et 1) peuvent coûter deux échecs de cache.

Il explique qu'une charge + CAS sur un seul emplacement peut en fait coûter deux échecs de cache, comme Load-Linked/Store-Conditional (voir là pour ce dernier). Son explication repose sur la connaissance du protocole de cohérence du cache MESI . Il utilise 4 états pour une ligne de cache: M (odifié), E (xclusif), S (hared), I(nvalid) (et donc il s'appelle MESI), expliqué ci-dessous si nécessaire. Le scénario, expliqué, est le suivant:

  • lOAD provoque un échec de cache - la ligne de cache pertinente est chargée à partir de la mémoire dans l'état partagé (c'est-à-dire que d'autres processeurs sont toujours autorisés à conserver cette ligne de mémoire en mémoire; aucune modification n'est autorisée dans cet état). Si l'emplacement est en mémoire, cette erreur de cache est ignorée. Coût possible: 1 échec de cache. (ignoré si la ligne de cache est à l'état Partagé, Exclusif ou Modifié, c'est-à-dire que les données sont dans le cache L1 de ce CPU).
  • le programme calcule les nouvelles valeurs à stocker,
  • et il exécute une instruction atomique CAS.
    • Il doit éviter les modifications simultanées, il doit donc supprimer les copies de la cacheline du cache des autres CPU, pour déplacer la cacheline à l'état Exclusive. Coût possible: 1 échec de cache. Ceci n'est pas nécessaire s'il est déjà détenu exclusivement, c'est-à-dire à l'état exclusif ou modifié. Dans les deux états, aucun autre processeur ne détient la ligne de cache, mais dans l'état exclusif, elle n'a pas (encore) été modifiée.
    • Après cette communication, la variable est modifiée dans le cache local de notre CPU, auquel cas elle est globalement visible pour tous les autres CPU (car leurs caches sont cohérents avec les nôtres). Il sera éventuellement écrit dans la mémoire principale selon les algorithmes habituels.
    • Les autres processeurs qui tentent de lire ou de modifier cette variable devront d'abord obtenir cette cacheline en mode partagé ou exclusif, et pour ce faire, contacteront ce processeur et recevront la version mise à jour de la cacheline. Une opération VERROUILLÉE, à la place, ne peut coûter qu'un échec de cache (car la ligne de cache sera demandée directement à l'état exclusif).

Dans tous les cas, une demande de mise en cache peut être bloquée par d'autres processeurs modifiant déjà les données.

52
Blaisorblade

J'ai fait du profilage avec la configuration suivante: la machine de test (AMD Athlon64 x2 3800+) a été démarrée, commutée en mode long (interruptions désactivées) et l'instruction d'intérêt a été exécutée en boucle, 100 itérations déroulées et 1000 cycles de boucle. Le corps de la boucle était aligné sur 16 octets. Le temps a été mesuré avec une instruction rdtsc avant et après la boucle. De plus, une boucle factice sans aucune instruction a été exécutée (qui a mesuré 2 cycles par itération de boucle et 14 cycles pour le reste) et le résultat a été soustrait du résultat du temps de profilage de l'instruction.

Les instructions suivantes ont été mesurées:

  • "lock cmpxchg [rsp - 8], rdx "(à la fois avec comparaison et non-concordance),
  • "lock xadd [rsp - 8], rdx ",
  • "lock bts qword ptr [rsp - 8], 1 "

Dans tous les cas, le temps mesuré était d'environ 310 cycles, l'erreur était d'environ +/- 8 cycles

Il s'agit de la valeur pour une exécution répétée sur la même mémoire (mise en cache). Avec un cache supplémentaire manquant, les délais sont considérablement plus élevés. Cela a également été fait avec un seul des 2 cœurs actifs, de sorte que le cache appartenait exclusivement et aucune synchronisation de cache n'était requise.

Pour évaluer le coût d'une instruction verrouillée sur un échec de cache, j'ai ajouté une instruction wbinvld avant l'instruction verrouillée et mis le wbinvld plus un add [rsp - 8], rax dans la boucle de comparaison. Dans les deux cas, le coût était d'environ 80 000 cycles par paire d'instructions! En cas de verrouillage bts, la différence de temps était d'environ 180 cycles par instruction.

Notez qu'il s'agit du débit réciproque, mais comme les opérations verrouillées sont des opérations de sérialisation, il n'y a probablement aucune différence de latence.

Conclusion: une opération verrouillée est lourde, mais un échec de cache peut être beaucoup plus lourd. Aussi: une opération verrouillée n'entraîne pas de manquements au cache. Il peut uniquement provoquer un trafic de synchronisation de cache, lorsqu'une ligne de cache n'appartient pas exclusivement.

Pour démarrer la machine, j'ai utilisé une version x64 de FreeLdr du projet ReactOS. Voici le code source asm:

#define LOOP_COUNT 1000
#define UNROLLED_COUNT 100

PUBLIC ProfileDummy
ProfileDummy:

    cli

    // Get current TSC value into r8
    rdtsc
    mov r8, rdx
    shl r8, 32
    or r8, rax

    mov rcx, LOOP_COUNT
    jmp looper1

.align 16
looper1:

REPEAT UNROLLED_COUNT
    // nothing, or add something to compare against
ENDR

    dec rcx
    jnz looper1

    // Put new TSC minus old TSC into rax
    rdtsc
    shl rdx, 32
    or rax, rdx
    sub rax, r8

    ret

PUBLIC ProfileFunction
ProfileFunction:

    cli

    rdtsc
    mov r8, rdx
    shl r8, 32
    or r8, rax
    mov rcx, LOOP_COUNT

    jmp looper2

.align 16
looper2:

REPEAT UNROLLED_COUNT
    // Put here the code you want to profile
    // make sure it doesn't mess up non-volatiles or r8
    lock bts qword ptr [rsp - 8], 1
ENDR

    dec rcx
    jnz looper2

    rdtsc
    shl rdx, 32
    or rax, rdx
    sub rax, r8

    ret
34
Timo

Sur SMP basé sur bus, le préfixe atomique LOCK affirme (active) un signal de câble de bus LOCK#. Il interdira à d'autres cpus/périphériques sur le bus de l'utiliser.

Livre Ppro & P2 http: //books.google.com/books? Id = 3gDmyIYvFH4C & pg = PA245 & dq = lock + instruction + pentium & lr = & ei = _E61S5ehLI78zQSzrqwI & cd = 1 # v = onepage & q = lock% 20instruction% 20 pages 244-246

Les instructions verrouillées sont les opérations de sérialisation, de synchronisation ..../à propos de l'ordre/RMW verrouillé/lecture-modification-écriture = atomique lui-même/instruction garantit que le processeur exécutera toutes les instructions avant l'instruction verrouillée avant de l'exécuter./à propos des écritures non encore vidées/force toutes les écritures publiées dans le processeur à être vidées dans la mémoire externe avant d'exécuter l'instruction suivante.

/ à propos de SMP/le sémaphore est dans le cache à l'état S ... émission d'une transaction de lecture et d'invalidation pour 0 octet de date (il s'agit d'un kill/de copies partagées de la ligne de cache dans les CPU adjacentes /)

4
osgx