OpenMP

aus Wikipedia, der freien Enzyklopädie
Dies ist eine alte Version dieser Seite, zuletzt bearbeitet am 4. Juli 2016 um 06:54 Uhr durch Crazy1880 (Diskussion | Beiträge) (ref-TAG-fix). Sie kann sich erheblich von der aktuellen Version unterscheiden.
Zur Navigation springen Zur Suche springen
OpenMP

Logo
OpenMP Logo
Basisdaten

Entwickler Liste kompatibler Compiler
Aktuelle Version Version 4.5
(November 2015)
Aktuelle Vorabversion -
Betriebssystem Linux, UNIX, Microsoft Windows NT
Programmiersprache C++, C
Kategorie API
Lizenz unbekannt (open)
deutschsprachig nein
openmp.org/wp/

OpenMP (Open Multi-Processing) ist eine seit 1997 gemeinschaftlich von verschiedenen Hardware- und Compilerherstellern entwickelte Programmierschnittstelle (API) für die Shared-Memory-Programmierung in C++, C und Fortran auf Multiprozessor-Computern.

OpenMP parallelisiert Programme auf der Ebene von Schleifen, die in verschiedenen Threads ausgeführt werden, und unterscheidet sich dadurch von anderen Ansätzen (z. B. MPI), bei denen ganze Prozesse parallel laufen und durch Nachrichtenaustausch zusammenwirken.

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 (sogenannte 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 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). Der Grund dafür ist, dass eine mit OpenMP für mehrere Threads aufgeteilte for-Schleife auch mit einem einzelnen Thread sequentiell abgearbeitet werden kann.

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 (Originalthread) in mehrere Threads auf, so dass der vom Konstrukt eingeschlossene Programmteil parallel abgearbeitet wird. Der Originalthread wird als Master Thread bezeichnet und trägt die ID 0.

Beispiel: Gibt „Hallo Welt!“ mehrmals mit Hilfe mehrerer Threads aus (jeder Thread eine Ausgabe).

 int main(int argc, char* argv[])
 {
   #pragma omp parallel
   printf("Hallo Welt!\n");

   return 0;
 }
  • Konstrukte zur Lastverteilung bestimmen, wie nebenläufige, unabhängige Arbeitslast auf parallele Threads verteilt wird.
    • omp for und omp do teilen Schleifendurchläufe (möglichst) gleichmäßig auf alle Threads auf (Gebietsaufteilung, data partitioning).
    • sections verteilt aufeinander folgende 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 (Gebietsaufteilung).

 #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ächst die meisten Daten in allen Threads sichtbar. Es besteht jedoch in einigen Programmen die Notwendigkeit von privaten, also nur für einen Thread sichtbaren Daten, und dem expliziten Austauschen von Werten zwischen sequentiellen und parallelen Abschnitten. Dafür dienen in OpenMP die sogenannten data clauses.
    • shared: Die Daten dieses Typs sind gleichzeitig für alle Threads sichtbar und änderbar. Sie liegen für alle Threads an der gleichen Speicherstelle. Ohne weitere Angaben sind Daten gemeinsame Daten. Die einzige Ausnahme davon sind Schleifenvariablen.
    • private: Jeder Thread verfügt über seine eigene Kopie dieser Daten. Private Daten werden nicht initialisiert. Die Werte werden nicht außerhalb des parallelen Abschnitts bewahrt.
    • firstprivate: Diese Daten sind private Daten, mit dem Unterschied, dass sie mit dem letzten Wert vor dem parallelen Abschnitt initialisiert werden.
    • lastprivate: Diese Daten sind private 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: Diese Daten sind globale Daten, die im parallelen Programmabschnitt jedoch als privat behandelt werden. 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 Wert explizit an die privaten Daten übertragen. Ein copyout ist nicht notwendig, da der globale Wert 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.

Folgende Konstrukte werden zur Synchronisation der Threads verwendet:

    • 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 wartet an der Barriere, bis alle anderen Threads der Gruppe ebenfalls die Barriere erreicht haben.
    • ordered: Der eingeschlossene 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 zuerst 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 und am Ende des Blockes keine Barriere impliziert ist.
  • Laufzeitroutinen werden benutzt, um zum Beispiel die Threadanzahl während der Laufzeit zu bestimmen, zu ermitteln, ob das Programm sich aktuell im parallelen oder sequentiellen Zustand befindet, etc.
  • Umgebungsvariablen liefern Informationen wie zum Beispiel die Thread ID. Durch gezieltes Verändern bestimmter Umgebungsvariablen lässt sich die Ausführung von OpenMP-Programmen verändern. So kann beispielsweise die Anzahl von Threads und die Schleifenparallelisierung zur Laufzeit beeinflusst werden.

Beispiel-Code

Parallele Ausführung einer for-Schleife mittels OpenMP. Je nach Anzahl der beteiligten Threads wird die Schleife in verschiedene kleine Abschnitte unterteilt, die je einem Thread zugeordnet werden. So wird erreicht, dass alle Threads gleichzeitig rechnen.

#include <omp.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int i;
    omp_set_num_threads(4);

    #pragma omp parallel for
    for (i = 0; i < 4; ++i)
    {
        const int id = omp_get_thread_num();

        printf("Hello World from thread %d\n", id);

        if (id == 0)
            /* Nur im Masterthread ausführen */
            printf("There are %d threads\n", omp_get_num_threads());
    }

    return EXIT_SUCCESS;
}

Beim Übersetzen muss man dem Compiler sagen, dass er die Pragma-Anweisungen beachten und notwendige Bibliotheken für die omp-Funktionen einbinden soll.

% gcc-4.2 -Wall -fopenmp -o omp-bsp omp-bsp.c
% ./omp-bsp
Hello World from thread 3
Hello World from thread 0
Hello World from thread 1
Hello World from thread 2
There are 4 threads

Statt die Anzahl der Threads im Programm festzulegen, kann man dies auch zur Laufzeit bestimmen. Dazu setzt man die Umgebungsvariable OMP_NUM_THREADS auf den gewünschten Wert.

% OMP_NUM_THREADS=4 ./omp-bsp
Hello World from thread 3
Hello World from thread 0
Hello World from thread 1
Hello World from thread 2
There are 4 threads

Implementation

OpenMP ist in den meisten Compilern integriert.

  • Microsoft Visual C++ 2005, 2008 und 2010 (Professional, Team System, Premium und Ultimate Edition),
  • Intel Parallel Studio für verschiedene Prozessoren (OpenMP 3.1 seit Version 13),
  • GCC seit Version 4.2 (OpenMP 4.0 seit Version 5.0[1]),
  • Clang/LLVM (OpenMP 3.1 seit Version 3.6.1),
  • Oracle Solaris Studio Compiler und Tools für Solaris OS (UltraSPARC und x86/x64) und Linux,
  • Fortran, C und C++ Compiler der Portland Group (OpenMP 2.5),
  • Gfortran,
  • IBM XL C/C++ compiler,
  • Nanos Compiler
  • Pelles C (OpenMP 3.1 seit Version 8)

Weblinks

Einzelnachweise

  1. https://gcc.gnu.org/gcc-5/changes.html