web-dev-qa-db-fra.com

Quelle est la différence entre atomique et critique dans OpenMP?

Quelle est la différence entre atomique et critique dans OpenMP?

Je peux le faire

#pragma omp atomic
g_qCount++;

mais n'est-ce pas pareil que

#pragma omp critical
g_qCount++;

?

98

L'effet sur g_qCount est le même, mais ce qui est fait est différent.

Une section critique OpenMP est tout à fait générale: elle peut entourer n’importe quel bloc de code arbitraire. Cependant, vous payez pour cette généralité en générant des frais généraux importants chaque fois qu'un thread entre et sort de la section critique (en plus du coût inhérent de la sérialisation).

(En outre, dans OpenMP, toutes les sections critiques non nommées sont considérées comme identiques (si vous préférez, il n'y a qu'un verrou pour toutes les sections critiques non nommées), de sorte que si un thread est dans une section critique [non nommée] comme ci-dessus, aucun Section critique [non nommée]. Comme vous pouvez le deviner, vous pouvez contourner ce problème en utilisant des sections critiques nommées).

Une opération atomique a beaucoup moins de frais généraux. Le cas échéant, il tire parti du matériel fournissant une opération d’incrémentation atomique par exemple; dans ce cas, aucun verrou/déverrouillage n’est nécessaire lors de l’entrée/la sortie de la ligne de code, c’est l’incrément atomique auquel le matériel indique que vous ne pouvez pas être dérangé.

Les inconvénients sont que la surcharge est beaucoup plus faible, et qu'un thread étant dans une opération atomique ne bloque aucune opération atomique (différente) sur le point de se produire. L'inconvénient est l'ensemble restreint d'opérations supportées par l'atome.

Bien entendu, dans les deux cas, vous payez le coût de la sérialisation.

158
Jonathan Dursi

Dans OpenMP, toutes les sections critiques non nommées s'excluent mutuellement.

La différence la plus importante entre "critique" et "atomique" est que "atomique" ne peut protéger qu'une seule affectation et que vous pouvez l'utiliser avec des opérateurs spécifiques.

27
Michael

Section critique:

  • Assure la sérialisation des blocs de code.
  • Peut être étendu pour sérialiser des groupes de blocs avec l'utilisation appropriée de la balise "name".

  • Ralentissez!

fonctionnement atomique:

  • C'est beaucoup plus rapide!

  • Assure uniquement la sérialisation d'une opération particulière.

15
efarsarakis

Le moyen le plus rapide n’est ni critique ni atomique. L’ajout avec section critique coûte environ 200 fois plus cher que l’ajout simple, l’ajout atomique coûte 25 fois plus cher que l’ajout simple.

L'option la plus rapide (pas toujours applicable) est de donner à chaque thread son propre compteur et d'effectuer l'opération de réduction lorsque vous avez besoin de la somme totale.

7
Andrii

Les limitations de atomic sont importantes. Ils devraient être détaillés sur le spécifications OpenMP . MSDN propose un aide-mémoire rapide, car je ne serais pas surpris que cela ne change pas. (Visual Studio 2012 a une implémentation OpenMP depuis mars 2002). Pour citer MSDN:

La déclaration d'expression doit avoir l'une des formes suivantes:

x binop = expr

x++

++x

x--

--x

Dans les expressions précédentes: x est une expression lvalue de type scalaire. expr est une expression de type scalaire et ne fait pas référence à l'objet désigné par x. binop n'est pas un opérateur surchargé et est l'un des +, *, -, /, &, ^, |, <<, ou >>.

Je recommande d'utiliser atomic lorsque vous le pouvez et nommé sections critiques autrement. Les nommer est important; vous éviterez ainsi les maux de tête de débogage.

5
darda

Déjà de bonnes explications ici. Cependant, nous pouvons plonger un peu plus loin. Pour comprendre la différence fondamentale entre les concepts atomiques et de la section critique dans OpenMP , nous devons d'abord comprendre le concept de verrouiller . Passons en revue pourquoi nous devons utiliser des verrous .

