=================================== Einführung in die MPI-Schnittstelle =================================== MPI (\textit{Message Passing Interface}) ist ein Standard für eine Bibliotheksschnittstelle für parallele Programme. Im Gegensatz zu Threads und OpenMP leben die parallelen Ausführungsfäden in getrennten Prozessen und damit auch getrennten Adressräumen. Dies ermöglicht auch den Einsatz von MPI auf einem Cluster (wie beispielsweise der Pacioli) oder einem beliebigen Netzwerk von Rechnern. 1994 entstand die erste Fassung des Standards (1.0), 1995 die Version 1.2 und seit 1997 gibt es 2.0. Im September 2012 erschien die Version 3.0, die bei uns bislang nur auf der Thales unterstützt wird. Die Standards sind öffentlich unter `http://www.mpi-forum.org/`. Der Standard umfasst die sprachspezifischen Schnittstellen für Fortran und C. In C++ wird entsprechend die C-Schnittstelle verwendet. Es stehen mehrere Open-Source-Implementierungen zur Verfügung: * OpenMPI: `http://www.open-mpi.org/` (auf unseren Debian-Maschinen in E.44, Thales und Theseus) * MPICH: `http://www.mpich.org/` * MVAPICH: `http://mvapich.cse.ohio-state.edu/` (spezialisiert auf Infiniband) Wir verwenden im Rahmen dieser Vorlesung OpenMPI. Bei Übersetzungen wird statt `g++` das Kommando `mpiCC` oder `mpic++` verwendet. MPI-Programme werden nicht direkt gestartet, sondern mit Hilfe des Programms `mpirun`, das sich um die Einrichtung der Ausführungsumgebung kümmert. Auf welchen Rechnern wieviel Prozesse auf welche Weise gestartet werden, ist das Problem von `mpirun`. Das kann entsprechend konfiguriert werden, wobei insbesondere auch die Gesamtzahl der Prozesse festgelegt wird. Zu Beginn starten alle Prozesse zeitgleich. Dies ist ein wesentlicher Unterschied zu allen bisherigen Vorgehensweisen, bei denen wir mit einem Ausführungsfaden begannen. Ähnlich wie bei OpenMP kann jeder Prozess erfahren, wieviel Prozesse es gibt und welche Nummer der eigene Prozess hat (in MPI _rank_ genannt). Der Prozess mit dem _rank_ 0 hat dabei eine Sonderrolle. Er ist der einzige, der ggf. die Kommandozeile bearbeitet und auf der Standardausgabe etwas ausgeben kann. In einem Master/Worker-Pattern übernimmt dieser Prozess naturgemäß die Rolle des Masters. Das wird am folgenden kleinen Beispiel demonstriert, bei dem die Worker jeweils mit `MPI_Send` ihre Prozessnummer an den Master schicken, während der Master diese mit `MPI_Recv` entgegennimmt und ausgibt: :import:session20/mpi_test.cpp Zu beachten ist, dass _n_ Prozesse dieses Programm gleichzeitig starten. `MPI_Init` muss vor allen anderen MPI-Funktion aufgerufen werden. Diese Funktion dient dazu, die Verbindung zur Ausführungsumgebung aufzunehmen. Analog ist am Ende `MPI_Finalize` aufzurufen, das den Prozess von der Ausführungsumgebung wieder trennt. Mit `MPI_Comm_rank` wird die eigene Prozessnummer ermittelt, mit `MPI_Comm_size` die Zahl der Prozesse. Der erste Parameter spezifiziert hier jeweils die Kommunikationsdomäne. Das ist hier zu Beginn immer `MPI_COMM_WORLD`. Später können ggf. auch weitere Kommunikationsdomänen erzeugt werden. Zur Kommunikation werden hier `MPI_Send` und `MPI_Recv` verwendet. Beide haben eine ähnliche Parameterfolge: * `void* buf`: ein Zeiger auf ein Array mit zu übertragenden Elementen. * `int count`: die Größe des Arrays (bei einem einzelnen Wert ist hier 1 anzugeben). * `MPI_Datatype datatype`: der Datentyp eines Elements (`MPI_INT` steht hier für `int`). * `int dest` (bei `MPI_Send`) bzw. `int source` (bei `MPI_Recv`) legt fest, an wen die Nachricht geht bzw. von wem eine Nachricht akzeptiert wird. * `int tag`: Nachrichten können mit einem Etikett versehen werden; der Empfänger akzeptiert nur Nachrichten mit dem angegebenen `tag`. Im einfachsten Fall wird hier nur 0 verwendet. * `MPI_Comm comm`: die zu verwendende Kommunikationsdomäne; im einfachsten Fall immer `MPI_COMM_WORLD`. * `MPI_Status* status`: kommt noch bei `MPI_Recv` hinzu. Hier kann u.a. festgestellt werden, wieviel Array-Elemente tatsächlich übertragen wurden. Auf der Empfängerseite spezifizierte `count` nur das akzeptable Maximum. Dieser Wert kann auch 0 sein. So lässt sich so ein Programm übersetzen und ausführen: ---- SHELL (path=session20) --------------------------------------------------- mpiCC -g -std=c++11 -o mpi_test mpi_test.cpp mpirun -np 4 mpi_test ------------------------------------------------------------------------------- Aufgabe ======= Erweitern Sie das triviale Beispiel dahingehend, dass es mit Hilfe der Simpson-Regel ein numerisches Integral für die Funktion _f_ auf dem Intervall $[a, b]$ unter Verwendung von $n$ Teilintervallen berechnet: ---- LATEX -------------------------------------------------------------------- S(f,a,b,n) ~=~ \frac{h}{3} \left( \frac{1}{2} f(x_0) + \sum_{k=1}^{n-1} f(x_k) + 2 \sum_{k=1}^{n} f\left(\frac{x_{k-1} + x_k}{2}\right) + \frac{1}{2} f(x_n) \right) ------------------------------------------------------------------------------- mit $h ~=~ \frac{b-a}{n}$ und $ x_k ~=~ a + k \cdot h$. Als Funktion könnten Sie beispielsweise $\frac{4}{1 + x^2}$ über das Intervall $[0, 1]$ verwenden. Dann können Sie Ihr Resultat mit $\pi$ vergleichen. Die Zahl der Teilintervalle können Sie der Einfachheit halber global als Konstante festlegen (100 genügt bei diesem Beispiel). Alle Prozesse (einschließlich _rank_ 0) sollten das numerische Integral auf ihrem jeweiligen Teilintervall berechnen, dann sollten wie im Trivialbeispiel alle Prozesse mit _rank_ größer 0 ihr Resultat an den Prozess mit _rank_ 0 schicken, der dann die Teilergebnisse aufsummiert und ausgibt. Als MPI-Datentyp für `double` kann `MPI_DOUBLE` verwendet werden. :navigate: up -> doc:index next -> doc:session20/page02