OpenMP
aus Wikipedia, der freien Enzyklopädie
OpenMP ist eine seit 1997 gemeinschaftlich von verschiedenen Hardware- und Compilerherstellern entwickelte Programmierschnittstelle. Der Standard dient zur Shared-Memory-Programmierung in C/C++/Fortran auf Multiprozessor-Computern.
Während bei der Parallelisierung von Programmen gemäß dem Paradigma des Nachrichtenaustauschs (siehe hierzu z. B. MPI) diese zumeist auf Prozessebene vollzogen wird, findet die Parallelisierung mittels OpenMP auf Thread-, bzw. Schleifenebene statt.
Der OpenMP-Standard definiert dazu spezielle Compiler-Direktiven, die diesen dann anweisen z. B. die Abarbeitung einer for-Schleife auf mehrere Threads und/oder Prozessoren zu verteilen. Es existieren jedoch auch Bibliotheksfunktionen sowie Umgebungsvariablen, die zur OpenMP-Programmierung dienen.
OpenMP ist zum Einsatz auf Systemen mit gemeinsamem Hauptspeicher (Shared-Memory-Maschinen) gedacht (sog. UMA- und NUMA-Systeme), während andere Ansätze wie Message Passing Interface, PVM eher auf Multicomputern (Distributed-Memory Maschinen) aufsetzen. Bei modernen Supercomputern werden OpenMP und MPI (Message Passing Interface) oftmals zusammen eingesetzt. Dabei laufen auf einzelnen Shared-Memory-Clients OpenMP-Prozesse, die sich mittels MPI austauschen.
Eine interessante Eigenschaft von OpenMP ist, dass (bis auf Ausnahmen) die Programme auch korrekt laufen, wenn der Compiler die OpenMP-Anweisungen (siehe unten im Beispiel) nicht kennt und als Kommentar bewertet (also ignoriert). Das resultiert daher, dass eine mit OpenMP für mehrere Threads aufgeteilte for-Schleife natürlich auch mit einem einzelnen Thread sequentiell abgearbeitet werden kann.
[Bearbeiten] Hauptbestandteile
Die Hauptbestandteile von OpenMP sind Konstrukte zur Threaderzeugung, Lastverteilung auf mehrere Threads, Verwaltung des Gültigkeitsbereiches von Daten, Synchronisation, Laufzeitroutinen und Umgebungsvariablen.
- Threaderzeugung: omp parallel teilt das Programm (Orginialthread) in mehrere Thread auf, so dass der von Konstrukt eingeschlossene Programmteil parallel abgearbeitet wird. Der Orginialthread wird als Master Thread bezeichnet und trägt die ID 0.
Beispiel: Gibt „Hallo Welt!“ mehrmals mit Hilfe mehrerer Thread aus (jeder Thread eine Ausgabe).
int main(int argc, char* argv[]) { #pragma omp parallel printf("Hallo Welt!\n"); return 0; }
- Konstrukturen zur Lastverteilung bestimmen wie nebenläufige, unabhängige Arbeitslast auf parallele Thread verteilt wird.
- omp for und omp do teilen Schleifendurchläufe (möglichst) gleichmäßig auf alle Threads auf (Gebietsaufteilung, data partitioning).
- sections verteilt aufeinander folgend aber unabhängige Programmteile auf verschiedene Threads (Funktionsaufteilung, function partitioning).
Beispiel: Initialisiert ein großes Array parallel, wobei jeder Thread die Initialisierung eines Teiles des Arrays übernimmt (Gebietsauteilung).
#define N 100000 int main(int argc, char *argv[]) { int i, a[N]; #pragma omp parallel for for (i=0;i<N;i++) a[i]= 2*i; return 0; }
- Verwaltung des Gültigkeitsbereiches von Daten: Bei Shared-Memory-Programmierung sind zunächste die meisten Daten in allen Threads sichtbar. Es besteht jedoch in einigen Programmen die Notwendigkeit nach private also nur für einen Thread sichtbare Daten und dem explizite Austauschen von Werten zwischen sequentiellen und parallelen Bereichen. Dafür dienen in OpenMP die sogenannten data clauses.
- shared Daten sind gleichzeitig für alle Threads sichtbar und änderbar. Sie liegen für alle Threads an der gleichen Speicherstelle. Ohne weiter Angaben sind Daten gemeinsame Daten. Die einzige Ausnahme davon sind Schleifenvariablen.
- private Daten: Jeder Thread verfügt über seine eigene Kopie. Private Daten werden nicht initialisiert. Die Werte werden nicht außerhalb des parallelen Abschnitts bewahrt.
- firstprivate Daten sind privaten Daten, mit dem Unterschied, dass sie mit dem letzten Wert vor dem parallelen Abschnitt initialisiert werden.
- lastprivate Daten sind privaten Daten, mit dem Unterschied, dass der Thread, welcher die letzte Iteration ausführt anschließend den Wert aus dem parallelen Abschnitt herauskopiert. firstprivate und lastprivate können kombiniert werden.
- threadprivate Daten sind globale Daten im parallelen Programmabschnitt jedoch privat. Der globale Wert wird über den parallelen Abschnitt hinweg bewahrt.
- copyin ist analog zu firstprivate für private Daten, allerdings für threadprivate Daten, welche nicht initialisiert werden. Mit copyin wird der globale Werte explizit an die privaten Daten übertragen. Ein copyout ist nicht notwendig, da der globale Werte bewahrt wird.
- reduction: Die Daten sind privat, werden jedoch am Ende auf einen globalen Wert zusammengefasst (reduziert). So lässt sich zum Beispiel die Summe aller Elemente eines Arrays parallel bestimmen.
- Konstrukte zur Synchronisation:
- critical section: Der eingeschlossene Programmabschnitt wird von allen Threads ausgeführt, allerdings niemals gleichzeitig.
- atomic ist analog zu critical section, jedoch mit dem Hinweis an den Compiler spezielle Hardwarefunktionen zu benutzen. Der Compiler ist an diesen Hinweis nicht gebunden, er kann ihn ignorieren. Sinnvoll ist die Verwendung von atomic für das exklusive Aktualisieren von Daten.
- barrier markiert eine Barriere. Jeder Thread warten an der Barriere bis alle anderen Threads der Gruppe ebenfalls die Barriere erreicht haben.
- ordered: Der eingeschlosse Abschnitt wird der Reihenfolge nach abgearbeitet, als würde die Schleife sequentiell abgearbeitet werden.
- flush markiert einen Synchronisationpunkt an dem ein konsistentes Speicherabbild hergestellt werden muss. Private Daten werden in den Arbeitsspeicher zurückgeschrieben.
- single: Der umschlossene Programmteil wird nur von dem Thread ausgeführt, welcher ihn zu erst erreicht, dies impliziert eine Barriere am Ende des Blocks.
- master: Analog zu single mit dem Unterschied, dass der umschlossene Programmteil vom Master Thread ausgeführt wird.
- Laufzeitroutinen werden benutzt um zum Beispiel um die Threadanzahl während er Laufzeit zu bestimmen, zu ermitteln ob das Programm sich aktuell im parallelen oder sequentiellen Zustand befindet, etc.
- Umgebungsvariablen liefern Information wie zum Beispiel Thread ID. Durch gezieltes Verändern bestimmter Umgebungsvariablen lässt sich die Ausführung von OpenMP-Programmen verändern, so kann die Anzahl von Threads und die Schleifenparallelisierung zur Laufzeit beeinflusst werden.
[Bearbeiten] Beispiel-Code
Parallele Ausführung einer for-Schleife mittels OpenMP. Je nach Anzahl der beteiligten Threads wird die Schleife in verschiedene kleine Bereiche unterteilt, die je einem Thread zugeordnet werden. So wird erreicht, dass alle Threads gleichzeitig rechnen und schließlich das komplette Array abgearbeitet ist.
#pragma omp parallel for for (i = first; i < size; i++) field[i] = 1;