Was ist der Unterschied zwischen atomar und kritisch in OpenMP?


111

Was ist der Unterschied zwischen atomar und kritisch in OpenMP?

Ich kann dies tun

#pragma omp atomic
g_qCount++;

aber ist das nicht dasselbe wie

#pragma omp critical
g_qCount++;

?

Antworten:


173

Der Effekt auf g_qCount ist der gleiche, aber was getan wird, ist anders.

Ein kritischer OpenMP-Abschnitt ist völlig allgemein gehalten - er kann jeden beliebigen Codeblock umgeben. Sie zahlen für diese Allgemeinheit jedoch, indem Sie jedes Mal, wenn ein Thread den kritischen Abschnitt betritt und verlässt, einen erheblichen Overhead verursachen (zusätzlich zu den inhärenten Kosten für die Serialisierung).

(Außerdem werden in OpenMP alle unbenannten kritischen Abschnitte als identisch betrachtet (wenn Sie es vorziehen, gibt es nur eine Sperre für alle unbenannten kritischen Abschnitte), sodass kein Thread in einen [unbenannten] kritischen Abschnitt wie oben eintreten kann, wenn sich ein Thread in einem [unbenannten] kritischen Abschnitt befindet [unbenannter] kritischer Abschnitt. Wie Sie vielleicht erraten haben, können Sie dies umgehen, indem Sie benannte kritische Abschnitte verwenden.

Eine atomare Operation hat einen viel geringeren Overhead. Wo verfügbar, nutzt es die Hardware, die (sagen wir) eine atomare Inkrementierungsoperation bereitstellt; In diesem Fall ist beim Eingeben / Verlassen der Codezeile kein Sperren / Entsperren erforderlich. Es wird lediglich das atomare Inkrement ausgeführt, das laut Hardware nicht gestört werden kann.

Die Vorteile sind, dass der Overhead viel geringer ist und ein Thread, der sich in einer atomaren Operation befindet, keine (unterschiedlichen) atomaren Operationen blockiert, die bevorstehen. Der Nachteil ist die eingeschränkte Anzahl von Operationen, die Atomic unterstützt.

In beiden Fällen fallen natürlich die Kosten für die Serialisierung an.


5
"Sie könnten die Portabilität verlieren" - ich bin mir nicht sicher, ob dies wahr ist. Der Standard (Version 2.0) gibt an, welche atomaren Operationen zulässig sind (im Grunde Dinge wie ++und *=) und dass sie möglicherweise durch criticalAbschnitte ersetzt werden, wenn sie in der Hardware nicht unterstützt werden .
Dan R

@ DanRoche: Ja, du hast ganz recht. Ich glaube nicht, dass diese Aussage jemals richtig war, ich werde sie jetzt korrigieren.
Jonathan Dursi

Vor ein paar Tagen habe ich ein OpenMP-Tutorial befolgt, und soweit ich verstanden habe, gibt es einen Unterschied zwischen den beiden verschiedenen Codes. Das Ergebnis kann unterschiedlich sein, da der kritische Abschnitt sicherstellt, dass die Anweisung jeweils von einem Thread ausgeführt wird. Es ist jedoch möglich, dass die Anweisung: g_qCount = g_qCount + 1; Für Thread 1 wird das Ergebnis g_qCount einfach nur im Schreibpuffer und nicht im RAM-Speicher gespeichert. Wenn Thread 2 den Wert g_qCount abruft, liest er einfach den Wert im RAM und nicht im Schreibpuffer. Atomic Befehl stellt sicher, dass der Befehl die Daten in den Speicher
spült

30

In OpenMP schließen sich alle unbenannten kritischen Abschnitte gegenseitig aus.

Der wichtigste Unterschied zwischen kritisch und atomar besteht darin, dass atomar nur eine einzelne Zuweisung schützen kann und Sie sie mit bestimmten Operatoren verwenden können.


13
Dies wäre besser ein Kommentar (oder eine Bearbeitung) der vorherigen Antwort gewesen.
Kynan

20

Kritischer Abschnitt:

  • Gewährleistet die Serialisierung von Codeblöcken.
  • Kann erweitert werden, um Gruppen von Blöcken unter ordnungsgemäßer Verwendung des Namensnamens zu serialisieren.

  • Langsamer!

Atombetrieb:

  • Ist viel schneller!

  • Gewährleistet nur die Serialisierung eines bestimmten Vorgangs.


9
Aber diese Antwort ist sehr gut lesbar und wäre eine großartige Zusammenfassung der ersten Antwort
Michał Miszczyszyn

7

Der schnellste Weg ist weder kritisch noch atomar. Ungefähr ist die Addition mit kritischem Abschnitt 200-mal teurer als die einfache Addition, die atomare Addition ist 25-mal teurer als die einfache Addition.

Die schnellste Option (nicht immer anwendbar) besteht darin, jedem Thread einen eigenen Zähler zuzuweisen und die Operation zu reduzieren, wenn Sie eine Gesamtsumme benötigen.


2
Ich bin nicht einverstanden mit allen Zahlen, die Sie in Ihrer Erklärung erwähnen. Unter der Annahme von x86_64 hat die atomare Operation einen Overhead von einigen Zyklen (Synchronisieren einer Cache-Zeile) auf die Kosten von ungefähr einem Zyklus. Wenn Sie sonst einen "echten Anteil" hätten, wäre der Overhead gleich Null. Ein kritischer Abschnitt verursacht die Kosten eines Schlosses. Abhängig davon, ob die Sperre bereits aktiviert ist oder nicht, beträgt der Overhead ungefähr 2 atomare Anweisungen ODER zwei Durchläufe des Schedulers und die Ruhezeit - das ist normalerweise deutlich mehr als das 200-fache.
Klaas van Gend

6

Die Einschränkungen von atomicsind wichtig. Sie sollten in den OpenMP-Spezifikationen aufgeführt sein . MSDN bietet einen kurzen Spickzettel an, da ich mich nicht wundern würde, wenn sich dies nicht ändern würde. (Visual Studio 2012 verfügt ab März 2002 über eine OpenMP-Implementierung.) So zitieren Sie MSDN:

Die Ausdrucksanweisung muss eine der folgenden Formen haben:

xbinop =expr

x++

++x

x--

--x

In den vorhergehenden Ausdrücken: xist ein lvalueAusdruck mit Skalartyp. exprist ein Ausdruck mit Skalartyp und verweist nicht auf das durch bezeichnete Objekt x. binop ist nicht überladener Operator und ist eine von +, *, -, /, &, ^, |, <<, oder >>.

Ich empfehle zu verwenden, atomicwenn Sie können und benannte kritische Abschnitte anders. Sie zu benennen ist wichtig; Auf diese Weise vermeiden Sie das Debuggen von Kopfschmerzen.


1
Dies ist nicht alles, wir haben andere erweiterte atomare Anweisungen wie: #pragma omp aromic update (oder lesen, aktualisieren, schreiben, erfassen), so dass wir eine andere nützliche Aussage haben können
Pooria

1

Schon tolle Erklärungen hier. Wir können jedoch etwas tiefer tauchen. Um den Kernunterschied zwischen den Konzepten für atomare und kritische Abschnitte in OpenMP zu verstehen, müssen wir zuerst das Konzept der Sperre verstehen . Lassen Sie uns überprüfen, warum wir Sperren verwenden müssen .

Ein paralleles Programm wird von mehreren Threads ausgeführt. Deterministische Ergebnisse werden nur dann auftreten, wenn wir eine Synchronisation zwischen diesen Threads durchführen. Natürlich ist eine Synchronisation zwischen Threads nicht immer erforderlich. Wir beziehen uns auf die Fälle, in denen eine Synchronisation erforderlich ist.

Um die Threads in einem Multithread-Programm zu synchronisieren , verwenden wir lock . Wenn der Zugriff jeweils nur von einem Thread eingeschränkt werden muss, kommen Sperren ins Spiel. Die Implementierung des Sperrkonzepts kann von Prozessor zu Prozessor variieren. Lassen Sie uns herausfinden, wie eine einfache Sperre aus algorithmischer Sicht funktionieren kann.

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

Der angegebene Algorithmus kann wie folgt in der Hardwaresprache implementiert werden. Wir gehen von einem einzelnen Prozessor aus und analysieren das Verhalten von Sperren darin. Nehmen wir für diese Übung einen der folgenden Prozessoren an: MIPS , Alpha , ARM oder Power .

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

Dieses Programm scheint in Ordnung zu sein, ist es aber nicht. Der obige Code leidet unter dem vorherigen Problem. Synchronisation . Lassen Sie uns das Problem finden. Angenommen, der Anfangswert der Sperre ist Null. Wenn zwei Threads diesen Code ausführen, erreicht einer möglicherweise die SW R1-Sperre, bevor der andere die Sperrvariable liest . Daher denken beide, dass das Schloss frei ist. Um dieses Problem zu lösen, wird anstelle von einfachem LW und SW eine andere Anweisung bereitgestellt . Es heißt Read-Modify-Write- Anweisung. Es ist ein komplexer Befehl (bestehend aus Subanweisungen) , die das sicherstellt Verriegelungserreichungsverfahren nur durch einen erfolgt einzigenThread zu einem Zeitpunkt. Der Unterschied zwischen Lesen, Ändern und Schreiben im Vergleich zu den einfachen Lese- und Schreibanweisungen besteht darin, dass eine andere Art des Ladens und Speicherns verwendet wird . Es verwendet LL (Load Linked) zum Laden der Sperrvariablen und SC (Store Conditional) zum Schreiben in die Sperrvariable. Ein zusätzliches Verbindungsregister wird verwendet, um sicherzustellen, dass die Prozedur der Sperrerfassung von einem einzelnen Thread ausgeführt wird. Der Algorithmus ist unten angegeben.

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

Wenn das Verbindungsregister zurückgesetzt wird und ein anderer Thread angenommen hat, dass die Sperre frei ist, kann er den inkrementierten Wert nicht erneut in die Sperre schreiben. Somit wird die Parallelität des Zugriffs auf die Sperrvariable erfasst.

Der Hauptunterschied zwischen kritisch und atomar ergibt sich aus der Idee, dass:

Warum Sperren (eine neue Variable) verwenden, während wir die tatsächliche Variable (für die wir eine Operation ausführen) als Sperrvariable verwenden können?

Die Verwendung einer neuen Variablen für Sperren führt zu einem kritischen Abschnitt , während die Verwendung der tatsächlichen Variablen als Sperre zu einem atomaren Konzept führt. Der kritische Abschnitt ist nützlich, wenn wir viele Berechnungen (mehr als eine Zeile) für die tatsächliche Variable durchführen. Dies liegt daran, dass, wenn das Ergebnis dieser Berechnungen nicht in die tatsächliche Variable geschrieben werden kann, die gesamte Prozedur wiederholt werden sollte, um die Ergebnisse zu berechnen. Dies kann zu einer schlechten Leistung führen, verglichen mit dem Warten auf die Freigabe der Sperre, bevor ein Bereich mit hoher Rechenleistung betreten wird. Daher wird empfohlen, die atomare Direktive immer dann zu verwenden, wenn Sie eine einzelne Berechnung (x ++, x--, ++ x, --x usw.) ausführen und verwenden möchtenkritische Anweisung, wenn der Intensivabschnitt eine rechnerisch komplexere Region ausführt.


-5

Atomic ist relativ leistungsfähig, wenn Sie den gegenseitigen Ausschluss nur für eine einzelne Anweisung aktivieren müssen.


13
Dies ist nichts weiter als eine schlecht formulierte Wiederholung der akzeptierten Antwort ohne Erklärung.
High Performance Mark

-5

atomic ist ein kritischer Abschnitt für eine einzelne Anweisung, dh Sie sperren die Ausführung einer Anweisung

Der kritische Abschnitt ist eine Sperre für einen Codeblock

Ein guter Compiler übersetzt Ihren zweiten Code genauso wie den ersten


Das ist einfach falsch. Bitte sprechen Sie nicht über Dinge, die Sie nicht verstehen.
jcsahnwaldt Reinstate Monica
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.