OpenMP - Forschungszentrum Jülich

Transcrição

OpenMP - Forschungszentrum Jülich
Einführung in die Parallelprogrammierung
OpenMP Teil 1:
Einführung in OpenMP
Annika Hagemeier
Jülich Supercomputing Centre (JSC)
Forschungszentrum Jülich
Inhalt










Motivation
Was ist OpenMP?
Historie
OpenMP Grundlagen
Direktiven
Laufzeitroutinen
Umgebungsvariablen
Parallele Region
Parallele Ausführung kontrollieren
Vor- und Nachteile
17. November 2014
Einführung in OpenMP
2
Literatur




Parallel Programming in OpenMP
R. Chandra, L. Dagum, D. Kohr, D. Maydan, J. McDonald, R.
Menon (2001)
Using OpenMP – Portable Shared Memory Parallel
Programming
B. Chapman, G. Jost, R. Van der Pas (2008)
OpenMP: Eine Einführung in die parallele Programmierung in
C/C++
S. Hoffmann, R. Lienhart (2008)
OpenMP Application Program Interface (Version 4.0)
OpenMP Architecture Review Board (Juli 2013)
http://www.openmp.org/mp-documents/OpenMP4.0.0.pdf

OpenMP 4.0.1 Beispiele (Februar 2014)
http://openmp.org/mp-documents/OpenMP_Examples_4.0.1.pdf
17. November 2014
Einführung in OpenMP
3
Motivation


Die Entwicklung von Shared-Memory-Architekturen hat in
den letzen Jahren deutlich zugenommen.
Auch die Anzahl der Prozessoren hat deutlich zugenommen
(2 – 32 RISC-Prozessoren)
Wie parallelisiert man Anwendungen für SharedMemory-Architekturen?

hardware-spezifische Programmiermodelle?
⇒ nicht portabel
⇒ sind “low-level” und verlangen tiefe Programmierkenntnis
⇒ Aufwand nicht gerechtfertigt, da nur auf einer Plattform
anwendbar

MPI?
⇒ zwar portabel, aber Programmieraufwand sehr hoch
⇒ Aufwand nicht gerechtfertigt, da die Anzahl der Prozessoren
begrenzt
17. November 2014
Einführung in OpenMP
4
Was ist OpenMP?
= Open Specifications for Multi Processing






OpenMP ist eine standardisierte Programmierschnittstelle
(API), die zur portablen Programmierung von Parallelrechnern mit gemeinsamem Speicher (bzw. gemeinsamem
Adressraum) entwickelt wurde
breite Unterstützung von Herstellern (u.a. SGI/CRAY, SUN,
IBM, Intel, Compaq)
läuft mit C/C++ und Fortran
nicht anwendbar auf Systemen mit verteiltem Speicher
keine Laufzeitüberprüfung hinsichtlich Korrektheit der
Direktiven (Deadlocks, Datenabhängigkeiten etc.)
aktuelle Spezifikation:


OpenMP Application Program Interface 4.0 (Juli 2013)
http://openmp.org/wp/
17. November 2014
Einführung in OpenMP
5
Historie von OpenMP (1)

OpenMP ist ein kürzlich entwickelter Industriestandard


Vorgänger





proprietäre Ansätze einiger Hersteller (u.a. SGI, CRAY, SUN,
IBM) Ende der 80er
die Hersteller boten unterschiedliche Sets von Direktiven an,
die jedoch in Syntax und Semantik ähnlich waren
alle benutzen aus Portabilitätsgründen einheitliche Kommentaroder Pragma-Annotationen
verschiedene Ansätze zur Standardisierung mit wenig Erfolg
(PCF, ANSI X3H5)
OpenMP wurde durch die Entwickler motiviert


vom ersten Konzept bis zum angenommenen Standard: Juli
1996 - Oktober 1997
wachsendes Interesse an einer wirklich portablen Lösung
OpenMP Architectural Review Board (ARB)


Gruppe hauptsächlich aus Hardware- und Software-Herstellern
OpenMP Spezifikationen werden vom OpenMP ARB entwickelt
17. November 2014
Einführung in OpenMP
6
Historie von OpenMP (2)
OpenMP Spezifikationen:






1997
1998
2000
2002
2005

ein Standard für beide Sprachen

Klarstellungen besonders im Speichermodell
2008 V3.0


erstes API für Fortran (V1.0)
erstes API für C/C++ (V1.0)
Fortran (V2.0)
C/C++ (V2.0)
C/C++ und Fortran (V2.5)
Erweiterungen wie Task-Parallelität, geschachtelte Schleifen, ...
2011 V3.1

Erweiterungen wie Optimierung des Task-Modells,
Mechanismus um Threads an Prozesse zu binden, Erweiterung
des atomic-Pragmas, min/max-Reduktion für C/C++, ...
17. November 2014
Einführung in OpenMP
7
Historie von OpenMP (3)

2013 V4.0








Unterstützung für Beschleuniger (Identifizierung von CodeRegionen, die auf anderen Rechenlaufwerken gerechnet
werden sollen)
SIMD-Konstrukte zur Vektorisierung von seriellen und parallelen
Schleifen
Error Handling
Thread Affinity (Mechanismen zur Bestimmung, wo Threads
ausgeführt werden sollen)
Erweiterung des Task-Modells (Task-Gruppen, Abhängigkeiten
zwischen Tasks)
Fortran 2003 Unterstützung
Benutzerdefinierte Reduktionen
Neue Klausel bei atomic
17. November 2014
Einführung in OpenMP
8
OpenMP Komponenten
1. Direktiven
 Instruktionen für Compiler, die OpenMP unterstützen:





Pragmas in C/C++
Quelltext-Kommentare in Fortran
Kontrollstrukturen zur Beschreibung der Parallelität
Datenumgebungskonstrukte zur Kommunikation
zwischen den Threads
Synchronisationkonstrukte zur Koordination der Threads
2. Laufzeitroutinen
 Abfragen und Ändern der Parameter der parallelen
Umgebung
 Synchronisation
3. Umgebungsvariablen
 Einstellen der Parameter der parallelen Umgebung
17. November 2014
Einführung in OpenMP
9
Erstes Beispiel
Programm:
int main(void)
{
#pragma omp parallel
printf(“Hello World\n“);
}
Übersetzen (Beispiel):
Ausführen:
export OMP_NUM_THREADS=4
./hello
Hello world
Hello world
Hello world
Hello world
icc -openmp -o hello hello.c
17. November 2014
Einführung in OpenMP
10
Ausführungsmodell (1)





OpenMP basiert auf dem fork/join Modell
Ein OpenMP-Programm startet als
einzelner Thread (master thread).
Weitere Threads (Team) werden durch
eine parallele Region erzeugt und am
Ende der parallelen Region wieder (an
das Betriebs- oder Laufzeitsystem)
zurückgegeben.
Ein Team besteht aus einer festen
Anzahl von Threads, die die parallele
Region redundant abarbeiten.
Am Ende der parallelen Region findet
eine Barrier-Synchronisation aller
Threads im Team statt.
Nach der parallelen Region führt der
master thread den Code (sequentiell)
fort.
17. November 2014
Einführung in OpenMP
11
Ausführungsmodell (2)


In einem Programm können mehrere parallele Konstrukte
spezifiziert werden.
Folglich kann sich der master thread in einem Programm
mehrfach aufspalten und wieder zusammenfügen.
17. November 2014
Einführung in OpenMP
12
Kommunikation und Datenumgebung (1)

jeder Thread hat eine eigene Datenumgebung oder
Ausführungskontext:




Master Thread:


globale Variablen
automatische Variablen in Unterfunktionen (Stack)
dynamisch angelegte Variablen (Heap)
Ausführungskontext existiert während der gesamten
Laufzeit des Programms
Worker Threads:



Auführungskontext existiert nur innerhalb einer parallelen
Region
eigener Stack zum Aufruf von Unterfunktionen
alle anderen Variablen sind entweder gemeinsam oder
privat
17. November 2014
Einführung in OpenMP
13
Kommunikation und Datenumgebung (2)






Daten werden unterteilt in gemeinsame (engl. shared) und
private Daten
die Bindung einer Variablen in einer parallelen Region wird
individuell durch ein Statement vom Programmierer
festgelegt
Daten sind per Default, ohne weitere Angaben gemeinsam
eine Ausnahme bilden Schleifenvariablen:
 Schleifenvariablen einer parallelen Schleife sind immer
privat
neben den Attributen shared und private, kann eine
Variable auch das Attribut reduction besitzen
Reduktionsvariablen:


Mischung aus gemeinsam und privat
Gegenstand einer arithmetischen Operation am Ende eines
parallelen Konstrukts
17. November 2014
Einführung in OpenMP
14
Kommunikation und Datenumgebung (3)

Gemeinsame Daten:




sind allen Threads bekannt und von diesen zugreifbar
alle Threads, die auf diese Daten zugreifen, greifen auf die
selbe Speicherstelle zu
Threads kommunizieren miteinander oder synchronisieren
sich über gemeinsame Daten
Private Daten:





jeder Thread legt seine eigene private Kopie an
jeder Thread greift nur auf seine eigene Speicherstelle in
seinem Ausführungskontext zu
nicht zugreifbar für andere Threads
existiert nur während der Ausführung einer parallelen
Region
Wert beim Eintritt und Austritt in die parallele Region
undefiniert
17. November 2014
Einführung in OpenMP
15
Ausführungsmodel mit gemeinsamen Variablen
17. November 2014
Einführung in OpenMP
16
Ausführungsmodel mit privaten Variablen
17. November 2014
Einführung in OpenMP
17
Synchronisation


OpenMP Threads kommunizieren miteinander durch das
Schreiben und Lesen von gemeinsamen Daten
oft ist es jedoch nötig den Zugriff auf gemeinsame Daten bei
mehreren Threads zu koordinieren (synchronisieren):



mehrere Threads versuchen gleichzeitig die gleiche Variable zu
ändern
ein Thread liest eine Variable, während ein anderer die gleiche
Variable gerade ändert
ungeregelter Zugriff:
 der Wert der Variablen ist undefiniert oder hängt von der
Reihenfolge ab, in der die Threads auf die Variablen zugreifen
⇒ mehrere unabhängige Programmdurchläufe liefern unterschiedliche Ergebnisse
⇒ unzulässig, da das parallele Programm in jedem Fall die gleichen
Ergebnisse liefern soll, wie das serielle Programm
17. November 2014
Einführung in OpenMP
18
Speichermodell
Geteilter Speicher mit gelockerter Konsistenz


alle Threads haben Zugriff zum Speicher
jeder Thread darf außerdem seine eigene temporäre Sicht
auf den Speicher haben




jeder Thread hat außerdem Zugriff auf einen (Thread-)
privaten Speicher


kein zwingend notwendiger Teil des Speichermodells
kann jedes Element zwischen Thread und Speicher darstellen
(z.B. Register oder Cache)
erlaubt das Halten von Variablen im Cache, so dass nicht jedes
Mal auf den Speicher zugegriffen werden muss
andere Threads haben dort keinen Zugriff
die flush-Direktive erzwingt Konsistenz zwischen
temporärer Sicht und Speicher
17. November 2014
Einführung in OpenMP
19
OpenMP Direktiven (1)


Direktiven weisen den Compiler an, bestimmte CodeAbschnitte zu parallelisieren
man unterscheidet Direktiven für




Parallelität (for, parallel, sections, single, task, ...)
Synchronisation (barrier, critical, master, atomic, ...)
Daten (threadprivate)
alle für OpenMP relevanten Pragmas beginnen mit
#pragma omp


bzw.
!$omp
solche Pragmas werden von Compilern ohne OpenMPUnterstützung einfach ignoriert
eine OpenMP-Direktive bezieht sich immer auf den
folgenden strukturierten Block oder ein OpenMPKonstrukt
17. November 2014
Einführung in OpenMP
20
OpenMP Direktiven (2)

OpenMP-Pragmas haben die allgemeine Form:
#pragma omp Direktive [Klausel[[,] Klausel] ...] new-line

Klauseln sind optional und beeinflussen das Verhalten der
Direktive, auf die sie sich beziehen





jede Direktive hat eine andere Menge von gültigen Klauseln
im Allgemeinen ist die Menge jedoch sehr ähnlich
für einige Direktiven ist diese Menge leer (keine Klauseln sind
erlaubt)
alle Anweisungen müssen mit dem Zeilenumbruch enden
Direktiven über mehrere Zeilen werden folgendermaßen
geschrieben:
#pragma omp Direktive hier_steht_der_Beginn \
und_hier_steht_der_Rest
17. November 2014
Einführung in OpenMP
21
OpenMP Direktiven: Syntax



C/C++
Direktiven sind spezielle Compiler-Pragmas
Direktiven und API-Funktionen werden kleingeschrieben
es gibt keine END-Direktive wie in Fortran, sondern die
Direktive bezieht sich immer auf den folgenden strukturierten Block
#pragma omp Direktive [Klausel[[,] Klausel] ...] new-line
strukturierter Block
Beispiel:
int main() {
#pragma omp parallel default(shared)
printf(”hello world\n” );
}

Stand-alone Direktiven haben keinen zugehörigen Block
Beispiel:
#pragma omp barrier
17. November 2014
Einführung in OpenMP
22
OpenMP Direktiven: Syntax


Fortran
Direktiven sind spezielle Kommentare
die Groß-/Kleinschreibung ist irrelevant
!$OMP Direktive [Klausel[[,] Klausel] ...] new-line
strukturierter Block
!$OMP END
Beispiel:
!$OMP PARALLEL DEFAULT(SHARED)
write(*,*) ´Hello world´
!$OMP END PARALLEL

Stand-alone Direktiven haben keinen zugehörigen Block
Beispiel:
!$omp barrier
17. November 2014
Einführung in OpenMP
23
Strukturierter Block



besteht aus einem oder mehreren Statements
hat einen Eingang am Anfang und einen Ausgang am Ende
Nicht erlaubt:



Erlaubt:



Verzweigungen in den Block
Verzweigungen aus dem Block heraus
Verzweigungen innerhalb des Blocks
Beenden des Programms innerhalb des Blocks (z.B. mit exit())
Strukturierte Blöcke, die aus mehreren Statements
bestehen, müssen in C/C++ immer mit Klammern
umschlossen werden!
 ansonsten bezieht sich die Direktive nur auf das erste
Statement
17. November 2014
Einführung in OpenMP
24
OpenMP Direktiven: Beispiele
/* fehlende Klammern, nur das 1. Statement wird parallel ausgeführt */
#pragma omp parallel default(shared)
printf(“Ich werde parallel ausgeführt.\n”);
printf(“Ich werde NICHT parallel ausgeführt.\n”);
/* falsch gesetzte Klammer, Code compiliert nicht */
#pragma omp parallel default(shared) {
printf(“Ich werde parallel ausgeführt.\n”);
printf(“Ich werde auch parallel ausgeführt.\n”);
}
/* richtig gesetzte Klammern, beide Statements werden parallel
ausgeführt */
#pragma omp parallel default(shared)
{
printf(“Ich werde parallel ausgeführt.\n”);
printf(“Ich werde auch parallel ausgeführt.\n”);
}
17. November 2014
Einführung in OpenMP
25
Bedingte Übersetzung




OpenMP Direktiven werden von gewöhnlichen Compilern
(ohne OpenMP-Unterstützung) ignoriert
zusätzlich ermöglichen die meisten OpenMP-Compiler das
Ausschalten von OpenMP durch ein Compiler-Flag
⇒ das Programm kann sowohl seriell (ohne OpenMP) als
auch parallel ausgeführt werden
dies gilt jedoch nur für Direktiven
ein Programm kann jedoch auch andere OpenMP spezifische
Kommandos (z. B. Aufruf von Laufzeitroutinen) enthalten
 Bedingte Übersetzung
 OpenMP bietet die Möglichkeit der bedingten
Übersetzung
 Statements, die nur für die parallele Ausführung relevant
sind, werden markiert
17. November 2014
Einführung in OpenMP
26
Bedingte Übersetzung: Syntax

