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++;
?
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:
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.
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.
Kritischer Abschnitt:
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.
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.
Die Einschränkungen von atomic
sind 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:
x
binop =expr
x++
++x
x--
--x
In den vorhergehenden Ausdrücken:
x
ist einlvalue
Ausdruck mit Skalartyp.expr
ist ein Ausdruck mit Skalartyp und verweist nicht auf das durch bezeichnete Objektx
. binop ist nicht überladener Operator und ist eine von+
,*
,-
,/
,&
,^
,|
,<<
, oder>>
.
Ich empfehle zu verwenden, atomic
wenn Sie können und benannte kritische Abschnitte anders. Sie zu benennen ist wichtig; Auf diese Weise vermeiden Sie das Debuggen von Kopfschmerzen.
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.
Atomic ist relativ leistungsfähig, wenn Sie den gegenseitigen Ausschluss nur für eine einzelne Anweisung aktivieren müssen.
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
++
und*=
) und dass sie möglicherweise durchcritical
Abschnitte ersetzt werden, wenn sie in der Hardware nicht unterstützt werden .