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
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