20.01.2013 Aufrufe

OpenMP - PVS - Westfälische Wilhelms-Universität Münster

OpenMP - PVS - Westfälische Wilhelms-Universität Münster

OpenMP - PVS - Westfälische Wilhelms-Universität Münster

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

<strong>OpenMP</strong><br />

Prof. Sergei Gorlatch, Michel Steuwer<br />

Sommersemester 2012<br />

Gruppe <strong>PVS</strong> (Parallele und Verteilte Systeme)<br />

Institut für Informatik<br />

<strong>Westfälische</strong> <strong>Wilhelms</strong>-<strong>Universität</strong> <strong>Münster</strong>


Compiler-unterstützte<br />

Parallelisierung mit <strong>OpenMP</strong><br />

• Bisher betrachtete Ansätze zur Parallelprogrammierung verlangen<br />

vom Benutzer explizite Beschreibung der Parallelität<br />

• Mehrere parallel ablaufende Prozesse, Threads oder Work-items<br />

• Datenpartitionierung zwischen den Prozessen<br />

• Kommunikation<br />

• Synchronisation (Lock/Unlock, Barrier, etc.)<br />

• Dies macht Parallelprogrammierung schwierig und fehleranfällig<br />

• Wir werden heute einen populären Ansatz kennenlernen, wo die Last der<br />

Parallelisierung teilweise vom Compiler übernommen wird:<br />

• <strong>OpenMP</strong> (Open MultiProcessing)<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

2


<strong>OpenMP</strong> ist ...<br />

<strong>OpenMP</strong><br />

• ein Standard zur Multithread-Programmierung auf Multiprozessoren mit<br />

gemeinsamem Speicher<br />

• spezifiziert für C/C++ und Fortran; verfügbar auf verschiedenen<br />

Plattformen, z.B. Unix, Windows, ...<br />

• von einer Gruppe führender Hard- und Software-Hersteller (u. a. AMD, IBM,<br />

Intel, Microsoft, NVIDIA) gemeinsam definiert worden<br />

• Version 1.0 erschien 1997 für Fortran und 1998 für C/C++<br />

• Aktuell ist Version 3.0<br />

• Quelle: http://www.openmp.org<br />

<strong>OpenMP</strong> ist nicht ...<br />

• notwendigerweise von allen Herstellern identisch implementiert<br />

• geeignet für die Programmierung von GPUs (zumindest noch nicht)<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

3


Ziele von <strong>OpenMP</strong><br />

• Standard: einheitlich für verschiedene Architekturen/Plattformen, z.Zt. vor<br />

allem für Multi-core CPUs verwendet<br />

• Kurz und bündig: Wenige einfache Direktiven.<br />

Ursprünglich: nur 2-3 Direktiven vorgesehen<br />

Inzwischen: ziemlich groß geworden<br />

• Einfach und vielseitig:<br />

• Inkrementelles Parallelisieren: Einem sequentiellen Programm werden<br />

schrittweise Direktiven hinzugefügt<br />

• Im Gegensatz dazu: PThreads ist ein „Alles oder Nichts“ Ansatz<br />

• Grob- und feinkörnige Parallelität ist möglich<br />

• Portabilität : Fortran (77,90,95), C, C++<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

4


Programmiermodell:<br />

Eigenschaften<br />

• Parallelität wird ausschließlich über Compiler-Direktiven (sog. Pragmas)<br />

spezifiziert<br />

• Direktiven sind in C/C++ Code eingebettet<br />

• Der Standard unterstützt verschachtelte Parallelität, die wird jedoch nicht<br />

von jeder Implementierung garantiert<br />

• Standard erlaubt dynamische Threads, d. h. die Anzahl benutzter Threads<br />

kann variieren. Auch das wird nicht von jeder Implementierung unterstützt<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

5


# include <br />

int main ( int argc , char * argv []) {<br />

int var1 , var2 , var3 ;<br />

}<br />

/* sequential code */<br />

...<br />

Code-Struktur: Beispiel<br />

/* beginning of parallel section :<br />

fork a team of threads , specify variable scope */<br />

# pragma omp parallel private (var1 , var2 ) shared ( var3 )<br />

