2010-10-27 9 views
8

Ich verwende OpenMP und muss die Funktion fetch-and-add verwenden. OpenMP stellt jedoch keine entsprechende Anweisung/Aufruf bereit. Ich möchte die maximale Portabilität beibehalten, daher möchte ich mich nicht auf Compiler-Intrinsics verlassen.Abrufen und Hinzufügen mithilfe von atomaren OpenMP-Vorgängen

Ich bin eher auf der Suche nach einer Möglichkeit, die atomaren Operationen von OpenMP zu nutzen, um dies zu implementieren, aber ich habe eine Sackgasse erreicht. Kann das überhaupt gemacht werden? N. B., der folgende Code fast tut, was ich will:

#pragma omp atomic 
x += a 

Fast - aber nicht ganz, da ich wirklich den alten Wert von x benötigen. fetch_and_add sollte produzieren definiert werden, um das gleiche Ergebnis wie die folgende (nur nicht-locking): konnte

template <typename T> 
T fetch_and_add(volatile T& value, T increment) { 
    T old; 
    #pragma omp critical 
    { 
     old = value; 
     value += increment; 
    } 
    return old; 
} 

(Eine äquivalente Frage für Vergleichs- und Auslagerungs gefragt, aber man kann in Bezug auf die anderen umgesetzt werden, wenn ich mich nicht irre)

+0

genau das zu sagen, "atomar" ist nicht wirklich, was sein Name zu versprechen scheint, da jeder Thread, dessen Speicher durch einen "atomaren" (auf irgendeinem anderen Thread) geändert wurde, erneut cachen muss. So häufige und wiederholte "atomare" können Ihre Leistung töten (besser Sperren verwenden und Puffer schreiben schreibt). – Walter

+0

@Walter Das ist auch, was ich empirisch gefunden habe: Lock-Free-Algorithmus, der nur auf Augenhöhe mit dem äquivalenten Algorithmus funktioniert, der Sperren verwendet. Und der Lock-Free-Algorithmus verwendet eine weitaus komplexere Synchronisation - nicht in Bezug auf die Leistung, sondern in Bezug auf die Logik (und somit die Möglichkeiten, Fehler einzuführen). –

Antwort

4

Ab openmp 3.1 gibt es Unterstützung für atomare Updates, Sie können entweder den alten Wert oder den neuen Wert erfassen. Da wir den Wert aus dem Speicher holen müssen, um ihn trotzdem zu inkrementieren, macht es nur Sinn, dass wir von einem CPU-Register darauf zugreifen und es in eine thread-private-Variable schreiben können.

Es gibt eine schöne Arbeit-um, wenn Sie gcc (oder g ++) verwenden, Atom builtins nachschlagen: http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html

denken Sie Intel C/C++ Compiler auch Unterstützung für diese hat, aber ich habe nicht versucht, es.

Vorerst (bis openmp 3.1 implementiert ist), habe ich Inline-Wrapper-Funktionen in C++ verwendet, wo können Sie wählen, welche Version bei der Kompilierung zu verwenden:

template <class T> 
inline T my_fetch_add(T *ptr, T val) { 
    #ifdef GCC_EXTENSION 
    return __sync_fetch_and_add(ptr, val); 
    #endif 
    #ifdef OPENMP_3_1 
    T t; 
    #pragma omp atomic capture 
    { t = *ptr; *ptr += val; } 
    return t; 
    #endif 
} 

Update: Ich habe gerade versucht Intel C++ Compiler Derzeit unterstützt es Openmp 3.1 (Atomic Capture ist implementiert).Intel bietet die kostenfreie Nutzung der Compiler unter Linux für nicht-kommerzielle Zwecke:

http://software.intel.com/en-us/articles/non-commercial-software-download/

4,7 GCC OpenMP 3.1 unterstützen wird, wenn es schließlich ... hoffentlich bald veröffentlicht wird :)

+0

Ich habe sowieso auf die Verwendung von GCC-Builds zurückgegriffen, aber das ist natürlich schrecklich für die Interoperabilität. Danke für den OpenMP 3.1 Zeiger. Da VC++ momentan OpenMP 3 nicht unterstützt, ist dies im Moment eher theoretisch. –

+1

Nur für compleynes: es sollte '#ifdef __GNUC__' ...' #elif definiert sein (_OPENMP) und _OPENMP> = 201107' (für OpenMP 3.1) ... '#else #error" Benötigt gcc oder OpenMP> = 3.1 "# endif". Vielen Dank! – eudoxos

2

wenn Sie alten Wert von x und a erhalten mögen nicht geändert wird, verwenden (xa) als alter Wert:.

fetch_and_add(int *x, int a) { 
#pragma omp atomic 
*x += a; 

return (*x-a); 
} 

UPDATE: es war nicht wirklich eine Antwort , weil x nach Atom durch einen anderen Thread modifiziert werden kann. So scheint es unmöglich zu sein, universelles "Fetch-and-add" mit OMP Pragmas zu machen. Als universell meine ich Operation, die von jedem Ort des OMP-Codes leicht verwendet werden kann.

Sie können omp_*_lock Funktionen verwenden, um eine atomics zu simulieren:

typedef struct {omp_lock_t Sperre; int Wert;} atomic_simulated_t;

fetch_and_add(atomic_simulated_t *x, int a) 
{ 
    int ret; 
    omp_set_lock(x->lock); 
    x->value +=a; 
    ret = x->value; 
    omp_unset_lock(x->lock); 
} 

Dies ist hässlich und langsam (tun ein 2-Atom-Ops anstelle von 1). Aber wenn Sie möchten, dass Ihr Code sehr portabel ist, wird er in allen Fällen nicht der schnellste sein.

Sie sagen "wie folgt (nur nicht-verriegeln)". Aber was ist der Unterschied zwischen "non-locking" -Operationen (mit dem Präfix "LOCK" der CPU, LL/SC oder etc) und Locking-Operationen (die selbst mit mehreren atomaren Anweisungen implementiert werden, busy loop für kurze Wartezeit auf Entsperren und OS schlafen für lange Wartezeiten)?

+0

Und für cas - openmp unterstützt eine Variante der bedingten Atom, aber nur in Fortran. Es ist ein MIN und MAX; sie sind bedingt. Kann zum Implementieren einer Teilmenge von CAS-Operationen verwendet werden. – osgx

+0

Duh. Ich fühle mich jetzt irgendwie dumm. –

+0

@Konrad Rudolph, ich auch, weil ich 1 Woche brauche, um das zu bekommen :). Der erforderliche Schritt für mich war auch eine Lern-LL/SC-Operation auf verschiedenen Plattformen. – osgx