C/C++:

das Makro _OPENMP wird zum selektiven Übersetzen
verwendet:


Bei OpenMP-Unterstützung wird _OPENMP auf den Wert yyyymm
gesetzt (Jahr und Monat der unterstützen OpenMP-Version)
OpenMP-Kommandos werden mit einer Direktive zur
Abfrage des Makros versehen
#ifdef _OPENMP
iam = omp_get_thread_num();
#endif

Fortran:
 OpenMP-Kommandos werden mit !$ am Zeilenanfang
gekennzeichnet
!$
17. November 2014
iam = OMP_GET_THREAD_NUM()
Einführung in OpenMP
27
Parameterangaben zur Datenumgebung (1)
private(Variablenliste)


Variablen in der Variablenliste werden als privat deklariert
jeder Thread bekommt seine eigene private Kopie der Variablen
shared(Variablenliste)


Variablen in der Variablenliste werden als gemeinsam deklariert
alle Threads greifen auf die selbe Variable zu
default(private / shared / none)




ändert das Standardverhalten von Variablen, die nicht explizit mit
einer shared- bzw. private-Klausel aufgeführt werden
default(private): existiert nur für Fortran
default(shared): ändert nichts am Standardverhalten
default(none):


für jede Variable in einer parallelen Region muss die Zugriffsklausel
explizit angegeben werden, ansonsten Fehler beim Compilieren
Aufdecken von Programmierfehlern, die aus falschen Annahmen über
Variablenzugriffe resultieren
17. November 2014
Einführung in OpenMP
28
Parameterangaben zur Datenumgebung (2)


der Inhalt privater Variablen ist beim Eintritt und beim Verlassen
einer parallelen Region undefiniert
⇒ minimiert das Kopieren von Daten
die Klauseln firstprivate und lastprivate erlauben eine
Initialisierung bzw. Finalisierung der Werte privater Variablen
firstprivate(Variablenliste)


Variablen in der Variablenliste werden als privat deklariert und
erhalten den Wert, den die Variable vor Eintritt in die prallele
Region hatte
jede Kopie der Variablen wird initialisiert (nur einmal pro Thread)
lastprivate(Variablenliste)

Variablen in der Variablenliste werden als privat deklariert und der
Thread, der die sequentiell letzte Iteration einer parallelen Schleife
ausführt, kopiert den Wert seiner Variablen auf die Variable
außerhalb der parallelen Region
17. November 2014
Einführung in OpenMP
29
Laufzeitroutinen

die Funktionen der Laufzeitbibliothek werden hauptsächlich
dazu verwendet, Parameter der Laufzeitumgebung von
OpenMP abzufragen bzw. zu setzen


bietet außerdem portable Funktionen zur Zeitmessung



z. B. omp_set_num_threads(), omp_get_num_threads()
z. B. omp_get_wtime() liefert die Zeit in Sekunden
zusätzlich enthält die Bibliothek Funktionen zur
Synchronisation von Threads (z. B. omp_set_lock())
zur Verwendung von Funktionen der Laufzeitbibliothek muss
die Header-Datei omp.h eingebunden werden
#ifdef _OPENMP
#include <omp.h>
#endif
17. November 2014
Einführung in OpenMP
30
Laufzeitroutinen der Ausführungsumgebung (1)
void omp_set_num_threads(int num_threads);

setzt die Anzahl der Threads in einem Team
int omp_get_num_threads(void);

gibt die Anzahl der Threads im aktuellen Team zurück
int omp_get_max_threads(void);

gibt die maximale Anzahl Threads zurück, die verwendet werden
können um ein neues Team von Threads zu erzeugen
int omp_get_thread_num(void);

gibt die Thread-ID zurück (Zahl zwischen 0 und n-1)
int omp_get_num_procs(void);

liefert die Anzahl der Prozessoren, auf denen das Programm
parallel ausgeführt werden kann
17. November 2014
Einführung in OpenMP
31
Laufzeitroutinen der Ausführungsumgebung (2)
int omp_in_parallel(void);

liefert true (Wert ungleich 0) zurück, wenn sich der Aufruf in
einer parallelen Region befindet, ansonsten false (Wert 0)
void omp_set_dynamic(int dynamics_threads);

die dynamische Anpassung von Thread-Teamgrößen kann mit
dieser Funktion aktiviert und deaktiviert werden
int omp_get_dynamic(void);

gibt an, ob die dynamische Anpassung von Thread-Teamgrößen
aktiviert wurde oder nicht
void omp_set_nested(int nested);

aktiviert verschachtelte Parallelität bei ineinander verschachtelten parallelen Abschnitten
int omp_get_nested(void);

fragt ab, ob verschachtelte Parallelität aktiviert ist
17. November 2014
Einführung in OpenMP
32
Laufzeitroutinen der Ausführungsumgebung (3)
weitere (hier nicht behandelte) Laufzeitroutinen:









omp_get_cancellation()
omp_set_schedule()
omp_get_schedule()
omp_get_thread_limit()
omp_set_max_active_levels()
omp_get_max_active_levels()
omp_get_level()
omp_getancestor_thread_num()
omp_get_team_size()
4.0









omp_get_active_level()
omp_in_final()
4.0 omp_get_proc_bind()
4.0 omp_set_default_device()
4.0 omp_get_default_device()
4.0 omp_get_num_devices()
4.0 omp_get_num_teams()
4.0 omp_get_team_num()
4.0 omp_is_initial_device()
nähere Informationen zu den oben genannten Funktionen im
OpenMP-Standard
17. November 2014
Einführung in OpenMP
33
Laufzeitroutinen zur Zeitmessung

die OpenMP-Laufzeitbibliothek bietet portable Funktionen zur
Messung der Ausführungszeit von Programmen
double omp_get_wtime(void);



liefert die Zeit (in Sekunden) zurück, die seit einem festen
Zeitpunkt in der Vergangenheit verstrichen ist
die zeitliche Auflösung ist begrenzt und von der zugrunde
liegenden Architektur und Betriebssystem abhängig
⇒ zu messende Laufzeit sollte nicht zu kurz sein
zur Zeitmessung speichert man den Zeitpunkt des Beginns der
Ausführung ab und bildet nach deren Ende die Differenz aus
diesem Wert und der aktuellen Zeit
double omp_get_wtick(void);


die Rückgabewerte von omp_get_wtime() basieren auf einem
internen Timer
omp_get_wtick() gibt die Anzahl Sekunden zwischen zwei Ticks
dieses Timers zurück
17. November 2014
Einführung in OpenMP
34
Beispiel: Zeitmessung

befindet sich der zu vermessende Code komplett innerhalb
eines parallelen Abschnitts, so kann der Aufruf von
omp_get_wtime() innerhalb eines single-Blocks erfolgen,
damit die Zeitnahme nur einmal erfolgt
#pragma omp parallel
{
// ...
#pragma omp single nowait
start = omp_get_wtime();
// ... zu vermessender Codeabschnitt
#pragma omp single nowait
end = omp_get_wtime();
// ...
} // Ende paralleler Abschnitt
printf(“Zeit in Sekunden: %lf\n”, end - start);
17. November 2014
Einführung in OpenMP
35
Umgebungsvariablen (1)



werden dazu verwendet, Parameter der Laufzeitumgebung von
OpenMP zu setzen
die Standardwerte der meisten Umgebungsvariablen sind
implementierungsabhängig (Ausnahme: OMP_NESTED)
werden einmal zu Beginn der Programmausführung abgefragt
(Änderung der Parameter während der Programmausführung nur
über Laufzeitroutinen möglich)
OMP_SCHEDULE type[,chunk]




nur anwendbar auf parallele Schleifen mit Schedule-Typ runtime
Schedule-Typ muss angegeben werden
optional kann noch die Blockgröße (chunk) angegeben werden
Beispiele: export OMP_SCHEDULE=”dynamic”
export OMP_SCHEDULE=”GUIDED,4”
OMP_NUM_THREADS num


setzt die Anzahl der Threads in einem Team
Beispiel: export OMP_NUM_THREADS=4
17. November 2014
Einführung in OpenMP
36
Umgebungsvariablen (2)
OMP_DYNAMIC dynamic


dynamische Anpassung der Anzahl der Threads erlauben bzw.
nicht erlauben
Beispiel: export OMP_DYNAMIC=TRUE
OMP_NESTED nested



Verschachtelung von parallelen Konstrukten erlauben bzw. nicht
erlauben (default: OMP_NESTED=FALSE)
Beispiel: export OMP_NESTED=TRUE
weitere (hier nicht behandelte) Umgebungsvariablen:






OMP_STACKSIZE
OMP_WAIT_POLICY
OMP_MAX_ACTIVE_LEVELS
OMP_THREAD_LIMIT
OMP_PROC_BIND

4.0

4.0

4.0

4.0
OMP_CANCELLATION
OMP_DEFAULT_DEVICE
OMP_DISPLAY_ENV
OMP_PLACES
nähere Informationen zu den oben genannten Umgebungsvariablen
im OpenMP-Standard
17. November 2014
Einführung in OpenMP
37
Parallelität mit OpenMP
OpenMP bietet verschiedene Möglichkeiten der Parallelität

Parallelisierung von Schleifen:



Parallele Regionen:



ermöglicht die parallele Ausführung eines beliebigen CodeAbschnitts
Parallelisierung im SPMD-Stil (Single-Program Multiple-Data)
Work-Sharing Konstrukte:


Haupteinsatzgebiet / Stärke von OpenMP
jeder Thread führt eine (andere) Teilmenge von Iterationen einer
Schleife aus
ermöglicht die Aufteilung der Arbeit auf die Threads innerhalb
einer parallelen Region
Tasks:

erlaubt die einfache Parallelisierung von Operationen auf
komplexen Datenstrukturen wie Listen oder Bäumen
17. November 2014
Einführung in OpenMP
38
Parallele Region



grundlegendes Konstrukt zur Parallelisierung eines
beliebigen Programmabschnitts
Schachtelung von parallelen Regionen im Prinzip möglich,
aber vielfach noch nicht implementiert oder nicht per
Default möglich
innerhalb einer parallelen Region werden weitere
Konstrukte eingesetzt
 zur Koordination der einzelnen Threads (z.B. critical)
 zur Aufteilung der Arbeit auf die Threads (z.B. for,
sections)
17. November 2014
Einführung in OpenMP
39
Parallele Region: Syntax
#pragma omp parallel [Klausel[[,] Klausel] ...] new-line
strukturierter Block
Klauseln:









private (list)
shared (list)
default (shared | none)
firstprivate (list)
reduction (operator: list)
if (scalar-expression)
copyin (list)
num_threads (integer-expression)
4.0 proc_bind (master | close | spread)
17. November 2014
Einführung in OpenMP
40
Parallele Region: Ausführungsmodell



zu Beginn einer parallelen Region wird ein Team von
Threads erzeugt (Thread 0 ist der Master Thread, der zuvor
den sequentiellen Code ausgeführt hat)
die Threads des Teams bearbeiten den Code gemeinsam
bis zum Ende der parallelen Region ab
am Ende der parallelen Region findet eine BarrierSynchronisation statt, danach fährt der Master Thread mit
der sequentiellen Ausführung fort
Master
Fork
#pragma omp parallel
{
Worker
printf(“Hello World!\n”);
}
17. November 2014
printf()
printf()
printf()
Join
Einführung in OpenMP
41
Parallele Ausführung kontrollieren (1)

Dynamisches Ausschalten der parallel-Direktive mit der ifKlausel:





die Entscheidung, ob ein Codeabschnitt parallel ausgeführt
werden soll oder nicht kann von Faktoren abhängen, die erst zur
Laufzeit feststehen
wenn ein Programm in eine parallele Region mit einer if-Klausel
eintritt, wird zunächst die logische Bedingung ausgewertet
ist die Bedingung true, wird die Region parallel ausgeführt
ist die bedingung false, wird die Region seriell ausgeführt
Abfrage auf parallele Ausführung mit omp_in_parallel():


true, wenn die Funktion innerhalb einer parallel ausgeführten
Region aufgerufen wurde
false, wenn die Funktion innerhalb einer seriellen Region oder
einer serialisierten parallelen Region aufgerufen wurde
17. November 2014
Einführung in OpenMP
42
Parallele Ausführung kontrollieren (2)

Threadaffinität kontrollieren mit der proc_bind-Klausel:


Die proc_bind-Klausel definiert die Regel zur Platzierung der
Threads auf der zur Verfügung stehenden Partition
Erlaubt sind die folgenden Regeln:

master:

jeder Thread im Team soll der selben Stelle zugewiesen
werden, wie der Master Thread
close:
die Threads im Team sollen Stellen nahe der des Master
Threads zugewiesen werden

spread:
die Threads im Team sollen möglichst breit gestreut auf die
zu verfügung stehende Partition verteilt werden
17. November 2014
Einführung in OpenMP
43
Parallele Ausführung kontrollieren (3)
Anzahl der Threads kontrollieren:
 export OMP_NUM_THREADS=n


omp_set_num_threads(n)



wird die Umgebungsvariable vor Programmstart gesetzt, werden
Teams mit n Threads erzeugt
Einstellung der Teamgröße zur Laufzeit
beeinflusst nur nachfolgende parallele Regionen
num_threads

kontrolliert die
Teamgröße für eine
spezielle Region
#pragma omp parallel for num_threads(4)
for (i=0; i<16; i++)
[…]
Prioritätenreihenfolge der Mechanismen:



num_threads
omp_set_num_threads(n)
OMP_NUM_THREADS
17. November 2014
Einführung in OpenMP
44
Vorteile von OpenMP





erlaubt die Parallelisierung seriellen Codes durch Hinzufügen
von Direktiven
einfache parallele Algorithmen sind einfach und schnell zu
implementieren
Aufblähung des Codes sehr gering
 (in der Regel 2 – 25%)
guter Code compiliert und läuft auch mit einem
gewöhnlichen Compiler auf einer CPU (Direktiven werden
vom Compiler ignoriert)
gemeinsamer Adressraum vereinfacht die Entwicklung von
Debuggern
17. November 2014
Einführung in OpenMP
45
Nachteile




Skalierbarkeit ist begrenzt
Spezieller Compiler wird benötigt
Programmierer hat nicht die volle Verantwortung, Aktionen
wie die Aufteilung der Arbeit oder die Kommunikation
zwischen Threads werden implizit realisiert
 implizite Prozesse schwer nachvollziehbar
 wann wird kommuniziert und wann nicht
 wie teuer ist die Kommunikation
kann nur auf Architekturen mit gemeinsamem Speicher
verwendet werden
17. November 2014
Einführung in OpenMP
46
Übung
Übung 10:
Einführung in OpenMP
17. November 2014
Einführung in OpenMP
47
Einführung in die Parallelprogrammierung
OpenMP Teil 2:
Schleifenparallelisierung
Annika Hagemeier
Jülich Supercomputing Centre (JSC)
Forschungszentrum Jülich
Inhalt


Syntax
Klauseln









Einschränkungen
Laufzeitverhalten
Scheduling-Strategien
Reduktion
Datenabhängigkeiten und Race Condition




schedule
if
ordered
copyin
Flussabhängigkeit (Flow Dependence)
Gegenabhängigkeit (Anti Dependence)
Ausgabeabhängigkeit (Output Dependence)
Intel Thread Checker
17. November 2014
Schleifenparallelisierung
2
Parallele Schleife




Haupteinsatzgebiet von OpenMP
viele Programme können effizient parallelisiert werden nur
durch Parallelisierung der Schleifen
inkrementelle Parallelisierung: Hinzufügen von Direktiven und nur kleine lokale Änderungen im Programmtext
parallele Ausführung von Schleifen lässt sich als SPMD
(single program multiple data) beschreiben:


Korrektheit: die parallele Version muss die selben
Ergebnisse liefern, wie die serielle Version