{<br />

/* parallel section executed by all threads */<br />

...<br />

} /* all threads join master thread and disband */<br />

/* resume sequential code */<br />

...<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

6


• Mehrere Threads auf gemeinsamem Speicher<br />

• Fork-Join Modell<br />

master<br />

thread<br />

F<br />

O<br />

R<br />

K<br />

Programmiermodell: Illustration<br />

{parallel region}<br />

J F<br />

J<br />

O<br />

O<br />

O<br />

I<br />

R<br />

I<br />

N K<br />

N<br />

{parallel region}<br />

• Der ursprüngliche Master-Thread läuft sequentiell bis zum parallelen Bereich<br />

• Fork: Master-Thread erzeugt ein Team paralleler Threads, die Anweisungen<br />

des parallelen Bereichs parallel verarbeiten<br />

• Join: Synchronisation und Terminierung des Teams am Ende des parallelen<br />

Bereiches. Nur der Master-Thread bleibt übrig<br />

• Programmierer kann volle Kontrolle über Parallelität haben<br />

⇒ Explizites (nicht vollautomatisches) Programmiermodell<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

7


• Format: #pragma omp Direktive [Klausel[[,] Klausel]...]<br />

• #pragma omp: Identifizierung einer <strong>OpenMP</strong>-Direktive<br />

• Direktive: Gültige <strong>OpenMP</strong>-Direktive<br />

• Klausel: Gültige <strong>OpenMP</strong>-Klausel (optional).<br />

Die Reihenfolge mehrerer Klauseln ist beliebig<br />

• Zeilenumbruch am Ende ist zwingend erforderlich<br />

• Generelle Regeln:<br />

Direktiven-Format<br />

• Die Syntax ist case-sensitive<br />

• Nur ein Direktiven-Name je Pragma ist erlaubt (einige Kombinationen sind<br />

zur Abkürzung erlaubt)<br />

• Eine Direktive bezieht sich nur auf die nachfolgende Anweisung, die auch ein<br />

Block sein kann, d. h. ein in { } eingeschlossener Abschnitt im Quellcode<br />

• Zeilen können umgebrochen werden, wenn der Zeilenumbruch mit einem<br />

Backslash („\“) maskiert wird.<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

8


# pragma omp parallel [ Klausel [[ ,] Klausel ]...]<br />

structured_block<br />

Direktive parallel<br />

• Ein paralleler Bereich ist ein Code-Block, der von mehreren Threads<br />

ausgeführt wird<br />

• Master-Thread erzeugt ein Team von Threads, wobei er selbst zum ersten<br />

Thread dieses Teams wird<br />

• Wieviele Threads gestartet werden, d. h. wie groß das Team ist, wird von der<br />

<strong>OpenMP</strong> Implementierung bestimmt<br />

• Alle Threads führen ein Duplikat des folgenden Code-Blocks<br />

(structured_block) aus<br />

• Implizite Synchronisation (Barriere) passiert am Ende des Blockes<br />

• Danach terminieren alle Threads außer dem Master-Thread<br />

• Parallele Bereiche können ineinander verschachtelt werden (sog.<br />

verschachtelte Parallelität)<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

9


Quellcode:<br />

Beispiel: Hello World<br />

int main ( int argc , char * argv []) {<br />

/* fork a team of threads */<br />

# pragma omp parallel<br />

{ /* obtain and print Hello World */<br />

printf (" Hello World from <strong>OpenMP</strong> \n");<br />

} /* threads join master thread and terminate */<br />

}<br />

Compilieren mit: gcc -fopenmp main.c<br />

Ausgabe auf einem Rechner mit 8 logischen Kernen:<br />

Hello World from <strong>OpenMP</strong><br />

Hello World from <strong>OpenMP</strong><br />

Hello World from <strong>OpenMP</strong><br />

Hello World from <strong>OpenMP</strong><br />

Hello World from <strong>OpenMP</strong><br />

Hello World from <strong>OpenMP</strong><br />

Hello World from <strong>OpenMP</strong><br />

Hello World from <strong>OpenMP</strong><br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

10