Un programme parallèle est exécuté par plusieurs threads. Des résultats déterministes se produiront si et seulement si nous effectuons une synchronisation entre ces threads. Bien sûr, la synchronisation entre les threads n'est pas toujours requise. Nous parlons des cas où la synchronisation est nécessaire.

Afin de synchroniser les threads dans un programme multi-threadé, nous allons utiliser verrouiller. Lorsque l'accès doit être restreint par un seul thread à la fois, lock s entre en jeu. La mise en œuvre du concept verrouillé peut varier d'un processeur à l'autre. Voyons comment un verrou simple peut fonctionner d'un point de vue algorithmique.

1. Define a variable called lock.
2. For each thread:
   2.1. Read the lock.
   2.2. If lock == 0, lock = 1 and goto 3    // Try to grab the lock
       Else goto 2.1    // Wait until the lock is released
3. Do something...
4. lock = 0    // Release the lock

L'algorithme donné peut être implémenté dans le langage matériel comme suit. Nous allons utiliser un seul processeur et analyser le comportement des verrous dans ce processus. Pour cette pratique, supposons l’un des processeurs suivants: MIPS , Alpha, ARM ou Puissance.

try:    LW R1, lock
        BNEZ R1, try
        ADDI R1, R1, #1
        SW R1, lock

Ce programme semble être OK, mais ce n'est pas le cas. Le code ci-dessus souffre du problème précédent; synchronisation . Trouvons le problème. Supposons que la valeur initiale de lock soit égale à zéro. Si deux threads exécutent ce code, l’un peut atteindre le SW R1, verrouiller avant que l’autre ne lise le verrou variable. Ainsi, tous deux pensent que le verrou est libre. Pour résoudre ce problème, il existe une autre instruction fournie plutôt que simple [~ # ~] lw [~ # ~] et [~ # ~] sw [~ # ~] . Elle s'appelle Instruction lecture-modification-écriture . Il s’agit d’une instruction complexe (composée de sous-instructions) qui assure la procédure acquisition du verro n’est exécutée que par un thread nique à la fois. La différence de lecture-modification-écriture par rapport à la lecture simple lecture et Ecrire instructions est qu'il utilise une manière différente de Chargement et Stockage . Il utilise [~ # ~] ll [~ # ~] (Load Linked) pour charger la variable lock et [~ # ~] sc [~ # ~] (Store Conditionnel) pour écrire dans la variable lock. Un Link Register supplémentaire est utilisé pour s'assurer que la procédure d'acquisition du verrou est effectuée par un seul thread. L'algorithme est donné ci-dessous.

1. Define a variable called lock.
2. For each thread:
   2.1. Read the lock and put the address of lock variable inside the Link Register.
   2.2. If (lock == 0) and (&lock == Link Register), lock = 1 and reset the Link Register then goto 3    // Try to grab the lock
       Else goto 2.1    // Wait until the lock is released
3. Do something...
4. lock = 0    // Release the lock

Lorsque le registre de liaison est réinitialisé, si un autre thread a supposé que le verrou était libre, il ne pourrait plus écrire la valeur incrémentée dans le verrou. Ainsi, la concurrence d'accès à la variable lock est acquise.

La différence fondamentale entre critique et atomique provient de l'idée que:

Pourquoi utiliser des verrous (une nouvelle variable) alors que nous pouvons utiliser la variable réelle (sur laquelle nous effectuons une opération), en tant que variable de verrouillage?

L'utilisation d'une nouvelle variable pour verrous mènera à section critique, tout en utilisant la variable réelle comme verrou entraînera à atomique concept. La section critique est utile lorsque nous effectuons beaucoup de calculs (plusieurs lignes) sur la variable réelle. En effet, si le résultat de ces calculs ne parvient pas à être écrit sur la variable réelle, la procédure entière doit être répétée pour calculer les résultats. Cela peut entraîner des performances médiocres par rapport à l'attente de la libération du verrou avant d'entrer dans une région hautement calculatoire. Il est donc recommandé d’utiliser la directive atomique chaque fois que vous souhaitez effectuer un seul calcul (x ++, x--, ++ x, --x , etc.) et utiliser une directive critique lorsqu’une région plus complexe en calcul est réalisée par la section intensive.

1
hexpheus