jeder Thread führt den in der Schleife enthaltenen Code aus,
aber jeder für eine andere Teilmenge von Iterationen und damit
auf anderen Daten
Nicht alle Schleifen lassen sich ohne weiteres parallellisieren
for-Direktive: Schleifenparallelisierung in OpenMP
17. November 2014
Schleifenparallelisierung
3
Parallele Schleife: Syntax
#pragma omp for [Klausel[[,] Klausel] ...] new-line
for ( var = startwert; var op endwert; inkrement )
Schleifenkörper
parallelisiert nur die unmittelbar folgende Schleife

Klauseln:








private (list)
firstprivate (list)
lastprivate (list)
reduction (operator: list)
schedule (kind [, chunksize])
collapse (n)
ordered
nowait
17. November 2014
Schleifenparallelisierung
4
Parallele Schleife: Syntax (Kurzform)
#pragma omp parallel for [Klausel[[,] Klausel] ...] new-line
for ( var = startwert; var op endwert; inkrement )
Schleifenkörper



die Direktiven #pragma omp parallel und #pragma omp
sections lassen sich zu einer Direktive zusammenfassen
jedoch nur, wenn die parallele Region nur aus einer
parallelen Schleife besteht
alle für parallel und for gültigen Klauseln (außer nowait)
sind erlaubt:







if (scalar-expression)
lastprivate (list)

num_threads (n)
schedule (kind[, chunksize])

default (shared | none)
collapse (n)

shared (list)
ordered
copyin (list)
4.0 proc_bind (master | close | spread)



17. November 2014
private (list)
firstprivate (list)
reduction (operator: list)
Schleifenparallelisierung
5
Geschachtelte Schleifen

die for-Direktive bezieht sich nur auf die unmittelbar
folgende Schleife
Beispiel 1: Zeilensumme
#pragma omp parallel for
for ( i = 0; i < n; i++ ) {
a[i][0] = 0;
for ( j = 0; j < m; j++ )
a[i][0] = a[i][0] + a[i][j];
}
Beispiel 2: Glättungsfunktion
for ( i = 1; i < n; i++ ) {
#pragma omp parallel for
for ( j = 1; j < m - 1; j++ )
a[i][j] = ( a[i-1][j-1] + a[i-1][j] + a[i-1][j+1] ) / 3.0;
}
17. November 2014
Schleifenparallelisierung
6
for-Direktive: collapse-Klausel

zur Parallelisierung von perfekt geschachtelten Schleifen





alle Schleifenvariablen müssen komplett unabhängig
voneinander sein
nur die innerste Schleife darf beliebigen Code enthalten, alle
anderen dürfen nur geschachtelte Schleifen enthalten
sinnvoll z. B. bei Iterationen über mehrdimensionale Felder
n gibt an, wie viele Schleifen vom Compiler in eine Schleife
zusammengefasst (kollabiert) werden, d.h. wie viele Schleifen geschachtelt sind
Reihenfolge der
Iterationen der
kollabierten Schleife
hängt von der
sequentiellen
Reihenfolge ab
17. November 2014
#pragma omp parallel for collapse(3)
for(int l=0; l<10; ++l)
for(int j=0; j<3; ++j)
for(int k=0; k<7; ++k){
/* Nur hier darf
beliebiger Code stehen! */
foo[l][j][k] = 0;
}
Schleifenparallelisierung
7
Geordnete Ausführung mit ordered
#pragma omp ordered new-line
strukturierter Block

die ordered-Direktive bewirkt, dass der folgende Block in
der originalen sequentiellen Reihenfolge ausgeführt wird



z.B. geordnetes Schreiben von Werten in eine Datei
der Code in der ordered-Region wird sequentialisiert,
während der Code außerhalb der Region parallel
abgearbeitet wird
sollen Teile einer parallelen Schleife geordnet ausgeführt werden, so muss
das Schlüsselwort ordered
sowohl als Klausel als
auch als Direktive
eingefügt werden
17. November 2014
/* ordered als Klausel */
#pragma omp parallel for ordered
for ( i=0; i<n; i++ ) {
a[i] = f(i);
/* ordered als Direktive */
#pragma omp ordered
printf("a[%d] = %d", i, a[i]);
}
Schleifenparallelisierung
8
Einschränkungen der Schleifenstruktur
Damit eine Schleife in OpenMP parallelisiert werden kann,
muss sie in kanonischer Form vorliegen (erleichtert die
Compiler-basierte Parallelisierung):
 die Anzahl der Schleifendurchläufe muss vor dem Eintritt
in die Schleife berechnet werden können und darf sich
innerhalb der Schleife nicht ändern
 die Zählvariable darf innerhalb der Schleife nicht verändert
werden
 das Programm muss alle Iterationen beenden




kein vorzeitiges Verlassen durch eine break-Anweisung
keine Ausnahme innerhalb der Schleife werfen, die erst
außerhalb aufgefangen wird
beenden der aktuellen Iteration und Wechsel zur nächsten
Iteration möglich (continue erlaubt)
beenden des gesamten Programms innerhalb der Schleife
möglich (exit erlaubt)
17. November 2014
Schleifenparallelisierung
9
C/C++
Schleifenstruktur
for ( var = startwert ; var op endwert ; inkr-op )
Schleifenkörper



Logischer Operator (op):
 folgende Operatoren sind erlaubt:
Inkrementoperator (inkr-op):
 müssen den Wert der Zählvariablen in jedem Schleifendurchlauf um denselben
Wert verändern
<, <=, >, >=
++var, var++, ­­var, var­­
var += inkr, var ­= inkr,
var = var + inkr,
var = inkr + var,
var = var – inkr
Schleifenvariable (var):
 muss vom Typ int sein
 ist standardmäßig private
 Wert ist unbestimmt nach Schleifendurchlauf (Ausnahme:
lastprivate)
17. November 2014
Schleifenparallelisierung
10
Laufzeitverhalten







außerhalb der Schleife führt ein Master Thread den Code
sequentiell aus
erreicht der Master Thread eine parallele Schleife, erzeugt er
0 oder mehr Slave Threads
die Schleife wird gleichzeitig von allen Threads ausgeführt
jede Iteration wird nur einmal ausgeführt
jeder Thread kann mehr als eine Iteration ausführen
Variablen sind entweder shared oder private für jeden
Thread
am Ende der parallelen Schleife findet eine BarrierSynchronisation statt


alle Threads warten aufeinander, bevor sie mit der Ausführung
des restlichen Codes im parallelen Abschnitt fortfahren, bzw. der
Master Thread mit der seriellen Ausführung fortfährt
Vermeidung der Synchronisation durch Angabe von nowait
17. November 2014
Schleifenparallelisierung
11
Scheduling
Wie werden die Iterationen einer parallelen Schleife
auf die Threads eines Teams aufgeteilt?

Die Art der Aufteilung einer parallelen Schleife in OpenMP
lässt sich durch die schedule-Klausel beeinflussen
Wie teilt man die Iterationen am effizientesten auf?
 die Wahl der richtigen Scheduling-Strategie kann das
Laufzeitverhalten einer Schleife entscheidend beeinflussen
 Ziel: Arbeitslast möglichst ausgewogen verteilen
 Idealfall: alle Threads brauchen gleich lange, um ihre
Teilaufgaben zu erfüllen
 nicht ausbalancierte Schleifen zwingen schnelle Threads auf
langsamere zu warten
 man unterscheidet statisches und dynamisches
Scheduling
17. November 2014
Schleifenparallelisierung
12
Statisches Scheduling

die Verteilung der Iterationen geschieht zu Beginn des
Schleifendurchlaufs, basierend auf:




Anzahl der Threads
Anzahl der Iterationen
Index einer Iteration
ein Thread erhält bei jeder Ausführung dieselben
Iterationen

geringe Flexibilität

so gut wie kein Scheduling-Overhead

sinnvoll bei Schleifen mit der gleichen Menge an Arbeit pro
Iteration
#pragma omp parallel for
for ( i = 0; i < n; i++ )
z[i] = a * x[i] + y;
17. November 2014
Schleifenparallelisierung
13
Dynamisches Scheduling

die Verteilung der Iterationen geschieht während der
Ausführung der Schleife






Jeder Thread bekommt eine Teilmenge der Iterationen zu Beginn
des Schleifendurchlaufs
Nach Beendigung bekommt jeder Thread weitere Iterationen
zugewiesen
welcher Thread welche Iterationen bearbeitet kann sich von
Programmausführung zu Programmausführung ändern
mehr Flexibilität (Ausgleich der Lastverteilung)
höherer SchedulingOverhead durch zusätzlichen Verwaltungsaufwand
sinnvoll bei Schleifen mit
unterschiedlicher
Arbeitslast pro Iteration
17. November 2014
#pragma omp parallel for
for ( i = 0; i < n; i++ )
if ( f(i) )
do_big_work(i);
else
do_small_work(i);
Schleifenparallelisierung
14
Schedulingstrategien (1)


Die verschiedenen Schedulingstrategien in OpenMP
unterscheiden sich dadurch:
 Wie die Iterationen einer Schleife in Blöcke (engl. chunks)
aufgeteilt werden
 Wie die Chunks den Threads eines Teams zugewiesen
werden
Syntax:
schedule(type[, chunk])

erlaubte Angaben für type:
static

dynamic

guided

runtime

auto
chunk: positiver ganzzahliger Wert, der sich durch die Schleifenausführung nicht ändern darf


17. November 2014
Schleifenparallelisierung
15
Schedulingstrategien (2)
static

ohne Angabe von chunk:



Iterationen werden in ungefähr gleich große Blöcke aufgeteilt
jeder Thread bekommt höchstens einen Block
mit Angabe von chunk:



Iterationen werden in Blöcke der Größe chunk aufgeteilt
der letzte Block kann dabei auch weniger als chunk Iterationen
enthalten
die Blöcke werden in Reihenfolge der Threadnummern reihum
an die Threads verteilt
dynamic



jeder anfragende Thread bekommt einen Block zugewiesen
wenn chunk nicht angegeben wird, ist die Blockgröße 1
der letzte Block kann auch kleiner als chunk sein
17. November 2014
Schleifenparallelisierung
16
Schedulingstrategien (3)
guided




exponentiell fallende Blockgröße (compilerabhängig)
chunk definiert die minimale Blockgröße
wird chunk nicht angegeben ist die minimale Blockgröße 1
Zuteilung erfolgt dynamisch, d. h. jeder anfragende Thread
erhält einen Block
runtime




darf nur ohne chunk-Wert angegeben werden
Schedulingstrategie wird über Umgebungsvariable definiert
und erst zur Laufzeit ermittelt
ist die Variable nicht gesetzt, ist das Scheduling abhängig
von der Implementierung
Syntax: export OMP_SCHEDULE=“type[,chunksize]”

Beispiel: export OMP_SCHEDULE=“guided,10”
17. November 2014
Schleifenparallelisierung
17
Schedulingstrategien (4)
auto


der Compiler und/oder die Laufzeitumgebung entscheidet
über das Scheduling der Schleife
der Programmierer gibt der Implementierung die Freiheit
jede mögliche Art der Aufteilung der Iterationen auf die
Threads zu wählen
17. November 2014
Schleifenparallelisierung
18
Schedulingstrategien (4)
guided, 7
3
2
1
dynamic, 7
Thread ID
0
3
2
1
0
3
static
2
1
0
0
25
50
75
100
125
150
175
200
Iterationsnummer
17. November 2014
Schleifenparallelisierung
19
Beispiel: parallele Schleife
#include <stdio.h>
#include <omp.h>
#define N 100
int main(int argc, char *argv[])
{
int iam,nthreads;
int i,a[N],b[N];
#pragma omp parallel private(iam,nthreads)
{
iam=omp_get_thread_num();
nthreads=omp_get_num_threads();
#pragma omp for schedule(dynamic,10)
for(i=0;i<N;i++) {
a[i]=iam;
}
#pragma omp for schedule(guided)
for(i=0;i<N;i++) {
b[i]=iam;
}
}
for(i=0;i<N;i++) {
printf("a[%03d]=%d , b[%03d]=%d\n",i,a[i],i,b[i]);
}
return 0;
}
17. November 2014
Schleifenparallelisierung
20
parallel for vs. parallel
Master
Master
Worker
0 ­ 15
0 ­ 15
0 ­ 15
Worker
0 ­ 15
#pragma omp parallel
{
for ( i=0; i<16; i++ )
[…]
}
17. November 2014
0 ­ 3
4 ­ 7
8 ­ 11
12 ­ 15
#pragma omp parallel
{
#pragma omp for
for ( i=0; i<16; i++ )
[…]
}
Schleifenparallelisierung
21
Reduktionen





häufig wird in Schleifen wiederholt mittels eines binären
Operators ein Wert in einer Variablen akkumuliert
solche Schreibzugriffe auf gemeinsamen Variablen
müssen in jeder Iteration synchronisiert werden
kann die Laufzeit des parallelen Programms jedoch negativ
beeinflussen
zur effizienten Durchführung solcher Operationen stellt
OpenMP deshalb die reduction-Klausel zur Verfügung
Voraussetzung: der Operator ist kommutativ und
assoziativ, so dass das Endergebnis nicht von der
Ausführungsreihenfolge der Einzeloperationen abhängt
sum = 0;
#pragma omp parallel for reduction(+:sum)
for ( i = 0; i < n; i++ )
sum = sum + b[i];
17. November 2014
Schleifenparallelisierung
22
Reduktionen: Syntax
C/C++
reduction (op: Variable [, Variable] ...)



für alle Variablen in der Liste wird in jedem Thread eine private
Kopie angelegt und mit dem entsprechenden neutralen Element
des Operators initialisiert
während der parallelen Ausführung werden
zunächst die Zwischenergebnisse in den
Erlaubte Operatoren:
privaten Instanzen akkumuliert
am Ende des parallelen Abschnitts wird
dann synchronisiert die ursprüngliche
Variable des Master Threads akkumuliert
op:

erlaubte Operatoren siehe Tabelle
Variable:

skalare Variable vom Typ shared

nicht erlaubt sind:
Arrays, Pointer und const-Variablen
17. November 2014
Schleifenparallelisierung
23
Reduktionen: Syntax
Fortran
reduction ({op | intrinsic}: Variable [, Variable] ...)
für alle Variablen in der Liste wird in jedem Thread eine private

Kopie angelegt und mit dem entsprechenden neutralen Element
des Operators initialisiert
während der parallelen Ausführung werden
zunächst die Zwischenergebnisse in den
Erlaubte Operatoren:
privaten Instanzen akkumuliert
am Ende des parallelen Abschnitts wird
dann synchronisiert die ursprüngliche
Variable des Master Threads akkumuliert


op | intrinsic:

erlaubte Operatoren siehe Tabelle
Variable:



skalare Variable vom Typ shared
nicht erlaubt sind Fortran Pointer
ALLOCATABLE-Variablen müssen allokiert sein
und dürfen während der Reduktion nicht
deallokiert werden
17. November 2014
Schleifenparallelisierung
24
Reduktionen: benutzerdefiniert
4.0
C/C++
#pragma omp declare reduction( Operatorname : Typenliste
: Kombinierer) [NeutralesElement] new-line

definiert einen neuen Operator, der wie vordefinierte
Operatoren bei Reduktionen verwendet werden kann
Operatorname:

Name des Operators
Typenliste:

Liste von gültigen Variablentypen für den Operator
Kombinierer:


Ausdruck, der beschreibt, wie zwei Teilergebnisse zusammengefügt
werden
dazu können die Variablen omp_in (private Kopie, die Teilergebnis
enthält) und omp_out (private Kopie, die Wert nach zusammenfügen
zweiter Teilergebnisse enthält) verwendet werden
NeutralesElement:

Klausel zur Bestimmung des neutralen Elementes mit der Form:

initializer(expr), wobei expr omp_priv=initializer oder

Funktionsname( Argumentenliste )
17. November 2014
Schleifenparallelisierung
25
Benutzerdefinierte Reduktion: Beispiel

4.0
neuen Operator merge definieren:
#pragma omp declare reduction (merge : std::vector<int> :
omp_out.insert(omp_out.end(), omp_in.begin(), omp_in.end()))