• Shared-Memory Programmiermodell:<br />

die meisten Variablen sind shared by default<br />

Variablen und Gültigkeitsbereiche<br />

• Private Variablen: z.B. Indizes in Schleifen, Stack-Variablen (lokale Variablen<br />

in Funktionen) sind private by default<br />

Klausel shared(i)<br />

• Auf Variable i kann in jedem Thread des Teams zugegriffen werden<br />

Klausel private(i)<br />

• Eine neue Variable desselben Typs wird in jedem Thread des Teams<br />

deklariert, aber nicht initialisiert<br />

• Alle Referenzen auf die ursprüngliche Variable werden ersetzt durch<br />

Referenzen auf die neuen Variablen<br />

Klausel firstprivate(i)<br />

• Wie private, die neuen Variablen werden aber mit dem Wert der<br />

ursprünglichen Variable initialisiert<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

11


• Arbeitsverteilung = engl. work-sharing<br />

Direktiven zur Arbeitsverteilung<br />

• Idee: Berechnungen von mehreren Bereichen auf ein Thread-Team verteilen<br />

• Es werden keine neuen Threads erzeugt<br />

• Implizite Barriere am Ende des Bereichs<br />

• Klausel nowait hebt implizite Barriere am Ende eines Bereichs auf<br />

• Work-sharing Konstrukte werden nur innerhalb von parallelen Bereichen<br />

(#pragma omp parallel) verwendet<br />

• Work-sharing Konstrukte müssen in allen Threads (oder keinem) eines Teams<br />

und in gleicher Reihenfolge angegeben werden<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

12


F O R K<br />

for<br />

master thread<br />

team<br />

Direktiven zur Arbeitsverteilung:<br />

Illustration<br />

F O R K<br />

F O R K<br />

section team single<br />

J O I N J O I N J O I N<br />

master thread<br />

for:<br />

Iterationen einer Schleife<br />

parallel ausführen<br />

= Datenparallelität<br />

master thread<br />

master thread<br />

sections:<br />

mehrere, parallele<br />

Bereiche, mit einem<br />

Thread pro Bereich<br />

= Taskparallelität<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

13<br />

master thread<br />

team<br />

master thread<br />

single:<br />

Sequentialisierung eines<br />

Code-Bereiches


# pragma omp for [ Klausel [[ ,] Klausel ]...]<br />

for_loop<br />

Direktive for<br />

• Die Schleife, die dieser Direktive direkt folgt, parallel ausführen, d. h.<br />

verschiedene Iterationen gleichzeitig ausführen<br />

• Arbeit verteilen, ohne explizite Datenverteilung<br />

• Wenn nicht innerhalb eines parallelen Bereichs, dann sequentiell<br />

• Eine Klausel schedule beschreibt, wie die Iterationen auf die Threads<br />

aufgeteilt werden (Standard: implementierungsabhängig):<br />

# pragma omp for schedule ( Art [, chunk_size ])<br />

Dabei kann Art static, dynamic, guided, runtime oder auto sein<br />

(siehe nächste Folie)<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

14


Klausel schedule<br />

• static: Iterationen in Blöcke der Größe chunk_size (Standard:<br />

gleichförmig) aufteilen und statisch, d. h. einmal zu Beginn der Schleife, auf<br />

Threads verteilen<br />

• dynamic: Iterationen in Blöcke der Größe chunk_size (Standard: 1) aufteilen<br />

und dynamisch, d. h. zur Laufzeit der Schleife, auf freie Threads verteilen<br />

• guided: Iterationen in Blöcke aufteilen und dynamisch auf freie Threads<br />

verteilen. Dabei exponentielles Reduzieren der Blockgröße bis zu einer Größe<br />

von chunk_size (Standard: 1)<br />

• runtime: Scheduling wird zur Laufzeit bestimmt, dazu muss die<br />

Umgebungsvariable OMP_SCHEDULE als type [,chunk_size] definiert sein,<br />

wobei type static, dynamic oder guided seien muss<br />

• auto: Scheduling wird vom Compiler oder der Laufzeitumgebung gewählt<br />

(ab <strong>OpenMP</strong> 3.0)<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

15


Beispiel: Vektoraddition<br />

• Arrays a,b,c und Variable n global (shared)<br />

• (Schleifen-) Variable i lokal (private) in allen Threads<br />

• Aufteilung in Blöcke der Größe 100 und dynamisch auf Threads verteilen<br />

# include <br />

# define CHUNK 100<br />

# define N 1000<br />

int main ( int argc , char * argv []) {<br />

int i, n, chunk ; float a[N], b[N], c[N];<br />

for (i =0; i < N; i ++)<br />

a[i] = b[i] = i * 1.0;<br />

n = N; chunk = CHUNK ;<br />

# pragma omp parallel shared (a,b,c,n, chunk ) private (i)<br />

{<br />

# pragma omp for schedule ( dynamic , chunk )<br />

for (i =0; i < n; i ++)<br />

c[i] = a[i] + b[i];<br />

} /* end of parallel region */<br />

}<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

16


# pragma omp sections [ Klausel [[ ,] Klausel ]...]<br />

{<br />

[# pragma omp section ]<br />

structured_block<br />

[# pragma omp section ]<br />

structured_block<br />

...<br />

}<br />

Direktive sections<br />

• Enthält mehrere Bereiche, wobei jeder Bereich in genau einem Thread genau<br />

einmal ausgeführt wird<br />

• Bereiche werden mit der section Direktive angegeben<br />

• Sprünge aus/in eine(r) section sind verboten.<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

17


Beispiel für sections<br />

• Taskparallelität: ein Thread führt die Funktion foo, der andere bar<br />

int main ( int argc , char * argv []) {<br />

# pragma omp parallel<br />

{<br />

# pragma omp sections nowait<br />

{<br />

# pragma omp section<br />

foo ();<br />

# pragma omp section<br />

bar ();<br />

} /* end of sections */<br />

} /* end of parallel region */<br />

}<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

18


Syntaktische Abkürzungen<br />

• Paralleler Bereich, der genau ein for oder sections enthält, kann mit<br />

einem statt zwei Pragmas beschrieben werden<br />

• Beispiel: Vektoraddition mit statischem Scheduling<br />

# include <br />

# define CHUNK 100<br />

# define N 1000<br />

int main ( int argc , char * argv []) {<br />

int i, n, chunk ; float a[N], b[N], c[N];<br />

for (i = 0; i < N; i ++)<br />

a[i] = b[i] = i * 1.0;<br />

n = N; chunk = CHUNK ;<br />

# pragma omp parallel for shared (a,b,c,n) private (i) \<br />

schedule ( static , chunk )<br />

for (i = 0; i < n; i ++) c[i] = a[i] + b[i];<br />

}<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

19


Klausel reduction<br />

# pragma omp for reduction ( operator : var1 [[ , var2 ]...])<br />

for_loop<br />

• Reduziert alle Variablen, angegeben in einer Liste<br />

• Jeder Thread erzeugt eine private Kopie aller Variablen der Liste<br />

• Der Operator wird auf alle privaten Kopien einer shared Variable angewendet<br />

und das Resultat in die globale shared Variable geschrieben<br />

• Nur skalare Variablen sind in der Liste erlaubt<br />

• Reduktionsvariablen dürfen nur in einfachen Anweisungen der Form<br />

x++, --x, x += expr, x -= expr, etc. vorkommen<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

20


# include <br />

int main ( int argc , char * argv []) {<br />

int i, n, chunk ;<br />

float a [100] , b [100] , result ;<br />

/* some initializations */<br />

n = 100; chunk = 10; result = 0.0;<br />

}<br />

Beispiel für Reduktion:<br />

Skalarprodukt<br />

for (i = 0, i < n; i ++) {<br />

a[i] = i * 1.0;<br />

b[i] = i * 2.0; }<br />

# pragma omp parallel for default ( shared ) private (i) \<br />

schedule ( static , chunk ) \<br />

reduction (+: result )<br />

for (i = 0; i < n; i ++)<br />

result = result + (a[i] * b[i]);<br />

printf (" Final result = %f\n" , result );<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

21


# pragma omp single [ Klausel [[ ,] Klausel ]...]<br />

structured_block<br />

Direktiven single und master<br />

• Ein Bereich wird von nur einem (beliebigen) Thread ausgeführt, andere<br />

Threads warten an einer impliziten Barriere<br />

Direktive master<br />

# pragma omp master<br />

structured_block<br />

• Ein Bereich wird nur vom Master-Thread ausgeführt, andere Threads<br />

überspringen ihn<br />

• Wie single, aber ohne implizite Barriere<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

22


Synchronisation<br />

Motivierendes Beispiel:<br />

Mehrere Threads modifizieren gleichzeitig eine Variable x (Initialwert = 0)<br />

int main ( int argc , char * argv []) {<br />

int x = 0;<br />

# pragma omp parallel shared (x)<br />

{<br />

x = x + 1;<br />

}<br />

printf ("%d\n", x);<br />

}<br />

• Bei zwei Threads, welcher Wert wird für x ausgegeben? Antwort: 1 oder 2<br />

• Race condition, da die Variable x als shared von allen Threads geteilt wird<br />

• Lösung: Inkrementierung von x synchronisieren.<br />

Es gibt mehrere Direktiven dafür<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

23


# pragma omp critical [( name )]<br />

structured_block<br />

Direktive critical<br />

• Spezifiziert einen Bereich, der immer nur von einem Thread zur selben Zeit<br />

ausgeführt werden darf<br />

• Optionaler Name: critical-Bereiche mit gleichem Namen bzw. alle<br />

unbenannten werden als ein Bereich behandelt<br />

Beispiel: Dequeuing gleicher Task von zwei Threads vermeiden<br />

# pragma omp parallel shared (x,y) private ( x_next , y_next )<br />

{<br />

# pragma omp critical ( xaxis )<br />

x_next = dequeue (x);<br />

work ( x_next );<br />

# pragma omp critical ( yaxis )<br />

y_next = dequeue (y);<br />

work ( y_next ); } /* end of parallel region */<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

24


# pragma omp atomic<br />

statement<br />

Direktive atomic<br />

• Spezifiziert, dass ein Speicherbereich nur atomar geändert werden darf<br />

• Bezieht sich nur auf einen einfachen nachfolgenden Befehl<br />

• Ein atomarer Befehl darf folgende Formen haben:<br />

x++, ++x, x–, –x, x binop= expr<br />

• x: Skalare Variable<br />

• expr: skalarer Ausdruck, der keine Referenz auf x benutzt<br />

• binop: einer der Operatoren (nicht überladen) +,*,-,/,&,^,|,>>,


Beispiel: Race conditions vermeiden<br />

# pragma omp parallel for shared (x,y,index ,n)<br />

for (i = 0; i < n; i ++) {<br />

x[i] += work1 (i);<br />

# pragma omp atomic<br />

y[ index [i]] += work2 (i);<br />

}<br />

Beispiel für Direktive atomic<br />

• Der Zugriff auf x muss nicht geschützt werden, da in jeder Iterationen<br />

unterschiedliche Elemente von x zugegriffen wird<br />

• Der Zugriff auf y muss geschützt werden, da durch index[i] nicht mehr<br />

garantiert werden kann, dass in jeder Iteration auf unterschiedliche Elemente<br />

von y zugegriffen wird<br />

• In Vergleich zu critical können hier verschiedene Elemente von y<br />

gleichzeitig geändert werden<br />

• Beachte: Nur lesen und schreiben von y ist atomar, Berechnung in work2<br />

ist nicht atomar<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

26


Task-Parallelität<br />

(ab <strong>OpenMP</strong> 3.0)<br />

• Jedes, mit #omp parallel, erzeugtes Thread-Team besitzt eigenen Pool von<br />

Tasks, die von einem beliebigen Thread ausgeführt werden können<br />

• Erlaubt die Parallelisierung einer größeren Menge von Anwendungen<br />

Motivierendes Beispiel: Verkettete Liste<br />

while ( elem != NULL ) {<br />

work ( elem );<br />

elem = elem -> next ;<br />

} /* end of loop */<br />

• Direkte Parallelisierung nicht möglich, stattdessen Lösung mit for:<br />

1 Listenelemente zählen<br />

2 Zeiger auf Anfänge von Iterationsblöcken speichern<br />

3 while-Schleife in for-Schleife transformieren<br />

• Besserer Lösung mit der Direktive task<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

27


# pragma omp task [ Klausel [[ ,] Klausel ]...]<br />

structured_block<br />

Direktive task<br />

• Spezifiziert einen Bereich (Task), der von nur einem Thread ausgeführt wird<br />

(ähnlich zu section)<br />

• An einem task scheduling point kann ein Thread die Ausführung des<br />

aktuellen Tasks unterbrechen und zu einem anderen Task wechseln<br />

• Ein task scheduling point wird implizit von #pragma omp task, sowie am Ende<br />

eines parallelen Bereichs erzeugt<br />

• Wird als Klausel untied angegeben, so kann der Task an einem task<br />

scheduling point von einem anderen Thread ausgeführt werden<br />

• Sprünge aus/in einen task sind verboten<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

28


Beispiel: Verkettete Liste<br />

elem = head ;<br />

# pragma omp parallel<br />

{<br />

# pragma omp single nowait<br />

{<br />

while ( elem != NULL ) {<br />

# pragma omp task firstprivate ( elem ) /* create one task */<br />

{ work ( elem ); } /* per list element */<br />

elem = elem -> next ;<br />

} /* end of while loop */<br />

} /* end of single , no implied barrier */<br />

} /* end of parallel region , implicit task scheduling point ,<br />

wait until all tasks are finished */<br />

• Ein Thread erzeugt alle Tasks<br />

• Alle Task warten am Ende des parallelen Bereichs und bearbeiten die<br />

erzeugten Tasks<br />

• Durch nowait warten die anderen Threads nicht am Ende des single<br />

Blocks und beginnen zu arbeiten, sobald der erste Task erzeugt wird<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

29


# pragma omp taskwait<br />

Direktive taskwait<br />

• Wartet auf die Beendigung aller vom aktuellen Task bisher erzeugten<br />

Kind-Tasks<br />

• Die taskwait Direktive erzeugt einen impliziten task scheduling point, an<br />

dem ein Taskwechsel stattfinden kann<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

30


* basic algorithm : f(n) = f(n -1) + f(n -2) */<br />

long comp_fib_number ( int n) {<br />

long fnm1 , fnm2 , fn;<br />

}<br />

if (n == 0 || n == 1) return n;<br />

# pragma omp task shared ( fnm1 )<br />

fnm1 = comp_fib_number (n -1) ;<br />

# pragma omp task shared ( fnm2 )<br />

fnm2 = comp_fib_number (n -2) ;<br />

# pragma omp taskwait<br />

fn = fnm1 + fnm2 ;<br />

return fn;<br />

Beispiel: Fibonacci-Zahlen<br />

• Eine Fibonaccizahl kann erst berechnet werden, wenn ihre beiden Vorgänger<br />

berechnet wurden<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

31


Beispiel: Matrix-Multiplikation<br />

• In der gesamten Veranstaltung haben wir das Beispiel der<br />

Matrix-Multiplikation behandelt<br />

• Erinnerung: Matrix-Multiplikation mit PThreads:<br />

• Es müssen manuell mehrere Threads gestartet werden<br />

• Jeder Thread berechnet einen Teil der Ergebnis-Matrix, dazu wurde jedem<br />

Thread ein Bereich übergeben, den dieser Thread berechnet<br />

• Es muss auf die Beendigung aller Threads gewartet werden<br />

• Die Parallelisierung mit PThreads erforderte eine Umstrukturierung des<br />

sequentiellen Quellcodes<br />

• Dafür haben wir auf einer Quad-core CPU einen guten Speedup von 3.28<br />

gemessen<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

32


Matrix-Multiplikation in <strong>OpenMP</strong><br />

• Um die Matrix-Multiplikation in <strong>OpenMP</strong> umzusetzen, beginnen wir mit der<br />

sequentiellen Version, in welche wir eine Direktive hinzufügen<br />

• Wir parallelisieren hier die äußere Schleife<br />

• Dies entspricht derselben Parallelisierung, welche wir mit PThreads manuell<br />

programmiert haben<br />

• Wir verwenden ein statisches Scheduling<br />

void MatrixMul ( float *M, float * N, float * P, int Width ) {<br />

# pragma omp parallel for schedule ( static )<br />

for ( int i = 0; i < Width ; ++i)<br />

for ( int j = 0; j < Width ; ++j) {<br />

float sum = 0;<br />

for ( int k = 0; k < Width ; ++k) {<br />

sum += M[i * Width + k] * N[k * Width + j];<br />

}<br />

P[i * Width + j] = sum ;<br />

}<br />

}<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

33


• Die Laufzeit der Matrix-Multiplikation mit<br />

Matrizen der Größe 1680 × 1680 auf einem<br />

Intel i7 860 mit 4 Kernen und<br />

Hyper-Threading<br />

• Durch die verwendete Direktive<br />

# pragma omp parallel for \<br />

schedule ( static )<br />

erhalten wir eine vergleichbare Performance<br />

mit der PThread Variante<br />

• Die Programmierung mit PThreads war<br />

jedoch ungleich aufwendiger<br />

Messungen Matrix-Multiplikation<br />

Sekunden<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

35<br />

30<br />

25<br />

20<br />

15<br />

10<br />

Laufzeit Matrix Multiplikation<br />

Sequentiell<br />

PThreads (8 Threads)<br />

<strong>OpenMP</strong><br />

34


<strong>OpenMP</strong> Matrix-Multiplikation<br />

Varianten<br />

• Wir testen die <strong>OpenMP</strong> Matrix-Multiplikation mit unterschiedlichen<br />

Scheduling-Klauseln<br />

• Des Weiteren testen wir eine Variante, in der die innere und nicht die äußere<br />

Schleife der Matrix-Multiplikation parallelisiert wird<br />

• Das dynamic und guided<br />

Scheduling verteilt die Arbeit<br />

besser als das static Scheduling<br />

auf die Threads, so dass weniger<br />

Wartezeiten entstehen<br />

• Bei der Parallelisierung der<br />

inneren Schleife kann der Cache<br />

besser ausgenutzt werden, da alle<br />

Threads zeitgleich auf den selben<br />

Daten der Matrix M arbeiten, dies<br />

ist bei der Parallelisierung der<br />

äußeren Schleife nicht der Fall<br />

Sekunden<br />

10.5<br />

9.5<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

11<br />

10<br />

9<br />

Laufzeit Matrix Multiplikation<br />

35<br />

PThreads<br />

<strong>OpenMP</strong> static<br />

<strong>OpenMP</strong> dynamic<br />

<strong>OpenMP</strong> guided<br />

äußere Schleife innere Schleife


Vorteile<br />

Vor- und Nachteile von <strong>OpenMP</strong><br />

• Einfacher zu programmieren als explizites Threading (Pthreads)<br />

• Für daten- und taskparallele Anwendungen geeignet<br />

• Das sequentielle Originalprogramm wird nicht umgeschrieben, sondern nur<br />

(ggf. inkrementell) um Direktiven ergänzt<br />

• Debuggen ist vereinfacht (Programm ist weiterhin sequentiell ausführbar)<br />

Nachteile<br />

• Abhängig von der Qualität des Compilers (z.B. Unterstützung für<br />

verschachtelte Parallelität)<br />

• Optimiert für Datenparallelität; Taskparallelität nur eingeschränkt unterstützt<br />

Ausblick<br />

• Für den nächsten großen Versionssprung zur Version <strong>OpenMP</strong> 4.0 wird unter<br />

anderem an einer Unterstützung für GPUs gearbeitet<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

36


Zusammenfassung der<br />

Veranstaltung<br />

• In dieser Veranstaltung haben wir uns mit Mutli-core CPUs und GPUs<br />

auseinander gesetzt<br />

• Dabei haben wir sowhol die unterschiedlichen Hardware-Architekturen als<br />

auch verschiedene Programmieransätze behandelt<br />

• Multi-core CPUs und GPUs unterscheiden sich vor allem in der Anzahl der<br />

Recheneinheiten, sowie der verfügbaren Speicherbandbreite<br />

• GPUs bieten wesentlich höhere theoretische (Peask-) Leistung als Multi-core<br />

CPUs<br />

• GPUs können ihre volle Leistung jedoch nur bei geigneten datenparallelen<br />

Aufgaben entfalten<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

37


Parallele Programmierung<br />

• Es gibt mehrere Ansätze zur parallelen Programmierung<br />

• Wir haben PThreads und <strong>OpenMP</strong> zur Programmierung von Multi-core<br />

CPUs, sowie OpenCL und CUDA zur Programmierung von GPUs und<br />

Multi-core CPUs behandelt<br />

• In PThreads werden einzelne Threads explizit programmiert, während in<br />

<strong>OpenMP</strong> parallele Code Bereiche annotiert werden<br />

• OpenCL stellt hingegen ein komplexeres Programmiermodell dar:<br />

• Ein Kernel wird parallel ausgeführt, wobei anzugeben ist, wie viele Instanzen<br />

(Work-items) gestartet werden<br />

• Work-items werden zusätzlich in Work-groups organisiert, welche die<br />

Möglichkeit der Synchronisation einschränken<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

38


Optimierungen<br />

• Um für ein OpenCL Programm auf einer GPU eine möglichst schnelle<br />

Laufzeit zu erhalten, sind eine vielzahl von Optimierungen nötig<br />

• Dabei wird versucht, die Möglichkeiten der Hardware (z. B. lokaler Speicher)<br />

auszunutzen<br />

• Eine besondere Rolle spielen dabei die unterschiedlichen Typen von Speicher<br />

• Die effektivsten Optimierungen sind:<br />

• Eine gut gewählte Work-group Größe (mit Berücksichtigung der Warp-Größe)<br />

• Die Nutzung des lokalen Speichers und Reduzierung von Zugriffen auf den<br />

globalen Speicher<br />

• Weitere Optimierungen sind möglich, welche die Laufzeit nocheinmal<br />

erheblich steigern können<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

39


• Parallele Programmierung ist aufwendiger als<br />

sequentielle Programmierung<br />

• Dafür können geeignete Anwendungen (wie die<br />

Matrix-Multiplikation) erheblich beschleunigt<br />

werden<br />

• PThreads, <strong>OpenMP</strong> und OpenCL erreichen auf<br />

Multi-core CPUs vergleichbare Leistung,<br />

unterscheiden sich jedoch im<br />

Programmier-Aufwand<br />

• Die GPU entfaltet erst nach geeigneten<br />

Optimierungen ihr Potenzial, übertrift die CPU<br />

jedoch erheblich<br />

Fazit: Parallele Programmierung<br />

• Fazit: Programmierung der GPU ist aufwendig<br />

und erfordert Optimierungen, dafür wird eine<br />

sehr viel höhere Leistung erreicht Work-group 16 × 16<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

Sekunden<br />

40<br />

30<br />

20<br />

10<br />

0<br />

Laufzeit Matrix Multiplikation<br />

40<br />

Sequentiell<br />

PThreads<br />

<strong>OpenMP</strong><br />

OpenCL CPU<br />

OpenCL GPU<br />

lokaler Speicher


Aktuelle Hardware-Entwicklungen<br />

• Trend: Neue Prozessoren enthalten immer mehr Kerne<br />

• Die Bedeutung paralleler Programmierung wird daher weiter zunehmen<br />

• Prozessoren in der Entwicklung:<br />

• Intel: Knight’s Corner Parallelprozessor mit ca. 50 CPU Kernen<br />

• AMD: Accelerated processing unit (APU), heterogener Prozessor: Multi-core<br />

CPU mit integrierter GPU<br />

• NVIDIA: GPU Architektur Kepler mit doppelter Leistung der aktuellen Fermi<br />

Architektur<br />

• Zur Programmierung dieser Systeme ist i. d. .R. OpenCL geeignet!<br />

<strong>OpenMP</strong> SS 2012 Prof. Sergei Gorlatch, Michel Steuwer VL 9<br />

41

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!