neuen Operator merge in einer Reduktion verwenden:
void schedule (std::vector<int> &v, std::vector<int> &filtered) {
#pragma omp parallel for reduction (merge : filtered)
for (std::vector<int>::iterator it=v.begin(); it<v.end(); it++)
If ( filter(*it)) filtered.push_back(*it);
}
17. November 2014
Schleifenparallelisierung
26
Datenabhängigkeiten und Race Condition



die Parallelisierung muss die Korrektheit eines Programms
erhalten
Datenabhängigkeiten können die Korrektheit eines
Programms beeinflussen
typische Multi-Prozessor Programmierfehler treten auf, wenn:





zwischen zwei Synchronisationspunkten zwei oder mehr
Threads gleichzeitig auf die gleiche Speicherstelle/Variable
zugreifen und
mindestens einer der Threads den Wert der Variablen
verändert und
der Zugriff nicht geregelt (z.B. durch critical) ist
in vielen Fällen fehlen Anweisungen wie private oder barrier
Datenabhängigkeiten in parallelen Programmen können eine
Wettlaufsituation oder Race Condition hervorrufen ⇒
Ergebnisse hängen von der Reihenfolge ab, in der die
Operationen ausgeführt werden
17. November 2014
Schleifenparallelisierung
27
Data Races


gleichzeitige Zugriffe auf die gleiche Variable/Speicherstelle
werden Data Races genannt
keine Synchronisation notwendig, wenn alle Zugriffe nur lesen
sind:
#pragma omp parallel for shared(a,b)
for ( i = 0; i < n; i++ ) {
a[i] = a[i] + b;
}


b ist als shared deklariert:
 b wird nicht verändert, nur gelesen
 kein Konflikt bzgl. b → keine Synchronisation notwendig
a[ ] ist als shared deklariert:
 jeder Thread modifiziert eine andere Stelle von a[ ]
 kein Konflikt bzgl. a[ ] → keine Synchronisation notwendig
17. November 2014
Schleifenparallelisierung
28
Beispiel: Data Races
#pragma omp parallel for
for ( i = 1; i =< 2; i++ )
a[i] = a[i] + a[i-1];



Anfangswerte:
a[0] = 1;
a[1] = 1;
a[2] = 1;
das Ergebnis für a[i] hängt vom Ergebnis an der Stelle i-1 ab
kein Problem bei serieller Ausführung, das a[i-1] vor a[i]
berechnet wird
bei paralleler Ausführung mit mehreren nebenläufigen
Threads ist dies nicht mehr garantiert:


a[2] wird mit dem neuen Wert von
a[1] berechnet
a[0] = 1;
a[1] = 2;
a[2] = 3;
a[2] wird mit dem alten Wert von
a[1] berechnet
a[0] = 1;
a[1] = 2;
a[2] = 2;
17. November 2014
Schleifenparallelisierung
29
Vermeidung von Abhängigkeiten
wichtig bei der Parallelisierung sind Abhängigkeiten zwischen
verschiedenen Iterationen einer parallelen Schleife

keine Abhängigkeit:

wenn auf eine Speicherstelle nur lesend zugegriffen wird

wenn auf eine Speicherstelle nur in einer Iteration zugegriffen
wird



Abhängigkeit:

wenn auf eine Speicherstelle in mehr als einer Iteration
zugegriffen wird und mehr als einmal beschrieben wird
Faustregel: eine Schleife kann ohne weiteres parallelisiert werden,

wenn alle Zuweisungen Zuweisungen zu unterschiedlichen
Feldelementen sind

wenn jedem Element höchstens in einer Iteration ein Wert
zugewiesen wird

wenn in keiner Iteration von einem Element gelesen wird, dass
in einer anderen Iteration mit Werten belegt wird
17. November 2014
Schleifenparallelisierung
30
Klassifizierung von Abhängigkeiten



Klassifizierung von Abhängigkeiten zur Entscheidung:
 ob die Abhängigkeit beseitigt werden muss
 ob die Abhängigkeit beseitigt werden kann
 welche Technik zur Beseitigung verwendet werden kann
loop-carried und non-loop-carried Dependences:
 non-loop-carried Dependences sind in vielen Fällen
ungefährlich
weitere Abhängigkeiten:
 Flow Dependence
 Anti Dependence
 Output Dependence
17. November 2014
Schleifenparallelisierung
31
Flussabhängigkeit (Flow Dependence)
for ( i = 0; i < n; i++ ) {
x = x + a[i];
}







zwei Zugriffe A1 und A2 auf die gleiche Speicherstelle x
A1 beschreibt die Speicherstelle x
A2 liest die Speicherstelle x
das Ergebnis von A1 wird über die gemeinsam genutzte
Speicherstelle x an A2 kommuniziert
Ergebnis von A1 fließt nach A2 → Flussabhängigkeit
auch echte Datenabhängigkeit (True Dependence)
genannt
die zwei Zugriffe können nicht parallel ausgeführt werden
Gegeben seien zwei Anweisungen A1 und A2, wobei A1 in der
sequentiellen Ausführung vor A2 liege
17. November 2014
Schleifenparallelisierung
32
Flussabhängigkeit (Beseitigung)
for ( i = 0; i < n; i++ ) {
x = x + a[i];
}


A2 hängt vom Ergebnis ab, das in A1 gespeichert wird
 Abhängigkeit kann nicht immer beseitigt werden
hier: Beseitigung durch Reduktion
#pragma omp parallel for reduction(+: x)
for ( i = 0; i < n; i++ ) {
x = x + a[i];
}
Gegeben seien zwei Anweisungen A1 und A2, wobei A1 in der
sequentiellen Ausführung vor A2 liege
17. November 2014
Schleifenparallelisierung
33
Gegenabhängigkeit (Anti Dependence)
for ( i = 0; i < n - 1; i++ ) {
a[i] = a[i+1] + b[i];
}



A1 liest eine Speicherstelle
A2 beschreibt die selbe Speicherstelle anschließend
Gegenteil von Flussabhängigkeit
→ Gegenabhängigkeit
Gegeben seien zwei Anweisungen A1 und A2, wobei A1 in der
sequentiellen Ausführung vor A2 liege
17. November 2014
Schleifenparallelisierung
34
Gegenabhängigkeit (Beseitigung)




Speicheradressen
disjunkt machen
erzeuge temporären
Vektor a2[] und
initialisiere mit
Werten von a[] Initialisierung von
a2[] kann ebenfalls
parallel erfolgen
Overhead:
 Speicher
 Berechnung
for ( i = 0; i < n - 1; i++ ) {
a[i] = a[i+1] + b[i];
}
#pragma omp parallel for
for ( i = 0; i < n - 1; i++ ) {
a2[i] = a[i+1];
}
#pragma omp parallel for
for ( i = 0; i < n - 1; i++ ) {
a[i] = a2[i] + b[i];
}
Gegeben seien zwei Anweisungen A1 und A2, wobei A1 in der
sequentiellen Ausführung vor A2 liege
17. November 2014
Schleifenparallelisierung
35
Ausgabeabhängigkeit (Output Dependence)
for
x
d
}
y =




( i = 0; i < n; i++ ) {
= b[i] - c[i];
= 2 * x;
x + d;
A1 und A2 beschreiben die selbe Speicherstelle
Der Schreibzugriff von A2 erfolgt im sequentiellen Programm
nach dem von A1
die Speicherstelle wird lediglich ausgegeben
→ Ausgabeabhängigkeit
hier: Ausgabeabhängigkeit von d und x
Gegeben seien zwei Anweisungen A1 und A2, wobei A1 in der
sequentiellen Ausführung vor A2 liege
17. November 2014
Schleifenparallelisierung
36
Ausgabeabhängigkeit (Beseitigung)
for
x
d
}
y =


( i = 0; i < n; i++ ) {
= b[i] - c[i];
= 2 * x;
x + d;
privatisiere Speicherstelle
kopiere den letzen Wert mit lastprivate zurück auf die
gemeinsame Variable
#pragma
for (
x =
d =
}
y = x
omp parallel lastprivate(x, d)
i = 0; i < n; i++ ) {
b[i] - c[i];
2 * x;
+ d;
Gegeben seien zwei Anweisungen A1 und A2, wobei A1 in der
sequentiellen Ausführung vor A2 liege
17. November 2014
Schleifenparallelisierung
37
Intel Inspector XE



überprüft parallele Anwendungen auf Speicherfehler, DataRaces und Deadlocks
Finden solcher Fehler mit herkömmlichen Methoden
schwierig
unterstützte Betriebssysteme:



Linux (32 bit und 64 bit)
Windows (32 bit und 64 bit)
unterstützte Thread Technologien:




POSIX
OpenMP
Intel Threading Building Blocks (TBB)
WIN32-Threads
Eine parallele Anwendung sollte vor dem Produktiveinsatz immer mit einem solchen Tool überprüft
werden!
17. November 2014
Schleifenparallelisierung
38
Intel Inspector XE: Benutzung (JUDGE)


kann komplett auf Login-Knoten durchgeführt werden
Grafische Benutzerschnittstelle (inspxe-gui) und
Kommandozeilenschnittstelle (inspxe-cl) vorhanden
1. Modul laden:
module load inspector
2. Programm mit Option -g übersetzen
(evtl. Optimierung ausschalten):
icc -openmp -g -O0 -o my_proc my_proc.c
3. Inspector (GUI) starten:
inspxe-gui
Generelle Informationen zum Intel Inspector XE:
module help inspector
17. November 2014
Schleifenparallelisierung
39
Intel Inspector XE: Beispiel (race.c)
#include <stdio.h>
#include <omp.h>
#define SIZE 20
int main(int argc, char *argv[])
{
int i;
int a[SIZE];
int sum=0;
for (i=0; i<SIZE; ++i)
a[i] = i;
#pragma omp parallel for
for (i=0; i<SIZE; ++i)
sum = sum + a[i];
printf("Summe:
}
17. November 2014
%d\n",sum);
return 0;
Schleifenparallelisierung
40
Intel Inspector XE – Projekt anlegen (1)
17. November 2014
Schleifenparallelisierung
41
Intel Inspector XE – Projekt anlegen (2)
Sicherstellen, dass
mehrere Threads
verwendet werden

Bei größeren Programmen, möglichst kleine Konfigurationen
verweden, da die Ausführungszeit deutlich (10 – 1000 mal) länger ist
17. November 2014
Schleifenparallelisierung
42
Intel Inspector XE – Analyse konfigurieren
1. Neue Analyse
auswählen
2. Analyse-Typ
wählen
17. November 2014
3. Analyseumfang wählen
(je genauer desto mehr
Overhead)
Schleifenparallelisierung
4. Analyse
starten
43
Intel Inspector XE – Ergebnisse (1)
Doppelklick auf den Fehler öffnet einen Editor mit dem Quelltext
17. November 2014
Schleifenparallelisierung
44
Intel Inspector XE – Ergebnisse (2)
17. November 2014
Schleifenparallelisierung
45
Übung
Übung 11:
Schleifenparallellisierung
17. November 2014
Schleifenparallelisierung
46
Einführung in die Parallelprogrammierung
OpenMP Teil 3:
Worksharing-Konstrukte
Annika Hagemeier
Jülich Supercomputing Centre (JSC)
Forschungszentrum Jülich
Inhalt

Aufteilung der Arbeit in parallelen Regionen




Datenumgebung






Parallele Sektion (sections-Direktive)
single-Direktive
copyprivate-Klausel
Verwaiste Konstrukte
threadprivate-Direktive
Grundregeln
Tasks: parallele Aufgaben
Verschachtelte parallele Abschnitte
Parallele Ausführung kontrollieren
18. November 2014
Worksharing-Konstrukte
2
Motivation



bislang nur Parallelisierung von Schleifen
 iterative Berechnungen
 relativ einfach zu parallelisieren
außerdem kennen gelernt: parallel-Direktive
 ermöglicht die parallele Ausführung eines beliebigen
Code-Abschnitts
 nicht-iterative, unabhängige Aufgaben
in diesem Kapitel:
 weitere Techniken zur Parallelisierung nicht-iterativer,
unabhängiger Aufgaben
 Techniken zur Aufteilung der Aufgaben auf mehrere
Threads
18. November 2014
Worksharing-Konstrukte
3
Aufteilung der Arbeit




Manuelle Aufteilung
 erzeugen einer Kette von Aufgaben, die parallel
abgearbeitet werden können
Aufteilung nach Thread-Nummer
 Anhand ihrer Nummer werden den Threads Aufgaben
zugewiesen
Worksharing Konstrukte
 for
 sections
 single
Task-Konstrukt
 Parallelisierung von Operationen auf komplexen
Datenstrukturen wie Listen und Bäume
18. November 2014
Worksharing-Konstrukte
4
Manuelle Aufteilung der Arbeit (1)

gemeinsame Datenstruktur mit einer Liste von Aufgaben

alle Aufgaben können gleichzeitig ausgeführt werden


z. B. Rendern eines Bildausschnitts
Threads eines Teams fordern wiederholt neue Aufgaben an,
bis alle Aufgaben abgearbeitet wurden
int main(int argc, char* argv[]) {
int my_index;
#pragma omp parallel private(my_index)
{
my_index = get_next_task();
while ( my_index != -1 ) {
process_task(my_index);
my_index = get_next_task();
}
}
}
18. November 2014
Worksharing-Konstrukte
5
Manuelle Aufteilung der Arbeit (2)

der Zugriff auf die Aufgabenliste muss synchronisiert
werden, damit jede Aufgabe nur einmal verteilt wird
int index;
int get_next_task() {
#pragma omp critical
if ( index == MAX_INDEX ) {
return -1;
} else {
index++;
return index;
}
}
18. November 2014
Worksharing-Konstrukte
6
Aufteilung nach Thread-Nummer

mit Hilfe von Laufzeitroutinen kann jeder Thread seine
eigene Nummer (Id) und die Anzahl der Threads im Team
bestimmen

damit lässt sich die Arbeit in (gleichgroße) Blöcke zerlegen

jedem Thread wird ein Block zugewiesen
#pragma omp parallel private(nthreads, iam, chunk, start, end)
{
nthreads = omp_get_num_threads();
iam
= omp_get_thread_num();
chunk
= (n + (nthreads - 1))/nthreads;
start
= iam * chunk;
end
= n < (iam + 1) * chunk ? n : (iam + 1) * chunk;
for ( i = start; i < end; i++ )
do_work(i);
}
18. November 2014
Worksharing-Konstrukte
7
Worksharing-Konstrukte

manuelle Aufteilung der Arbeit kann mühsam sein



Implementierung der Berechnungen für die Arbeitsaufteilung
Neuschreiben des Code-Abschnitts
Worksharing-Konstrukte bieten eine automatische
Verteilung der Arbeit

Aufteilung von Schleifeniterationen
⇒ for-Direktive (bereits behandelt)

Aufteilung von unterschiedlichen Code-Abschnitten
⇒ sections-Direktive

Kennzeichnung von Code-Abschnitten, die nur von einem
Thread bearbeitet werden dürfen
⇒ single-Direktive
18. November 2014
Worksharing-Konstrukte
8
Grundregeln: Kollektives Ausführen


die Reihenfolge der Worksharing-Konstrukte und barrierDirektiven muss die gleiche sein für jeden Thread
jeder Thread muss an allen Worksharing-Konstrukten
teilnehmen



wenn ein Thread ein Konstrukt erreicht, müssen auch alle
anderen Threads dieses erreichen
wenn ein Thread mehrere Worksharing-Konstrukte in einer
parallelen Region ausführt, müssen alle anderen Threads diese in
der gleichen Reihenfolge ausführen
wenn ein Thread ein Worksharing-Konstrukt überspringt, müssen
auch alle anderen Threads dieses überspringen
#pragma omp parallel
{
if (omp_get_thread_num() != 0) /* nicht erlaubt */
#pragma omp for
for (i=0; i<n; i++)
[…];
}
18. November 2014
Worksharing-Konstrukte
9
Grundregeln: Schachtelung

Das Schachteln von Worksharing-Konstrukten ist in
OpenMP nicht erlaubt



wenn ein Thread innerhalb eines Worksharing-Konstruktes auf
ein anderes Worksharing-Konstrukt stößt, ist das Verhalten
undefiniert
Parallelisierung von geschachtelten Schleifen möglich
 Iterationen manuell verteilen
 geschachtelte parallele Regionen
keine barrier-Direktive innerhalb von WorksharingKonstrukten
#pragma omp
for (i=0;
{
#pragma omp
for
a
}
18. November 2014
for
i<n; i++)
for
(j=0; j<m; j++)
= […];
/* nicht erlaubt! */
Worksharing-Konstrukte
10
Parallele Sektion



enthält Anweisungsblöcke, die parallel zueinander
bearbeitet werden können
Anweisungsblöcke sind unabhängig, d. h. kein Block hängt
von den Ergebnissen eines anderen Blocks ab
jeder Block wird einmal von genau einem Thread des
Teams bearbeitet




dabei ist nicht festgelegt, welcher Thread welchen Block
bearbeitet
eventuell kann sogar ein Thread alle Blöcke bearbeiten
nützlich, wenn einzelne Task zu klein sind um sie effizient zu
parallelisieren
die Ausführungsreihenfolge der Blöcke einer Sektion ist nicht
definiert

OpenMP bietet keine Möglichkeit die Abarbeitungsreihenfolge
der Blöcke zu kontrollieren
18. November 2014
Worksharing-Konstrukte
11
Parallele Sektion: Syntax
#pragma omp sections [Klausel[[,] Klausel] ...] new-line
{
[#pragma omp section new-line]
strukturierter Block
[#pragma omp section new-line
strukturierter Block]
...
}
Klauseln:





private (list)
firstprivate (list)
lastprivate (list)
reduction (operator: list)
nowait
18. November 2014
Worksharing-Konstrukte
12
Parallele Sektion: Syntax (Kurzform)
#pragma omp parallel sections [Klausel[[,] Klausel] ...] new-line
{
[#pragma omp section new-line]
strukturierter Block
[#pragma omp section new-line
strukturierter Block]
...
}



genau wie beim Schleifenkonstrukt lassen sich #pragma omp
parallel und #pragma omp sections zu einer Direktive
zusammenfassen
jedoch nur, wenn die parallele Region nur aus einer
parallelen Sektion besteht
alle für parallel und sections gültigen Klauseln sind
erlaubt
18. November 2014
Worksharing-Konstrukte
13
Parallele Sektion: Bemerkungen (1)




das gesamte parallele Konstrukt wird von
#pragma omp sections
umschlossen
jede Teilaufgabe wird zu einem Codeblock zusammengefasst und mit
#pragma omp section
umschlossen (ohne “s”)
bei der ersten Teilaufgabe ist die Angabe von #pragma omp
section optional
nowait:


Threads werden am Ende eines parallelen Konstrukts
synchronisiert
nowait verhindert die implizite Synchronisation am Ende der
parallelen Sektion
18. November 2014
Worksharing-Konstrukte
14
Parallele Sektion: Bemerkungen (2)

firstprivate­Variablen:
werden beim Eintritt in die parallele Region initialisiert (von
jedem Thread)

ein Thread kann mehrere Blöcke einer Sektion bearbeiten
⇒ der Wert einer firstprivate-Variable kann sich vom Wert der
entsprechenden shared-Variable zu Beginn eines Blocks
unterscheiden


lastprivate-Variablen:


der Thread, der den lexikalisch letzten Block ausführt, kopiert
den Wert der Variablen auf die entsprechende shared-Variable
Reduktions-Variablen:

nachdem ein Thread alle ihm zugewiesenen Blöcke abgearbeitet
hat verbindet er seine private Kopie der Variablen mit der
gemeinsamem Kopie
18. November 2014
Worksharing-Konstrukte
15
Parallele Sektion: Beispiel
#include <stdio.h>
#define N 100
int main(int argc, char *argv[])
{
int iam,nthreads;
int i,a[N],b[N];
#pragma omp parallel sections
{
#pragma omp section
for( i=0; i<N; i++ ) {
a[i]=100;
}
#pragma omp section
for( i=0; i<N; i++ ) {
b[i]=200;
}
}
return 0;
}
18. November 2014
Worksharing-Konstrukte
16
single-Direktive

innerhalb eines parallelen Abschnitts besteht oftmals die
Notwendigkeit, bestimmte Anweisungen sequentiell
auszuführen, ohne deswegen den parallelen Abschnitt ganz
zu verlassen





z. B. Daten einlesen oder ausgeben
die single-Direktive sorgt dafür, dass der eingeschlossene
Codeblock von genau einem Thread des Teams
durchlaufen wird
der Thread, der den Codeblock durchläuft muss dabei nicht
unbedingt der Master-Thread sein
alle Threads führen eine Barrier-Synchronisation am Ende
des Konstruktes aus, falls nicht nowait angegeben wird
der single-Abschnitt muss folglich von jedem Thread
erreicht werden
18. November 2014
Worksharing-Konstrukte
17
single-Direktive: Syntax
#pragma omp single [Klausel[[,] Klausel] ...] new-line
strukturierter Block
Klauseln:




private (list)
firstprivate (list)
copyprivate (list)
nowait
18. November 2014
Worksharing-Konstrukte
18
single-Direktive: Beispiel




beim Erreichen der single-Direktive wird ein beliebiger
Thread ausgewählt, der den Block bearbeitet
die Richtigkeit des Programms darf nicht von der Wahl
eines bestimmten Threads abhängen
die übrigen Threads
warten am Ende des
Blocks, bis der Thread,
der den Block ausführt,
ebenfalls das Ende des
Blocks erreicht hat
wird ein nowait
angegeben, findet keine
Barrier-Synchronisation
am Ende des Blocks statt
18. November 2014
#pragma omp parallel
{
#pragma omp for
for(i=0; i<n; i++)
[…]
#pragma omp single
write_intermediate_result();
#pragma omp for
for(i=0; i<n; i++)
[…]
}
Worksharing-Konstrukte
19
copyprivate-Klausel



darf nur an eine single-Direktive angehängt werden
kopiert den Wert der privaten Variable(n) des Threads, der
den single-Block ausgeführt hat, auf die privaten Kopien
der anderen Threads im Team (Broadcast)
copyprivate kann also
den Wert einer privaten
Variable nach Ausführung eines singleBlocks den anderen
Threads im Team
mitteilen, ohne dass ein
Umweg über eine
gemeinsam genutzte
Variable genommen
werden muss
18. November 2014
double x;
#pragma omp threadprivate(x)
void init() {
double a;
#pragma omp single copyprivate(a,x)
{
scanf(“%lf”, &a);
scanf(“%lf”, &x);
}
use_values(a,x);
}
Worksharing-Konstrukte
20
Warum ist single ein Worksharing-Konstrukt?






der eingeschlossene Codeblock wird nur einmal ausgeführt
vergleichbar mit einer sections-Direktive, die nur einen
Block enthält
muss von allen Threads im Team erreicht werden
jeder Thread muss alle Worksharing-Konstrukte in der
gleichen Reihenfolge erreichen, auch die single-Direktive
implizite Barrier-Synchronisation am Ende des Konstruktes
Barrier-Synchronisation kann durch nowait vermieden
werden
18. November 2014
Worksharing-Konstrukte
21
Ausdehnung eines parallelen Konstrukts



Lexikalische Ausdehnung
 alle Statements, die lexikalisch eingeschlossen sind
Dynamische Ausdehnung
 beinhaltet auch Statements in Funktionen, die innerhalb
eines parallelen Konstruktes aufgerufen werden
Verwaiste (engl. orphaned) Direktiven
 Direktiven, die nicht in der lexikalischen, sondern in der
dynamischen Ausdehnung eines Konstruktes liegen
 erlaubt die parallele Ausführung eines Programms mit
minimalen Änderungen der seriellen Version
18. November 2014
Worksharing-Konstrukte
22
Datenumgebung




lexikalische oder statische Ausdehnung:
 Quelltext, der lexikalisch eingeschlossen ist
dynamische Ausdehnung:
 lexikalische Umgebung und zusätzlich der Quelltext der
Funktionen die in der lexikalischen Umgebung aufgerufen
werden
lexikalische Ausdehnung ist
eine Teilmenge der
dynamischen Ausdehnung
Datenklauseln (private,
shared, ...) beziehen sich
immer auf die lexikalische
Ausdehnung
18. November 2014
void subroutine();
{
printf(“Hello world!\n”);
}
int main(int argc, char* argv[])
{
#pragma omp parallel
{
subroutine();
}
}
Worksharing-Konstrukte
23
Verwaiste Worksharing-Konstrukte (1)



Worksharing-Konstrukte
können überall in der
dynamischen Umgebung
einer parallelen Region
auftreten
Worksharing-Konstrukte,
die sich nicht in der lexikalischen Umgebung befinden, werden verwaiste
Konstrukte (engl.
orphaned) genannt
Benutzer einer Funktion
sollten sich der Anwesenheit eines WorksharingKonstruktes bewusst sein
18. November 2014
void initialize(double a[], int
n) {
int i;
#pragma omp for
for (i=0; i<n; i++)
a[i] = 0.0;
}
int main(int argc, char* argv[])
{
#pragma omp parallel
{
initialize(a,n);
[…]
}
}
Worksharing-Konstrukte
24
Verwaiste Worksharing-Konstrukte (2)
Aufruf innerhalb einer parallelen Region:
 fast das gleiche Verhalten als läge das Konstrukt in der
lexikalischen Umgebung
 geringe Unterschiede bestehen lediglich in der Bindung
der Daten
Aufruf innerhalb eines seriellen Programmteils:
 fast das gleiche Verhalten als gäbe es keine WorksharingDirektive
 ein serieller Thread verhält sich wie ein paralleles Team
bestehend aus nur einem Master Thread
 kann gefahrlos aus seriellem Code aufgerufen werden
(Direktive wird ignoriert)
 geringe Unterschiede bestehen lediglich in der Bindung
der Daten
18. November 2014
Worksharing-Konstrukte
25
Verwaiste Konstrukte: Datenumgebung




Datenklauseln einer parallelen Region beziehen sich nur auf
die lexikalische Umgebung
globale Variablen (C/C++) und Variablen in commonBlöcken (Fortran) sind standardmäßig gemeinsam
unabhängig von den Datenklauseln der einschließenden
parallelen Region
automatische Variablen der Funktion, die das verwaiste
Konstrukt enthält sind immer privat
Parameter der Funktion, die das verwaiste Konstrukt
enthält haben die Bindung, die der entsprechenden Variablen
vor Funktionsaufruf zugewiesen wurde
18. November 2014
Worksharing-Konstrukte
26
Datenumgebung: Beispiel (1)

my_start und my_end sind nicht private in work()
int my_start, my_end;
void work() {
/* my_start and my_end are undefined */
printf("My subarray is from %d to %d\n",
my_start, my_end);
}
int main(int argc, char* argv[]) {
#pragma omp parallel private(my_start, my_end)
{
/* get subarray indices */
my_start = get_my_start(omp_get_thread_num(),
omp_get_num_threads());
my_end
= get_my_end(omp_get_thread_num(),
omp_get_num_threads());
work();
}
}
18. November 2014
Worksharing-Konstrukte
27
Datenumgebung: Beispiel (2)


Lösung: Variablen als Parameter übergeben
⇒ lästig für lange Parameterlisten
bessere Lösung: threadprivate-Direktive
int my_start, my_end;
void work(int my_start, int my_end) {
printf("My subarray is from %d to %d\n",
my_start, my_end);
}
int main(int argc, char* argv[]) {
#pragma omp parallel private(my_start, my_end)
{
my_start = […]
my_end
= […]
work(my_start, my_end);
}
}
18. November 2014
Worksharing-Konstrukte
28
threadprivate-Direktive (1)

my_start und my_end sind jetzt auch in work() private
int my_start, my_end;
#pragma omp threadprivate(my_start, my_end)
void work() {
printf("My subarray is from %d to %d\n",
my_start, my_end);
}
int main(int argc, char* argv[]) {
#pragma omp parallel
{
my_start = […]
my_end
= […]
work();
}
}
18. November 2014
Worksharing-Konstrukte
29
threadprivate-Direktive (2)







deklariert eine Variable für einen Thread als private für das
gesamte Programm
die Variable wird einmal zu einem unspezifizierten Zeitpunkt
im Programm initialisiert, jedoch bevor eine Kopie der
Variablen zum ersten Mal referenziert wird
der Wert der Variablen bleibt über mehrere parallele
Regionen hinaus erhalten, solange die Anzahl der Threads
konstant bleibt
Threads mit der gleichen Thread-Nummer erhalten
dieselbe Kopie der Variablen
threadprivate-Variablen dürfen in keiner Daten-Parameterliste erscheinen, ausgenommen copyin und copyprivate
die threadprivate-Direktive darf erst nach der Deklaration
der Variablen angewendet werden
es gelten noch einige andere Einschränkungen (siehe
Spezifikation)
18. November 2014
Worksharing-Konstrukte
30
copyin-Klausel

jeder Thread hat seine eigene Kopie von threadprivateVariablen während der gesamten Programmlaufzeit



ein Thread hat nicht die Möglichkeit auf die private Kopie eines
anderen Thread zuzugreifen
die copyin-Klausel kopiert den Wert vom Master Thread auf die
private Kopie jedes Threads, der in die parallele Region eintritt
Variablen in der copyin-Klausel müssen threadprivateVariablen sein
int c;
#pragma omp threadprivate(c)
int main(int argc, char* argv[]) {
c = 2;
#pragma omp parallel copyin(c)
{
/* c has value 2 in all thread-private copies */
[…] = c;
}
}
18. November 2014
Worksharing-Konstrukte
31
Tasks in OpenMP
Motivation:


Parallelisierung von Operationen auf komplexeren
Datenstrukturen wie Listen und Bäumen in OpenMP
grundsätzlich möglich
erfordert aber meist umfangreiche Änderungen im Code
⇒ entspricht jedoch nicht dem Prinzip der inkrementellen
Parallelisierung
⇒ meist mit sehr hohem Verwaltungsaufwand zur
Laufzeit verbunden
Tasks in OpenMP:


Tasks in OpenMP dienen der Parallelisierung irregulärer
Daten- und Kontrollstrukturen
erst seit OpenMP 3.0 Teil des Standards
18. November 2014
Worksharing-Konstrukte
32
Tasks



Task = Codeblock, dessen Anweisungen unabhängig sind
und sequentiell abgearbeitet werden (vgl. sections)
direkte oder verschobene Ausführung der Arbeit von
einem Thread des Teams
ein Task besteht aus:





einem Codeblock der ausgeführt werden soll
der entsprechenden Datenumgebung
internen Kontrollvariablen
kann an einen bestimmten Thread gebunden werden (nur
dieser Thread kann den Task ausführen
nützlich zur Parallelisierung von



while-Schleifen
rekursiven Algorithmen
Operationen auf komplexeren Datenstrukturen, wie Listen oder
Bäumen
18. November 2014
Worksharing-Konstrukte
33
Tasks: Syntax
#pragma omp task [Klausel[[,] Klausel] ...] new-line
{
strukturierter Block
}
Klauseln:









if (scalar-expression)
final (scalar-expression)
untied
default (shared | none)
mergeable
private (list)
firstprivate (list)
shared (list)
4.0 depend (dependence-type: list)
18. November 2014
Worksharing-Konstrukte
34
Tasks: Datenumgebung

Es gelten die allgemeinen impliziten Regeln:




C/C++
Schleifenvariablen eines for oder parallel for-Konstruktes sind
private
Automatische Variablen, die innerhalb eines Konstruktes
deklariert werden sind private
Globale und statische Variablen sind shared
Ist die Bindung der Daten nicht explizit (durch Klauseln)
bestimmt, gilt außerdem:
Variablen in verwaisten task-Konstrukten sind firstprivate

Variablen in nicht-verwaisten task-Konstrukten erben das
shared-Attribut aus dem umgebenen Kontext
⇒ Variablen sind firstprivate, falls sie nicht das shared-Attribut
erben

Tipp: Bei Unsicherheit default(none) verwenden und alle
Bindungen explizit setzen!
18. November 2014
Worksharing-Konstrukte
37
Datenumgebung: Beispiel
int a;
void bar(int f, int g) {
#pragma omp task
{
f = ?;
g = ?;
}
}
void foo() {
int b, c, g;
#pragma omp parallel private(b) shared(g)
{
int d, f;
bar(f, g);
#pragma omp task
{
int e;
a
b
c
d
e
=
=
=
=
=
?;
?;
?;
?;
?;
}}}
18. November 2014
Worksharing-Konstrukte
38
Datenumgebung: Beispiel (Lösung)
int a;

gem
void bar(int f, int g) {
#pragma omp task
{
f = firstprivate;
g = firstprivate;
}
}
void foo() {
int b, c, g;
#pragma omp parallel private(b) shared(g)
{
int d, f;
bar(f, g);
#pragma omp task
{
int e;
a
b
c
d
e
=
=
=
=
=
shared;
firstprivate;
shared;
firstprivate;
private;
}}}
18. November 2014
Worksharing-Konstrukte
39
Task-Typen (1)



Tied Task (tied = gebunden):
Task, dessen Ausführung nur von dem Thread wieder
aufgenommen werden kann, der sie auch zurückgestellt hat
⇒ der Task ist an einen Thread gebunden
Untied Task (untied = ungebunden):
Task, dessen Ausführung verschoben wurde, kann von
jedem beliebigen Thread wieder aufgenommen werden
⇒ der Task ist an keinen Thread gebunden
Undeferred Task (undeferred = nicht aufgeschoben):
Task, dessen Ausführung nicht verschoben wird (im Hinblick
auf den generierenden Task) der generierende Task ist
verschoben, solange bis der Undeferred Task ausgeführt
wurde
⇒ die Ausführung des Tasks darf nicht verschoben werden
18. November 2014
Worksharing-Konstrukte
40
Task-Typen (2)



Included Task (included = eingeschlossen):
Task, dessen Ausführung sequentiell in den generierenden
Task eingeschlossen ist
der Task ist undeferred und wird direkt vom ankommenden
Thread ausgeführt
Merged Task (merged = vermischt):
Task, dessen Datenumgebung, einschließlich der ICVs
(Internal Control Variables), mit der des generierenden Tasks
übereinstimmt
Final Task (final = letzter):
Task, der alle Kind-Tasks dazu zwingt, final und included zu
sein, d.h. es werden keine neuen eigenständigen (mit
eigener Datenumgebung) Tasks mehr erzeugt
18. November 2014
Worksharing-Konstrukte
41
Klauseln der task-Direktive (1)


if (Bedingung)
 ist die Bedingung false, so muss der Thread sofort mit
der Ausführung des neuen Tasks beginnen
 führt er bereits einen anderen Task aus, so wird dessen
Ausführung ausgesetzt und erst wieder aufgenommen,
wenn die Ausführung des inneren Tasks beendet ist
untied
 kontrolliert, welche Threads die Ausführung eines
ruhenden Tasks an einem Scheduling-Punkt wieder
aufnehmen dürfen
 normalerweise wird ein verschobener Task von dem
Thread fortgeführt, der ihn gestartet hat
 mit der Option untied kann jeder andere Thread mit der
Ausführung nach einer Aufschiebung des Tasks fortfahren
18. November 2014
Worksharing-Konstrukte
42
Klauseln der task-Direktive (2)


final (Bedingung)
 ist die Bedingung true, so ist der erzeugte Task ein Final
Task
 alle Task-Konstrukte, die währen der Ausführung eines
Final Tasks erreicht werden, erzeugen Final Tasks oder
Included Tasks
⇒ es werden keine neuen eigenständigen (mit eigener
Datenumgebung) Tasks mehr erzeugt
mergeable
 ist die mergeable-Klausel angegeben und ist der
generierte Task ein Undeferred oder Included Task, dann
kann ein Merged Task erzeugt werden, anstatt eines
eigentständigen Tasks
Ziel: Vermeidung von Overhead durch Datenumgebungen
18. November 2014
Worksharing-Konstrukte
43
Klauseln der task-Direktive (3)

4.0
depend (dependence-type : list)
 Definiert Abhängigkeiten zwischen Geschwister-Tasks

dependence-type ist entweder in, out oder inout und
list ist eine Liste von Variablen:


in: der generierte Task hängt von allen vorher generierten
Geschwister-Tasks ab, die eine der Variablen in einer out
oder inout-Klausel referenzieren
out oder inout: der generierte Task hängt von allen vorher
generierten Geschwister-Tasks ab, die eine der Variablen in
einer in, out oder inout-Klausel referenzieren
Beispiel:
#pragma omp task depend (out: a)
{...}
#pragma omp task depend (out: b)
{...}
#pragma omp task depend (in: a,b)
{...}
18. November 2014


die ersten beiden Tasks können
parallel ausgeführt werden
der dritte Task kann erst starten,
wenn Ausführung der ersten
beiden Tasks abgeschlossen ist
Worksharing-Konstrukte
44
Tasks: Regeln







trifft ein Thread auf ein task-Konstrukt, so wird ein Task des
Codeblocks erzeugt
der Thread kann den Task sofort selbst ausführen oder
seine Ausführung verschieben
bei Verschieben des Task, wird dieser später durch einen
beliebigen Thread aus dem Team ausgeführt
die Daten-Umgebung eines Tasks wird entsprechend der
Data-Sharing Parameter angelegt
an Scheduling-Punkten im Programm kann ein Thread die
Ausführung eines Tasks A unterbrechen und stattdessen
einen anderen Task B ausführen
zur Sicherstellung, dass Tasks ausgeführt wurden muss
synchronisiert werden (taskwait)
Task-Direktiven können geschachtelt werden

der innen liegende Task zählt nicht als Teil des äußeren Task,
sondern wird als Kind-Task (oder Child-Task) bezeichnet
18. November 2014
Worksharing-Konstrukte
45
Tasks: Taskwait
#pragma omp taskwait new-line




ähnliche Bedeutung wie eine Barrier-Synchronisation
wartet auf die Fertigstellung von Kind-Tasks, die seit dem
Beginn des aktuellen Tasks generiert wurden
die Ausführung der aktuellen Task-Region (über den mit
taskwait markierten Punkt hinaus) wird solange
ausgesetzen, bis die Ausführung aller Kind-Tasks beendet ist
ist ein impliziter Task-Scheduling-Punkt in der aktuellen TaskRegion
18. November 2014
Worksharing-Konstrukte
46
4.0
Tasks: Taskgroup
#pragma omp taskgroup new-line
{
strukturierter Block
}




ähnliche Bedeutung wie taskwait
wartet auf die Fertigstellung von Kind-Tasks und
Geschwister-Tasks, die seit dem Beginn des aktuellen
Tasks generiert wurden
die Ausführung der aktuellen Task-Region (über den mit
taskwait markierten Punkt hinaus) wird solange
ausgesetzen, bis die Ausführung aller Kind- und GeschwisterTasks beendet ist
Unterschied zu taskwait: taskwait wartet nur auf KindTasks
18. November 2014
Worksharing-Konstrukte
47
Tasks: Taskyield
#pragma omp taskyield new-line


Spezifiziert, dass die Ausführung des aktuellen Tasks
zugunsten eines anderen Tasks verschoben werden kann
Einschränkungen bzgl. der Plazierung im Programm:
 Darf nur an Stellen verwendet werden, an denen
Statements der Basissprache erlaubt sind
 Darf nicht vor einem if, while, do, switch oder label
verwendet werden
18. November 2014
Worksharing-Konstrukte
48
Task Scheduling


wann immer ein Thread einen Task-Scheduling-Punkt
erreicht, kann er einen Task-Switch (Wechsel des gerade
ausgeführten Tasks) durchführen:
 Starten der Ausführung eines neuen Tasks
 Wiederaufnahme eines anderen Tasks
Task-Scheduling-Punkte:
 Eintreten in eine Task-Region (direkt nach Generierung
eines neuen Tasks)
 nach Abarbeitung der letzten Instruktion einer TaskRegion (nach Beendigung eines Tasks)
 jeder Punkt innerhalb eines Tasks mit der untied-Klausel
 taskwait-Direktiven
 implizite und explizite Barrieren
18. November 2014
Worksharing-Konstrukte
49
Tasks: Unterschiede
Tasks unterscheiden sich von anderen parallelen Konstrukten
(sections, parallel, for, ...):

Tasks sind für die Dauer ihrer Ausführung nicht an genau
einen ausführenden Thread gebunden


ein Task kann zu verschiedenen Zeitpunkten von
verschiedenen Threads ausgeführt werden
jeder Task hat seinen eigenen privaten Datenbereich


führt ein Thread einen Task aus, so geschieht das unter
Verwendung des Datenbereichs des Tasks (nicht im
Datenbereich des Threads)
der Inhalt des Task-Datenbereichs bleibt bis zur
Beendigung des Tasks erhalten
18. November 2014
Worksharing-Konstrukte
50
Tasks: Beispiel Baumtraversierung
struct node {
struct node *left;
struct node *right;
};
extern void process(struct node *);
void traverse( struct node *p ) {
if (p->left)
#pragma omp task
/* p is firstprivate by default */
traverse(p->left);
if (p->right)
#pragma omp task
/* p is firstprivate by default */
traverse(p->right);
process(p);
}
int main (void){
...
#pragma omp parallel
{
#pragma omp single
traverse(p);
}
...
}
18. November 2014
Worksharing-Konstrukte
51
Tasks: Beispiel Baumtraversierung (Postorder)
struct node {
struct node *left;
struct node *right;
};
extern void process(struct node *);
void traverse( struct node *p ) {
if (p->left)
#pragma omp task
/* p is firstprivate by default */
traverse(p->left);
if (p->right)
#pragma omp task
/* p is firstprivate by default */
traverse(p->right);
#pragma omp taskwait
process(p);
}
int main (void){
...
#pragma omp parallel
{
#pragma omp single
traverse(p);
}
...
}
18. November 2014
Worksharing-Konstrukte
52
Geschachtelte Parallele Regionen (1)


haben bis jetzt nur Worksharing-Konstrukte innerhalb
paralleler Regionen betrachtet
OpenMP erlaubt auch mehrere parallele Abschnitte
ineinander zu verschachteln (engl. nested parallelism)



wird ein paralleler Abschnitt von einem Team von Threads
ausgeführt, so können die Threads wiederum auf ein #pragma
omp parallel stoßen
in diesem Fall, wird für jeden Thread, der die innere parallele
Region erreicht ein neues Team von Threads gestartet, das den
inneren parallelen Abschnitt
#pragma omp parallel
ausführt
{
standardmäßig wird die Aussome_work();
führung der inneren parallelen
#pragma omp parallel
{
Region serialisiert
some_other_work();
⇒ das Team enthält nur einen
}
}
Thread mit Nummer 0 (Master
Thread)
18. November 2014
Worksharing-Konstrukte
53
Geschachtelte Parallele Regionen (2)
Bindung:
 alle Worksharing-Konstrukte beziehen sich auf die nächste
einschließende parallele Direktive
 die Synchronisations-Konstrukte barrier and master beziehen sich auf die nächste einschließende parallele Direktive
Funktionen:
 omp_set_nested (int nested)


nested = 0: die verschachtelte parallele Region wird serialisiert
und mit nur einem Thread ausgeführt
nested ≠ 0: mehr als ein Thread kann zur Bearbeitung der
parallelen Region erzeugt werden



Anzahl der Threads abhängig von der Implementierung
parallele Region kann immer noch serialisiert werden
omp_get_nested ()

liefert den Wert von nested zurück
18. November 2014
Worksharing-Konstrukte
54
Geschachtelte Parallele Regionen (3)
Umgebungsvariable:

export OMP_NESTED=TRUE/FALSE
Abfragefunktionen:
 omp_get_level()


omp_ancestor_thread_num(level)


gibt zur Schachtelungstiefe level des aktuellen Threads die
Thread-ID des Vorfahren oder des aktuellen Threads zurück
omp_get_team_size(level)


gibt die Anzahl der geschachtelten parallelen Regionen zurück, in
der sich der aufrufende Thread befindet
gibt zur Schachtelungstiefe level die Größe des Teams zurück,
zu dem der aktuelle Thread selbst oder sein Vorfahr gehört
omp_get_active_level()

gibt die Anzahl der aktiven geschachtelten parallelen Regionen
zurück, in der sich der aufrufende Thread befindet
18. November 2014
Worksharing-Konstrukte
55
Geschachtelte Parallele Regionen: Beispiel
void process_task(int k) {
int i;
#pragma omp parallel for
for (i=0; i<n; i++)
a[k][i] = 3.0;
}
int main(int argc, char* argv[]) {
int my_index;
#pragma omp parallel private(my_index)
{
my_index = get_next_task();
while ( my_index != -1 ) {
process_task(my_index);
my_index = get_next_task();
}
}
}
18. November 2014
Worksharing-Konstrukte
56
Übung
Übung 12:
Worksharing-Konstrukte
18. November 2014
Worksharing-Konstrukte
57
Einführung in die Parallelprogrammierung
OpenMP Teil 4:
Synchronisation
Annika Hagemeier
Jülich Supercomputing Centre (JSC)
Forschungszentrum Jülich
Inhalt



Data Races
Synchronisationsarten
 Gegenseitiges Ausschließen
 Kritische Region (critical)
 Atomare Anweisung (atomic)
 Locks
 Sychronisation von Ereignissen
 Barrieren (barrier)
 Geordnete Ausführung (ordered)
 master-Direktive
 Sychronisation über Speicherstellen (flush)
Effiziente Parallelisierung
 Tipps zur effizienten Parallelisierung
18. November 2014
Synchronisation
2
Wettlaufsituationen (Race Condition)
wird eine Aufgabe von mehreren Threads gemeinsam
bearbeitet, kann es der Fall sein, dass sie sich in ihrer
Ausführung gegenseitig beeinflussen
 im Kontext von OpenMP nutzen alle Threads im Team einen
gemeinsamen Adressraum
 die Kommunikation zwischen den Threads erfolgt durch Lesen
und Schreiben gemeinsam genutzter Variablen
 die Reihenfolge von parallel ausgeführten Anweisungen ist
nicht garantiert
 um die gemeinsam genutzten Daten dennoch konsistent zu
halten, werden OpenMP-Sprachkonstrukte zur
Synchronisierung benötigt
Eine Situation, in der mehrere Threads auf gemeinsam genutzte
Daten zugreifen und das Ergebnis der Berechnung von der
jeweiligen Ausführungsreihenfolge abhängt heißt
Wettlaufsituation oder Race Condition.

18. November 2014
Synchronisation
3
Beispiel: Wettlaufsituation
Aufgabe: Finde das größte Element in einer Liste
cur_max = MINUS_INFINITY;
#pragma omp parallel for
for ( i = 0; i < n; i++ )
if ( a[i] > cur_max )
cur_max = a[i];
Die obige parallele Version kann falsche Ergebnisse liefern:
Thread 0:
Thread 1:
read a[i]
(12)
read cur_max
(10)
if (a[i] > cur_max) (12 > 10)
cur_max = a[i]
(12)
read a[j]
read cur_max
(11)
(10)
if (a[j] > cur_max) (11 > 10)
cur_max = a[j]
(11)
18. November 2014
Synchronisation
4
Synchronisation (1)

Kommunikation in Shared-Memory-Programmen implizit



Kommunikation = read/write-Operationen auf Variablen im
gemeinsamen Adressbereich
implizite Kommunikation bedarf Koordination ⇒
Synchronization
Synchronisation in OpenMP:
gegenseitiges Ausschließen:

kritische Section

atomare Anweisungen

Lock-Routinen
• Event-Synchronisation
 Barrieren
 ordered
 master
 flush
 taskwait

18. November 2014
Synchronisation
5
Synchronisation (2)

gegenseitiges Ausschließen
 exklusiver Zugriff auf eine gemeinsame Variable
 kann verwendet werden zur Sicherstellung, dass
1. innerhalb des Synchronisationskonstrukts nur ein Thread
Zugriff auf die Variable hat
2. Zugriffe mehrerer Threads in der Granularität des
Synchronisationskonstruktes verschachtelt werden

Synchronisation von Ereignissen
 signalisiert einem Thread die Fertigstellung von
Ereignissen eines anderen Threads
 Synchronization von Ereignissen kann verwendet werden
um die Reihenfolge der Threads zu regeln
 durch gegenseitiges Ausschließen lässt sich die
Zugriffsreihenfolge nicht regeln
18. November 2014
Synchronisation
6
Kritische Abschnitte

um eine Wettlaufsituation zu vermeiden, muss sichergestellt
werden, dass immer nur ein Thread den Wert der Variablen
cur_max verändern kann
⇒ Synchronisation des Zugriffs
Kritische Abschnitte:
 für parallel laufende Threads müssen also diejenigen
Codeabschnitte identifiziert werden, in denen ein Thread
exklusiven Zugriff auf gemeinsam genutzte Daten haben
muss
 diese Abschnitte werden kritische Abschnitte genannt
 wenn ein Thread einen kritischen Abschnitt ausführt, darf kein
anderer Thread diesen kritischen Abschnitt ausführen
18. November 2014
Synchronisation
7
Kritische Region
#pragma omp critical [(name)] new-line
{
strukturierter Block
}


OpenMP setzt das Konzept des kritischen Abschnitts direkt in ein
Pragma um
die critical-Direktive markiert den folgenden Block als
kritischen Abschnitt:




der Block wird zu jedem Zeitpunkt von höchstens einem Thread
ausgeführt
weitere Threads blockieren am Anfang des Blocks
alle mit critical gekennzeichneten Codeabschnitte im Programm werden wie ein einziger kritischer Abschnitt behandelt
⇒ Threads an verschiedenen Stellen im Programm warten
unnötigerweise aufeinander
feinere Granularität: kritische Regionen mit Namen versehen
⇒ Regionen mit unterschiedlichen Namen können parallel
ausgeführt werden
18. November 2014
Synchronisation
8
Beispiel: Wettlaufsituation (Lösung)
Aufgabe: Finde das größte
Element in einer Liste
cur_max = MINUS_INFINITY;
#pragma omp parallel for
for ( i = 0; i < n; i++ )
if ( a[i] > cur_max )
cur_max = a[i];
Erste Lösung:
Bessere Lösung:
cur_max = MINUS_INFINITY;
cur_max = MINUS_INFINITY;
#pragma omp parallel for
for ( i = 0; i < n; i++ ){
#pragma omp critical
{
if ( a[i] > cur_max )
cur_max = a[i];
}
}
#pragma omp parallel for
for ( i = 0; i < n; i++ ){
if( a[i] > cur_max)
#pragma omp critical
{
if ( a[i] > cur_max )
cur_max = a[i];
}
}
18. November 2014
Synchronisation
9
Beispiel: kritische Region mit Namen
cur_max = MINUS_INFINITY;
cur_min = PLUS_INFINITY;
#pragma omp parallel for
for ( i = 0; i < n; i++ ) {
if ( a[i] > cur_max )
#pragma omp critical (MAX_LOCK)
{
if ( a[i] > cur_max )
cur_max = a[i];
}
if ( a[i] < cur_min )
#pragma omp critical (MIN_LOCK)
{
if ( a[i] < cur_min )
cur_min = a[i];
}
}
18. November 2014
Synchronisation
10
Atomare Anweisung





atomare Anweisungen sind nützlich, wenn man ein Datum
exklusiv aktualisieren möchte
zwischen dem Lese- und anschließenden Schreibzugriff darf
kein anderer Thread auf dieses Datum schreibend zugreifen
statt eines kompletten Codeabschnitts wie bei critical wird
hier nur eine einzelne Speicherstelle vor unerlaubtem
Parallelzugriff geschützt
Vorteil:
 geringerer Verwaltungsaufwand zur Synchronisation der
Threads
⇒ in den meisten Fällen hat eine atomare Anweisung Laufzeitvorteile gegebenüber einem kritischen Abschnitt
Nachteil:
 nur bei bestimmten Operationen auf dieser Speicherstelle
möglich
18. November 2014
Synchronisation
11
Atomare Anweisung: Syntax (1)
C/C++
#pragma omp atomic [read | write | update | capture] \
[seq_cst] new-line
Zuweisung
die Zuweisung, auf die sich die atomic-Direktive bezieht, muss
die Anwendung eines Inkrement- oder Zuweisungsoperators auf
einer skalaren Variable x oder v sein:






x und v sind skalare
l-value Ausdrücke
expr ist ein skalarer
Ausdruck
binop ist folgendes:
+, ­, *, /, &, ^, |, <<, >>
v und expr dürfen
nicht auf x zugreifen
x und expr dürfen
nicht auf v zugreifen
18. November 2014
Klausel
erlaubte Zuweisung
read
write
update
oder keine
Klausel
capture
v = x;
x = expr;
x++; ++x; x--; --x;
x binop = expr;
x = x binop expr;
v = x++; v = ++x;
v = x--; v = --x;
v = x binop= expr;
seq_cst
bewirkt einen impliziten Flush
Synchronisation
12
Atomare Anweisung: Syntax (2)
C/C++
#pragma omp atomic capture [seq_cst] new-line
strukturierter Block
die Zuweisung, auf die sich die atomic-Direktive bezieht, muss
die Anwendung eines Inkrement- oder Zuweisungsoperators auf
einer skalaren Variable x oder v sein:






x und v sind skalare
l-value Ausdrücke
expr ist ein skalarer
Ausdruck
binop ist folgendes:
+, ­, *, /, &, ^, |, <<, >>
v und expr dürfen
nicht auf x zugreifen
x und expr dürfen
nicht auf v zugreifen
18. November 2014
erlaubte strukturierte Blöcke
{v = x; x binop= expr;}
{x binop= expr; v = x;}
{v = x; x = x binop expr;}
{x = x binop expr; v = x;}
{v = x; x++;}{v = x; ++x;}
{x++; v = x;}{++x; v = x;}
{v = x; x--;}{v = x; --x;}
{x--; v = x;}{--x; v = x;}
Synchronisation
13
Locks








Eine Art „kritische Region“ auf Daten
Jede Lock-Variable kann nur von einem Thread zu einem
Zeitpunkt belegt sein
Lock-Variablen müssen vom Typ omp_lock_t sein
auf Lock-Variablen kann nur mit entsprechenden Routinen
zugegriffen werden
ein OpenMP-Lock kann einen der folgenden Zustände haben:
uninitialized, unlocked, locked
es gibt zwei Arten von Locks:
 einfache Locks
 ineinander schachtelbare (nestable) Locks
einfache Locks: können nur einmal von einem Thread
gesetzt werden
schachtelbare Locks: können mehrfach von dem selben
Thread gesetzt werden, bevor sie wieder deaktiviert werden
18. November 2014
Synchronisation
14
Funktionen für einfache Locks
void omp_init_lock(omp_lock_t *lockvar);

einen einfachen Lock initialisieren
void omp_destroy_lock(omp_lock_t *lockvar);

einen Lock löschen (Status des Locks wird auf uninitialized
gesetzt)
void omp_set_lock(omp_lock_t *lockvar);

einen Lock sperren (Thread blockiert solange, bis er den
Lock bekommt, bzw. der Lock gesetzt ist)
void omp_unset_lock(omp_lock_t *lockvar);

einen Lock entsperren
void omp_test_lock(omp_lock_t *lockvar);
Lock testen und setzen (liefert TRUE, wenn Lock gesetzt
werden konnte)
Funktionen für geschachtelte Locks sind analog,
werden hier jedoch nicht weiter behandelt !

18. November 2014
Synchronisation
15
Beispiel: Lock
omp_lock_t lock;
omp_init_lock(&lock);
cur_max = MINUS_INFINITY;
#pragma omp parallel for
for ( i = 0; i < n; i++ ) {
if ( a[i] > cur_max )
{
omp_set_lock(&lock);
if ( a[i] > cur_max )
cur_max = a[i];
omp_unset_lock(&lock);
}
}
omp_destroy_lock(&lock);
18. November 2014
Synchronisation
16
Beispiel: Lock
omp_lock_t lock;
omp_init_lock(&lock);
cur_max = MINUS_INFINITY;
#pragma omp parallel for
for ( i = 0; i < n; i++ ) {
if ( a[i] > cur_max )
{
while (!omp_test_lock(&lock))
do_work();
if ( a[i] > cur_max )
cur_max = a[i];
omp_unset_lock(&lock);
}
}
omp_destroy_lock(&lock);
18. November 2014
Synchronisation
17
C/C++
Barrieren: Syntax
#pragma omp barrier new-line



implizite Barrieren: am Ende eines parallelen Abschnitts
warten alle Threads bis alle den parallelen Abschnitt
durchlaufen haben (wenn der Abschnitt nicht mit einer
nowait-Direktive versehen ist)
mit Hilfe der barrier-Direktive kann der Programmierer
Barrieren explizit an notwendigen Stellen im Programm
einfügen
Vorsicht: Barrieren sollten jedoch nur sparsam verwendet
werden, da sie die Performance negativ beeinflussen
18. November 2014
Synchronisation
18
Geordnete Ausführung: Syntax
C/C++
#pragma omp ordered new-line
strukturierter Block


manchmal kann es notwendig sein, dass innerhalb einer
parallel ausgeführten Schleife ein Stück Code in der
Reihenfolge ausgeführt werden muss, die der sequentiellen
Ausführung der Schleife entspricht, ohne dass man
deswegen den parallelen Abschnitt verlassen möchte oder
kann.
sollen Teile einer parallelen Schleife geordnet ausgeführt werden, so muss
das Schlüsselwort ordered
sowohl als Klausel als
auch als Direktive
eingefügt werden
18. November 2014
/* ordered als Klausel */
#pragma omp parallel for ordered
for ( i=0; i<n; i++ ) {
a[i] = f(i);
/* ordered als Direktive */
#pragma omp ordered
printf("a[%d] = %d", i, a[i]);
}
Synchronisation
19
C/C++
master-Direktive
#pragma omp master new-line
strukturierter Block


mit Hilfe der master-Direktive lässt sich genau spezifizieren,
welcher Thread den entsprechenden Abschnitt alleine
ausführen soll: der Master-Thread
in einem master-Abschnitt können z. B. Zwischenergebnisse
aus einer vorhergehenden parallelen Berechnung sequentiell
ausgegeben werden

zu beachten ist jedoch, dass diese Zwischenergebnisse
möglicherweise bereits weiter modifiziert wurden bevor der
Master-Thread die Ausgabe beendet hat, da die anderen
Threads nicht am Ende des Abschnitts auf den Master-Thread
warten
18. November 2014
Synchronisation
20
Unterschiede master und single



die single-Direktive stellt nur sicher, dass genau ein Thread
den Abschnitt ausführt, jedoch nicht welcher
mit Hilfe der master-Direktive lässt sich spezifizieren, dass
der Master-Thread den Abschnitt alleine ausführen soll
am Ende eines master-Abschnitts befindet sich keine
implizite Barriere


andere Threads können also einfach den master-Abschnitt
überspringen und den nachfolgenden Code ausführen
ein master-Abschnitt muss für die anderen Threads nicht
unbedingt erreichbar sein
18. November 2014
Synchronisation
21
Synchronisation über Speicherstellen




Register, Caches etc. können Daten puffern, wodurch in
einem Multiprozessorsystem Kohärenzprobleme auftreten
können
das OpenMP-Speichermodell besitzt eine gelockerte Kohärenz
⇒ der lokale Pufferspeicher eines Threads muss nicht zu
jedem Zeitpunkt kohärent mit dem Hauptspeicher sein
Beispiele:
 ein Wert, der einer Variablen zugewiesen wird, kann im
lokalen Pufferspeicher eines Threads verbleiben bis ein
Schreiben in den Hauptspeicher erzwungen wird
 das Lesen einer Variablen kann vom lokalen
Pufferspeicher eines Threads erfolgen solange nicht ein
Lesen vom Hauptspeicher erzwungen wird
ein typisches Beispiel, wo dies Probleme verursachen kann, ist
die Synchronisation von Threads über Speicherstellen
18. November 2014
Synchronisation
22
Die flush-Direktive: Syntax
C/C++
#pragma omp flush [(Liste von Variablen)]





Die flush-Direktive stellt einen Synchronisationspunkt für
Speicherbereiche dar.
Die flush-Direktive sorgt dafür, daß lokale Pufferspeicher
mit dem Hauptspeicher abgeglichen werden. Dies ist sowohl
bei dem schreibenden als auch lesenden Thread nötig.
In der Variablenliste sind nur gemeinsame Variablen
möglich. Fehlt die Angabe der Variablenliste, werden alle
gemeinsamen Variablen abgeglichen.
Eine flush-Operation begrenzt die Umsortierung von
Speicheroperationen durch den Compiler.
Ein Flush geschieht implizit bei folgenden Direktiven, falls
kein nowait angegeben wird: barrier, critical, end critical, enddo, end parallel, end sections, end
single, ordered, end ordered
18. November 2014
Synchronisation
23
Die flush-Direktive


Durchführung eines flush garantiert folgendes:
 eine flush-Operation ist erst dann beendet, wenn der Wert
der Variablen in den Hauptspeicher geschrieben wurde
 die lokale Kopie der Variablen wird verworfen, die Variable
muss beim nächsten Zugriff aus dem Hauptspeicher
gelesen werden
 spätere Speicheroperationen des Threads, die diese
Variable betreffen, dürfen erst starten, wenn die flushOperation beendet wurde
die flush-Direktive kann verwendet werden zur Sicherstellung
dass ein Wert der einer Variablen von einem Thread zugewiesen wurde von einem anderen Thread gelesen werden kann:
1)
2)
3)
4)
Der erste Thread schreibt einen Wert auf die Variable
Die Variable wird vom ersten Thread geflusht
Die Variable wird vom zweiten Thread geflusht
Der zweite Thread liest die Variable
18. November 2014
Synchronisation
24
Effiziente Parallelisierung



Letztlich entscheidet die Effizienz einer Parallelisierung
darüber, ob sich der zusätzliche Aufwand bei der
Programmierung gelohnt hat.
Obwohl OpenMP einer der einfachsten Möglichkeiten
darstellt, durch inkrementelles Vorgehen Schritt für Schritt
ohne komplette Neustrukturierung des Codes ein Programm
zu parallelisieren, zeigt sich in der Praxis, dass die effiziente
Parallelisierung ein nichttriviales Unterfangen ist.
Gerade beim Einstieg in OpenMP sind die Ergebnisse oft
ernüchternd: Das parallelisierte Programm läuft um ein
Vielfaches langsamer als das sequentielle.


der zusätzliche Verwaltungsaufwand für mehrere Threads frist
die durch die Parallelisierung erreichbare Beschleunigung
eine parallele Ausführung lohnt sich erst ab eine gewissen
Problemgröße
Das Ziel einer Parallelisierung ist immer ein
skalierbares Programm!
18. November 2014
Synchronisation
26
Tipps zur effizienten Parallelisierung (1)

Die Gesamtanzahl paralleler Abschnitte minimieren:




das Starten und Beenden von Threads bei jedem Eintreten und
Verlassen von parallelen Abschnitten ist mit nicht zu vernachlässigendem Verwaltungsaufwand verbunden
im Falle von parallelen Schleifen kommt noch zusätzlicher
Aufwand für die Zuweisung von Iterationen an Threads hinzu
oft können zwei parallele Abschnitte durch einen single- oder
master-Block zusammengefasst werden
Verschachtelte Schleifen so weit außen wie möglich
parallelisieren:


bei verschachtelten Schleifen sollte nach Möglichkeit (wenn
keine Datenabhängigkeiten dagegen sprechen) die äußerste
Schleife parallelisiert werden
Grund: innen liegende parallele Abschnitte werden in jeder
Iteration einer weiter außen liegenden Schleife betreten und
verlassen ⇒ Multiplikation des Verwaltungsaufwands
18. November 2014
Synchronisation
27
Tipps zur effizienten Parallelisierung (2)

Kritische Abschnitte möglichst sparsam verwenden:




viele kritische Abschnitte erhöhen den sequentiellen Teil eines
Programms ⇒ Performancelimitierung (Amdahl'sches Gesetz)
manchmal kann es vorteilhaft sein, mit Lock-Funktionen eine
exaktere Synchronisation auf gemeinsam genutzte Ressourcen
zu implementieren
alternativ kann man gemeinsam genutzte Ressourcen jedem
Thread als private Kopie mitgeben und am Ende die Ergebnisse
zusammenfassen
Kritische Abschnitte mit Namen versehen:



der für die Korrektheit notwendige Synchronisationsaufwand
lässt sich nicht umgehen
umso wichtiger ist es, dass Threads nicht unnötig an nicht oder
gleich benannten kritischen Abschnitten aufeinander warten
müssen
darüber hinaus sollte geprüft werden, ob ein kritischer Abschnitt
möglicherweise durch eine atomic-Direktive oder reductionKlausel ersetzt werden kann
18. November 2014
Synchronisation
28
Tipps zur effizienten Parallelisierung (3)

if-Klausel:




bei manchen Problemen lohnt sich eine parallele Ausführung
erst ab einer gewissen Problemgröße
die Parallelisierung “kleiner” Schleifen mit wenig Rechenaufwand pro Iteration lohnt sich oft nur ab einer gewissen Anzahl
von Iterationen
in solchen Fällen kann die if-Klausel verwendet werden, die die
Ausführung eines parallelen Abschnitts an eine zur Laufzeit
ausgewertete Bedingung knüpft
Lastbalancierung:


während der Parallelisierung mit OpenMP sollte man das
Programm immer wieder ausführen, seine Laufzeit messen und
dabei auch die Auslastung der Prozessoren überprüfen
stellt man ein Ungleichgewicht bei der Prozessornutzung fest,
muss die Aufteilung der Arbeit auf die Threads neu überdacht
werden (bei parallelen Schleifen z. B. genügt oft die Anpassung
der Scheduling-Strategie)
18. November 2014
Synchronisation
29
Tipps zur effizienten Parallelisierung (4)

Wo immer möglich nowait-Klauseln verwenden:



am Ende aller Arbeit aufteilenden Direktiven steht eine implizite
Barriere
wo es nicht notwendig ist, dass alle Threads auf die Beendigung
der Berechnungen auch des letzten Threads warten, sollte
immer die nowait-Klausel verwendet werden um die Wartezeit
an impliziten Barrieren zu minimieren
Möglichkeiten der Privatisierung von Daten nutzen:



zeitliche und räumliche Lokalität in Datenzugriffsmustern
ausnutzen, damit möglichst viele Anfragen direkt aus dem
Cache bedient werden können
Private Variablen erhöhen die Lokalität im Sinne der Cachenutzung ⇒ wo immer möglich Variablen als private deklarieren
False Sharing (mehrere Threads greifen gemeinsam auf dieselbe
Cache-Line zu) verhindern, z. B. durch statische SchedulingStrategien (Speicherbereiche auf die die Threads zugreifen
möglichst weit voneinander trennen)
18. November 2014
Synchronisation
30
Übung
Übung 13:
Synchronisation
18. November 2014
Synchronisation
31
Einführung in die Parallelprogrammierung
OpenMP Teil 5:
OpenMP 4.0 Erweiterungen
Annika Hagemeier
Jülich Supercomputing Centre (JSC)
Forschungszentrum Jülich
Inhalt




Array Sections
Error Handling
SIMD-Konstrukte
Unterstützung für Beschleuniger
24. November 2014
OpenMP 4.0 Erweiterungen
2
4.0
Array Sections



Ein Feldabschnitt (array section) bezeichnet eine Teilmenge
eines Feldes
Ein Feldabschnitt kann nur in Klauseln verwendet werden, wo
er explizit erlaubt ist
Auch erlaubt bei mehrdimensionalen Feldern
Syntax C/C++:
[
[
[
[

Beispiel:
untere Grenze : Länge ] oder
untere Grenze : ]
oder
: Länge ]
oder
: ]
a[0:6]
A[1:]
b[10][:][:0]
C[1:10][42][0:6]
Fortran unterstützt bereits die Definition von Teilfeldern
24. November 2014
OpenMP 4.0 Erweiterungen
3
4.0
Error Handling (1)
#pragma omp cancel Konstruktname [if (expr)] new-line
Sauberer Weg, um die vorzeitige Beendigung eines
Konstruktes zu signalisieren
 Ein Thread signalisiert
 Alle anderen Threads springen zum Ende des Konstruktes

Konstruktname:





parallel
sections
for
taskgroup
Beispiel:

#pragma omp parallel for private (eureka)
for (i=0, i<n, ++i)
eureka = testing(i,...)
#pragma omp cancel parallel if (eureka)
24. November 2014

Der erste Thread, für den
eureka true ist, bricht die
parallele Region ab und
beendet sich
Alle anderen Threads
beenden sich, wenn sie
das nächste mal an die
cancel-Direktive stoßen
OpenMP 4.0 Erweiterungen
4
Error Handling (2)
4.0
#pragma omp cancellation point Konstruktname new-line
Bezeichnet einen benutzer-definierten Cancellation-Punkt
Ein Thread oder Task muss an diesem Punkt prüfen, ob für
das angegebene Konstrukt eine Abbruchbedingung
vorhanden/erfüllt ist
Vordefinierte Cancellation-Punkte sind:
 Implizite Barrieren
 barrier-Regionen
 cancel-Regionen
 cancellation point-Regionen



Konstruktname:




parallel
sections
for
taskgroup
24. November 2014
OpenMP 4.0 Erweiterungen
5
SIMD-Vektorisierung


4.0
SIMD-Prozessing nutzt Datenparallelität aus
Datenparallelität bedeutet, dass Operationen die auf
Vektorelementen durchgeführt werden, gleichzeitig
ausgeführt werden können
24. November 2014
OpenMP 4.0 Erweiterungen
6
simd-Direktive: Syntax
4.0
#pragma omp simd [Klausel[[,] Klausel] ...] new-line
for-Schleife


Gibt an, dass die folgende Schleife in eine SIMD-Schleife
transformiert werden kann, d.h. mehrere Iterationen der
Schleife können gleichzeitig im Sinne von SIMD-Instruktionen
verarbeitet werden
Die Schleifeniterationen werden dabei nicht auf die Threads
verteilt
Klauseln:

safelen (length)

linear (list[:linear-step])

aligned (list[:alignment])

private (list)

lastprivate (list)

reduction (operator: list)

collapse (n)
24. November 2014
OpenMP 4.0 Erweiterungen
7
declare simd-Direktive: Syntax
4.0
#pragma omp declare simd [Klausel[[,] Klausel] ...] nl
[#pragma omp declare simd [Klausel[[,] Klausel] ...] nl]
[...]
Funktionsdefinition oder -deklaration

Ermöglicht die Erzeugung einer SIMD-Version der
zugehörigen Funktion
Klauseln:

simdlen (length)

linear (argument-list[:constant-linear-step])

aligned (argument-list[:alignment])

uniform (argument-list)

inbranch

notinbranch
24. November 2014
OpenMP 4.0 Erweiterungen
8
for simd-Direktive: Syntax
4.0
#pragma omp for simd [Klausel[[,] Klausel] ...] new-line
for-Schleife



Gibt an, dass die folgende Schleife in eine SIMD-Schleife
transformiert werden kann, d.h. mehrere Iterationen der
Schleife können gleichzeitig im Sinne von SIMD-Instruktionen
verarbeitet werden
Die Schleifeniterationen werden auch auf die Threads
verteilt
Erlaubte Klauseln: alle Klauseln, die bei der for- und der
simd-Direktive erlaubt sind
24. November 2014
OpenMP 4.0 Erweiterungen
9
Unterstützung für Beschleuniger (1)






4.0
Host-zentriertes Modell: ein Host-Device und mehrere Target
-Devices vom selben Typ
Device: eine logische Ausführungseinheit mit lokalem
Speicher
Device Data Environment: Datenumgebung eines TargetDevices
target-Direktive kontrolliert wie Daten und Quelltext auf
Target-Devices ausgelagert werden
Daten werden von der Datenumgebung des Hosts auf die
Datenumgebung des Target-Devices abgebildet
Quelltext innerhalb der target-Region wird auf dem TargetDevice ausgeführt


Wird standardmäßig sequentiell ausgeführt
Kann aber auch
#pragma omp target map(to:B,C), map(tofrom:sum)
OpenMP-Direktiven #pragma omp parallel for reduction(+:sum)
zur parallelen Ausfor (int i=0; i<N; ++i)
sum += B[i] + C[i];
führung enthalten
24. November 2014
OpenMP 4.0 Erweiterungen
10
Unterstützung für Beschleuniger (2)
4.0
Direktiven:
 target: erzeugt eine Datenumgebung für ein TargetDevice, bzw. transferriert Daten auf das Target-Device und
führt den folgenden strukturierten Block auf dem Device aus
 target data: erzeugt eine Datenumgebung für ein TargetDevice, bzw. transferriert Daten auf das Target-Device
 target update: aktualisiert Daten innerhalb einer targetRegion
 declare target: compiliert Quelltext für ein Target-Device
Weitere Direktiven
 teams: erzeugt mehrere Master-Threads, die parallel
ausgeführt werden können, aber nicht miteinander
kommunizieren können
 distribute: verteilt die Iterationen einer parallelen Schleife
auf mehrere Thread-Teams
24. November 2014
OpenMP 4.0 Erweiterungen
11
Einführung in die Parallelprogrammierung
Performance Analyse
Annika Hagemeier
Jülich Supercomputing Centre (JSC)
Forschungszentrum Jülich
Inhalt




Performance Analyse und Tuning
UNITE
Vampir
Scalasca
24. November 2014
Performance Analyse
2
Performance Analyse und Tuning



Erfolgreiches Tuning ist eine Kombination aus

Dem richtigen Algorithmus und Bibliotheken

Compiler-Flags und Pragmas/Direktiven

Nachdenken
Messen ist besser als Intuition bzw. Rätselraten

Um Performance-Probleme zu finden

Und Optimierungensergebnisse zu überprüfen
(nach jedem Schritt!)
Es ist leichter, ein langsames korrektes Programm zu
optimieren, als ein schnelles inkorrektes Programm zu
debuggen

Erst debuggen, dann tunen!

Niemanden interessiert es, wie schnell man ein
falsches Ergebniss produzieren kann
24. November 2014
Performance Analyse
3
Performance Analyse: Typisches Vorgehen

Habe ich ein Performance/Skalierungs-Problem?

Ermitteln z.B. durch Speedup-Messung

Mögliche Gründe:






Loadimbalances
Zu viel Overhead durch Kommunikation,
Synchronisation, ...
Serialisierungen
Was ist der größte Bottleneck?

Berechnung oder Kommunikation
Wo ist der größte Bottleneck?
Warum ist dies ein Bottleneck
24. November 2014
Performance Analyse
4
Ein Bild sagt mehr als 1000 Worte ...
Ringsendung
24. November 2014
Reales Beispiel
Performance Analyse
5
UNITE (UNiform Integrated Tool Environment)



Standardisiert den Zugriff auf Tools und
Dokumentation für die Performance-Analyse paralleler
MPI, OpenMP und hybriden MPI/OpenMP-Programme
auf High-Performance Compute Clustern
Besteht aus einer Reihe portabler, allgemein
anerkannter Tools, die mehrheitlich Open-Source sind
Basiert auf dem module-Kommando:

Standardisierte Tool- und Versionsidentifikation
 <tool>/<version>-<special>
 <special>: gibt an, ob das Tool für eine spezifische
MPI Bibliothek, Compiler oder 32/64-bit-Modus
vorgesehen ist
24. November 2014
Performance Analyse
6
UNITE - Benutzung


Tools erst sichtbar, nachdem UNITE geladen wurde:

module load UNITE
Benutzungsbeschreibung und Link zum Tool mittels:

module help <tool>
train085@jj28l01:~> module load UNITE
UNITE loaded
train085@jj28l01:~> module help vampir
------------------------------------------------------------------Module Specific Help for
/usr/local/UNITE/modulefiles/tools/vampir/8.1.0:
Vampir: Event Trace Visualizer
Version 8.1.0
Basic usage:
1. Start the trace visualizer (vampir)
2. Load and analyze trace file
For more information:
- See ${VAMPIR_ROOT}/doc/
- http://www.vampir.eu
- mailto:[email protected]
------------------------------------------------------------------24. November 2014
Performance Analyse
7
Vampir





Vampir = Visualization and Analysis of MPI Programs
Ursprünglich vom Forschungszentrum Jülich entwickelt
Heute hauptsächlich von der TU Dresden
weiterentwickelt
Vampirtrace:
 Bibliothek zum Tracen von MPI, OpenMP und
Anwendungsevents
 Bis Version 4.0 kommerziell, ab Version 5 open-source
Vampir Event Trace Visualizer:
 Kommerziell und portable
 Visualisiert Traces generiert mittels VampirTrace,
Scalasca, TAU
 Zudem existieren Konverter, die andere Formate in
Vampirformat konvertieren
24. November 2014
Performance Analyse
8
Vampir: Weitere Informationen
Weitere Informationen zu Vampir/VampirTrace:
 http://apps.fz-juelich.de/unite/index.php/Vampir
 http://www.vampir.eu/
24. November 2014
Performance Analyse
9
Vampir: Benutzung auf JUDGE (MPI)
Auf Login-Knoten:
 Module laden:
module load UNITE vampirtrace vampir
 Programm compilieren und linken mit:
vtcc -vt:cc mpicc -o my_proc my_proc.c
 interaktive Partition allokieren (mit X-Forwarding): msub -I -X -l nodes=2:ppn=8
Auf Compute-Knoten:
 Module laden:
module load UNITE vampirtrace vampir
 Programm normal ausführen (generiert Trace­Datei .otf):
mpiexec -np 8 ./my_proc
Auf Login-Knoten:
 Traces anschauen mit Vampir:
vampir my_proc.otf
24. November 2014
Performance Analyse
10
Vampir: Trace View Window
Zoom
Toolbar
Charts
Toolbar
Global Timeline
24. November 2014
Performance Analyse
11
Scalasca

SCALASCA ist ein open-source Toolset zur
skalierbaren Performance Analyse von
hochskalierbaren parallelen Anwendungen
Weitere Informationen zu Scalasca:


http://apps.fz-juelich.de/unite/index.php/Scalasca
http://www.scalasca.org
24. November 2014
Performance Analyse
12
Scalasca: Benutzung auf JUDGE
Auf Login-Knoten:
 Modul laden:
module load UNITE scalasca
 Programm compilieren und linken mit:
skin mpicc -o my_proc my_proc.c  interaktive Partition allokieren (mit X-Forwarding): msub -I -X -l nodes=2:ppn=8
Auf Compute-Knoten:
 Modul laden:
module load UNITE scalasca
 Programm ausführen mit Scalasca Analyzer (generiert
Verzeichnis epik_...):
scan mpiexec -np 8 ./my_proc
Auf Login-Knoten:
 Profiles anschauen:
square epik_...
24. November 2014
Performance Analyse
13
Scalasca: Cube Result Browser
24. November 2014
Performance Analyse
14
Übung
Übung 14:
Hybride Programmierung
und Performance Analyse
24. November 2014
Performance Analyse
15

Documentos relacionados