Programmieren mit MPI Dr. Victor Pankratius David J. Meder IPD Tichy – Lehrstuhl für Programmiersysteme
KIT – die Kooperation von Forschungszentrum Karlsruhe GmbH und Universität Karlsruhe (TH)
Programmieren mit MPI (1) MPI steht für: Message Passing Interface Standard spezifiziert die Schnittstelle einer Bibliothek für Nachrichtentausch. Standardisierung durch ein Gremium bestehend aus: Hardware-Hersteller (Supercomputer, Kommunikationshardware), Software-Firmen Universitäten Anwender
Sehr verbreitet
2
Kapitel 10 ‒ Programmieren mit MPI
Programmieren mit MPI (2) Zumeist kommt eine der beiden frei verfügbaren Implementierungen LAM oder MPIch zum Einsatz. Je nach verwendeter Hardware (Myrinet, Infiniband, etc.) werden angepasste oder erweiterte Versionen verwendet.
LAM wird aktuell nicht weiterentwickelt. Aus dem LAM-Projekt ist das Projekt Open MPI hervorgegangen. Open MPI ist eine MPI-2 Implementierung.
3
Kapitel 10 ‒ Programmieren mit MPI
Programmiermodell P
P
P
P
P
P
P
M
M
M
M
M
M M
Netzwerk Rechnerbündel mit verteiltem Speicher SPMD: Single Program Multiple Data Dasselbe Programm läuft auf allen Knoten. Programm ist in einer sequentiellen Sprache geschrieben (C, C++, Fortran). Alle Variablen sind prozessor- bzw. prozesslokal. Kommunikation findet stets über Bibliotheksaufrufe statt (Nachrichtenaustausch). 4
Kapitel 10 ‒ Programmieren mit MPI
Hello-World in MPI #include #include "mpi.h" int main (int argc, char **argv) { int rank, size; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); printf("Hello World.\n My rank is %d (out of %d)\n", rank, size); MPI_Finalize(); return 0; }
Übersetzen: mpicc -o hello hello.c Aufruf: mpirun -np 4 ./hello 5
Kapitel 10 ‒ Programmieren mit MPI
Ausführung von MPI-Programmen (1) #include #include "mpi.h" int main (int argc, char **argv) { int rank, size; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); if (rank == 0) { printf(“Hello Universe.\n); } else { printf(“Hello World.\n); } MPI_Finalize(); return 0; }
Übersetzen: mpicc -o hello2 hello2.c Aufruf: mpirun -np 4 ./hello2 6
Kapitel 10 ‒ Programmieren mit MPI
Ausführung von MPI-Programmen (2) int main (int argc, char **argv) { // ... if (rank == 0) { printf(“Hello Universe.\n); } else { printf(“Hello World.\n); } // ... }
7
Kapitel 10 ‒ Programmieren mit MPI
0
1
2
3
Übersetzen und Ausführen von MPIAnwendungen Übersetzen: mpicc (C-Code) oder mpiCC (C++-Code). mpicc und mpiCC sind Skripte, welche die auf dem Rechner
installierten C/C++-Übersetzer aufrufen.
Ausführen: mpirun Argument -np x : Zu verwendende Anzahl x an Prozessoren Weitere Parameter, z.B. -gdb: Startet den ersten Prozess unter dem gdb-Debugger
Beispiel: > mpirun –gdb -np 6 mmult
8
Kapitel 10 ‒ Programmieren mit MPI
Nachrichtenaustausch Nachrichten sind Datenpakete, die zwischen den Prozessen einer parallelen Anwendung ausgetauscht werden. Das Kommunikationssystem benötigt dazu folgende Informationen: Senderkennung Speicherort der Quelldaten Datentyp (Integer, Float, strukturierte Daten, …) Anzahl der Datenelemente beim Sender Empfängerkennung (ein oder mehrere Empfänger) Speicherort der Zieldaten Größe des Empfangsbereiches
9
Kapitel 10 ‒ Programmieren mit MPI
Adressierung (1) MPI verwendet zur Adressierung seiner Prozesse sog. Kommunikatoren. Ein Kommunikator ist eine eindeutige Kommunikationsumgebung (Nachrichtenkontext) für eine Prozessgruppe, deren Elemente von 0 bis N-1 durchnummeriert sind. Der vordefinierte Standardkommunikator ist MPI_COMM_WORLD, der alle gestarteten Prozesse enthält, wohingegen MPI_COMM_SELF nur den Prozess selbst enthält.
1 0
MPI_COMM_WORLD
N-2
10
2
Kapitel 10 ‒ Programmieren mit MPI
N-1
Adressierung (2) Kommunikator = Prozessgruppe + Kontext Bessere Unterstützung von Subroutinen und Bibliotheken bzgl. Koordination mit Hauptprogramm. Beispielszenario: send(to 0) receive(any) call library
call library
call library send(to 0)
receive(from 1) send(to 2) receive(from 0) send(to 1) receive(from 2)
Prozess 0 Hauptprogramm 11
Kapitel 10 ‒ Programmieren mit MPI
Prozess 1
Prozess 2 Subroutine
Adressierung (3)
receive(any) Verzögerung call library ??? receive(from 1) send(to 2)
call library send(to 0) send(to 0) ???
call library
receive(from 2)
Prozess 0
Prozess 1
Hauptprogramm Unterprogramm
Keine Verwendung von Kommunikatoren: Falsche Zuordnung der Nachrichten, evtl. Blockieren. 12
Kapitel 10 ‒ Programmieren mit MPI
receive(from 0) send(to 1)
Prozess 2
Adressierung (4)
receive(any) call library
call library send(to 0)
receive(from 1) send(to 2)
send(to 0)
call library receive(from 2)
Prozess 0
Prozess 1
Kommunikator 1 Kommunikator 2
Verwendung unterschiedlicher Kommunikatoren durch die Anwendung und die Bibliothek. 13
Kapitel 10 ‒ Programmieren mit MPI
receive(from 0) send(to 1)
Prozess 2
Kommunikatoren, Gruppen, Kontexte und virtuelle Topologien Kommunikator: Eindeutige Kommunikationsumgebung. Enthält zwingend einen Kontext und eine Gruppe, evtl. auch eine virtuelle Topologie. Wird in allen Kommunikationsoperationen als Referenz (Handle) verwendet. (Prozess-) Gruppe: Definiert eine geordnete Sammlung von Prozessen + Gültigkeitsbereich der Namen für eine Kommunikation. Wird zur Konstruktion von Kommunikatoren verwendet. Kontext: Definiert eine getrennte eindeutige Umgebung, in der eine Kommunikation isoliert stattfinden kann. Bsp: Trennt Kommunikation von versch. Bibliotheken (Unterscheidung durch Nachrichtenkennung). Virtuelle Topologie: Spezielle Anordnung von Prozessnummern in einer Gruppe, die eine bestimmte Topologie widerspiegeln (s. spätere Folien). 14
Kapitel 10 ‒ Programmieren mit MPI
Kommunikatoroperationen Erfragen der Größe der zugehörigen Prozessgruppe und Ordnungsnummer (Rang) darin: MPI_Comm_size(MPI_Comm comm, int *size) // Größe der Gruppe MPI_Comm_rank(MPI_Comm comm, int *rank) // Rang in der Gruppe
Kommunikator duplizieren (erzeugt neuen Kontext):
MPI_Comm_dup(MPI_Comm comm, MPI_Comm *newcomm)
Neuen Kommunikator erzeugen:
MPI_Comm_create(MPI_Comm comm, MPI_Group group, MPI_Comm *newcomm)
Kommunikator teilen:
MPI_Comm_split(MPI_Comm comm, int color, int key, MPI_Comm *newcomm) Alle Prozesse, die den gleichen Wert für color spezifizieren, sind nach der Operation in derselben Gruppe.
Kommunikator löschen:
MPI_Comm_free(MPI_Comm *comm)
Die Aufrufe zum Erzeugen/Löschen eines Kommunikators müssen immer von allen Prozessen der Prozessgruppe des alten Kommunikators aufgerufen werden.
15
Kapitel 10 ‒ Programmieren mit MPI
Gruppenoperationen Gruppenobjekt aus Kommunikator extrahieren MPI_Comm_group( … ) Größe der Gruppe erfragen MPI_Group_size( … )
Eigenen Rang bestimmen (liefert MPI_UNDEFINED, falls nicht Mitglied) MPI_Group_rank( … )
Gruppe modifizieren
MPI_Group_union( … ) MPI_Group_intersection( … ) MPI_Group_difference( … ) aus vorhandener Gruppe eine neue durch Einfügen MPI_Group_incl( … ) aus vorhandener Gruppe eine neue durch Ausschließen MPI_Group_excl( … )
Die neue Gruppe enthält die Prozesse mit den aufgezählten bzw. den nicht aufgezählten Rangnummern. Gruppe löschen: MPI_Group_free(MPI_Group *group)
Die Funktionen zum Erzeugen neuer Gruppen müssen immer von allen Mitgliedern der alten Gruppe aufgerufen werden!
16
Kapitel 10 ‒ Programmieren mit MPI
Virtuelle Topologien (1) Eine virtuelle Topologie ist eine an einen Kommunikator geknüpfte Zusatzinformation, und zwar die Abbildung von Prozessnummern auf einen am Problem orientierten Namensraum und umgekehrt. Methoden zur Erzeugung / zum Umgang mit virtuellen Topologien: Allgemein: Konstruktion eines Graphen: Knoten sind Prozesse. Kanten sind Kommunikationspfade.
Spezialfall: Kartesische Koordinaten: Ringe, Tori, zwei- oder höherdimensionale Gitter.
17
Kapitel 10 ‒ Programmieren mit MPI
Virtuelle Topologien (2): kartesische Koordinaten MPI_Cart_create(MPI_Comm comm, int ndims, int dims[], int periods[], int reorder, MPI_Comm *newcomm)
0 (0,0)
1 (1,0)
2 (2,0)
3 (3,0)
4 (0,1)
5 (1,1)
6 (2,1)
7 (3,1)
8 (0,2)
9 (1,2)
10 (2,2)
11 (3,2)
ndims=2, dims[]={4,3}, periods[]={true, false} 18
Kapitel 10 ‒ Programmieren mit MPI
Virtuelle Topologien (3): Parameter von MPI_Cart_create() comm: Alter Kommunikator. ndims: Anzahl der Dimensionen des Gitters/Torus. dims: Adresse eines Feldes mit ndims Einträgen, die die Größe
der jeweiligen Dimension angeben. periods: Adresse eines Feldes mit ndims Einträgen, die angeben, ob die jeweilige Dimension periodisch geschlossen ist (1), oder nicht (0). Dies hat Auswirkungen z.B. bei MPI_Cart_shift(), wo bei nicht geschlossenen Dimensionen MPI_PROC_NULL als Kommunikationspartner zurückgegeben werden kann.
reorder: Gibt an, ob MPI die Reihenfolge der Ränge der
beteiligten Prozesse beibehalten soll (0) oder ob es umnummerieren darf, um z.B. eine bessere Abbildung von Prozessen auf die Kommunikationshardware vorzunehmen. newcomm: Adresse des neuen Kommunikators, der dann die virtuelle Topologie enthält (Ausgabeparameter). 19
Kapitel 10 ‒ Programmieren mit MPI
Virtuelle Topologien (4): Nutzung der kartesischen Koordinaten Abbildung der Ordnungsnummer auf Koordinaten MPI_Cart_coords(MPI_Comm comm, int rank, int ndims, int *coords[])
Abbildung der Koordinaten auf die Ordnungsnummer MPI_Cart_rank(MPI_Comm comm, int coords[], int *rank)
Ermittlung des Rangs eines Nachbarprozesses entlang einer bestimmten Dimension, mit Abstand displ MPI_Cart_shift(MPI_Comm comm, int direction, int displ, int *rank_source, int *rank_dest) 20
Kapitel 10 ‒ Programmieren mit MPI
Virtuelle Topologien (5) – Beispiel int main(int argc, char *argv[]) { const int SIZE = 16, UP = 0, DOWN = 1,
LEFT = 2, RIGHT = 3; int taskCount, myRank; int neighbrs[4], dims[2]= {4, 4}, periods[2] = {0,0}, coords[2];
Folgendes Beispiel ermittelt die Ränge der Nachbarn jedes einzelnen Knotens.
MPI_Comm cartcomm; MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &taskCount); if (taskCount == SIZE) { MPI_Cart_create(MPI_COMM_WORLD, 2, dims, periods, 0, &cartcomm); MPI_Comm_rank(cartcomm, &myRank); MPI_Cart_coords(cartcomm, myRank, 2, coords);
MPI_Cart_shift(cartcomm, 0, 1, &neighbrs[UP], &neighbrs[DOWN]); MPI_Cart_shift(cartcomm, 1, 1, &neighbrs[LEFT], &neighbrs[RIGHT]); printf("Rang: %d Koordinaten: %d %d Nachbarn: %d %d %d %d\n", myRank, coords[0], coords[1], neighbrs[UP], neighbrs[DOWN], neighbrs[LEFT], neighbrs[RIGHT]); } MPI_Finalize(); }
21
Kapitel 10 ‒ Programmieren mit MPI
Allgemeines zu Kommunikation (1) Jeweils aus Sicht des aufrufenden Prozesses: Blockierende Kommunikationsoperation die Rückkehr der Kontrolle zum aufrufenden Prozess bedeutet, dass alle Ressourcen (z.B. Puffer), die für den Aufruf benötigt werden, erneut für andere Operationen genutzt werden können. Alle durch den Aufruf ausgelösten Zustandsänderungen des aufrufenden Prozesses finden vor der Rückkehr der Kontrolle statt.
Nicht blockierende Kommunikationsoperation die aufgerufene Kommunikationsanweisung gibt die Kontrolle zurück, bevor die durch sie ausgelöste Operation vollständig abgeschlossen ist und bevor eingesetzte Ressourcen (z.B. Puffer) wieder benutzt werden dürfen.
Die ausgelöste Operation ist erst dann wieder vollständig abgeschlossen, wenn alle Zustandsänderungen dieser Operation für den die Prozedur aufrufenden Prozess sichtbar sind und alle Ressourcen wieder verwendet werden können.
22
Kapitel 10 ‒ Programmieren mit MPI
Allgemeines zu Kommunikation (2) Lokale und nicht-lokale Aufrufe (vgl. MPI-Standard „Semantic Terms“) Lokaler Aufruf einer Prozedur Ein Prozeduraufruf wird lokal genannt, wenn deren Ausführung alleine vom lokal ausgeführten Prozess abhängt.
Nicht-lokaler Aufruf einer Prozedur Abarbeitung hängt von anderen, nicht-lokalen Prozessen ab. Wenn dort eine bestimmte Bedingung nicht eintritt, kehrt der Aufruf unter Umständen nie zurück.
23
Kapitel 10 ‒ Programmieren mit MPI
Allgemeines zu Kommunikation (3) Aus globaler Sicht: Synchrone Kommunikation Die eigentliche Übertragung einer Nachricht findet nur statt, wenn Sender und Empfänger zur gleichen Zeit an der Kommunikation teilnehmen.
Asynchrone Kommunikation Der Sender kann Daten versenden ohne sicher zu sein, dass der Empfänger bereit ist, die Daten zu empfangen.
24
Kapitel 10 ‒ Programmieren mit MPI
Punkt-zu-Punkt-Kommunikation Einfachste Form des Datenaustausches zwischen zwei Prozessen. Genau 2 Prozesse beteiligt: Sender + Empfänger Beide müssen Kommunikationsanweisung explizit durchführen. MPI_Send / MPI_Recv Davon gibt es verschiedene Varianten.
25
Kapitel 10 ‒ Programmieren mit MPI
Senden und Empfangen (1) MPI_Send ( void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) buf: Adresse des Puffers mit den zu
sendenden Daten count: Anzahl der zu sendenden Elemente (nicht die Länge der Daten in Bytes !!) datatype: Typ der Elemente dest: Adresse (Rang) des Empfängers tag: Paketkennung (vom Benutzer frei wählbar) comm: Kommunikator
26
Kapitel 10 ‒ Programmieren mit MPI
MPI_Recv ( void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status) buf: Adresse des Puffers für die zu
empfangenden Daten count: Anzahl der zu empfangenden Datenelemente (nicht die Länge der Daten in Bytes !!) datatype: Typ der Elemente source: Adresse (Rang) des Senders (oder MPI_ANY_SOURCE) tag: Paketkennung (oder MPI_ANY_TAG) comm: Kommunikator status: Statusinformation und Fehlercodes (Rückgabewert)
Senden und Empfangen (2) MPI_Send() und MPI_Recv() Typischerweise blockierende, synchrone Operationen Recv kann gestartet werden, wenn Send noch nicht gestartet Recv blockiert, bis Nachricht erhalten wurde
Send kann gestartet werden, wenn Recv noch nicht gestartet Send blockiert, bis Nachricht gesendet wurde
Das tatsächliche Verhalten des Senders sowie Puffermechanismen hängen aber von der spezifischen MPI-Implementierung ab. Achtung: Fehlerquelle für Verklemmungen (Deadlocks)!
27
Kapitel 10 ‒ Programmieren mit MPI
Senden und Empfangen – Beispiel (1)
MPI_Comm_rank(comm, &myRank);
Warten auf Daten von P1
Quelle / Ziel if (myRank == 0) { /* P0 */ MPI_Recv(recvbuf, count, MPI_INT, MPI_Send(sendbuf, count, MPI_INT, } else if (myRank == 1) { /* P1 */ MPI_Recv(recvbuf, count, MPI_INT, MPI_Send(sendbuf, count, MPI_INT, }
1, tag, comm, &status); 1, tag, comm);
P0 Warten auf Daten von P0
0, tag, comm, &status); 0, tag, comm);
P1
Problem? Beide Prozesse warten gegenseitig aufeinander Keiner sendet Verklemmung (Deadlock) Parallele und verteilte Programmierung, T.Rauber, G. Rünger, Springer, 2000, S. 173 28
Kapitel 10 ‒ Programmieren mit MPI
Senden und Empfangen – Beispiel (2a) MPI_Comm_rank(comm, &myRank);
Quelle / Ziel if (myRank == 0) { /* P0 */ MPI_Send(sendbuf, count, MPI_INT, MPI_Recv(recvbuf, count, MPI_INT, } else if (myRank == 1) { /* P1 */ MPI_Send(sendbuf, count, MPI_INT, MPI_Recv(recvbuf, count, MPI_INT, }
1, tag, comm); 1, tag, comm, &status);
0, tag, comm); 0, tag, comm, &status);
Problem? Nur korrekt, wenn die abgeschickten Nachrichten aus sendbuf in einem Systempuffer zwischengespeichert werden und die Kontrolle danach sofort wieder an den Sender zurückgegeben wird. Ansonsten tritt eine Verklemmung (Deadlock) auf. Parallele und verteilte Programmierung, T.Rauber, G. Rünger, Springer, 2000, S. 173 29
Kapitel 10 ‒ Programmieren mit MPI
Senden und Empfangen – Beispiel (2b) Mit Systempuffer
Ohne Systempuffer Wartet, bis P1 die Daten entgegennimmt
P0 Send(P1)
P0
Receive(P1)
Send(P1)
Send(P0) Receive(P0)
P1
Aufruf kehrt sofort zurück Es ist sichergestellt, dass ein Prozess nur für ihn bestimmte Nachrichten bekommt. 30
Kapitel 10 ‒ Programmieren mit MPI
Wartet, bis P0 die Daten entgegennimmt
Send(P0)
P1
Aufruf blockiert Verklemmung
Senden und Empfangen – Beispiel (3) MPI_Comm_rank(comm, &myRank);
Quelle / Ziel
if (myRank == 0) { /* P0 */ MPI_Send(sendbuf, count, MPI_INT, MPI_Recv(recvbuf, count, MPI_INT, } else if (myRank == 1) { /* P1 */ MPI_Recv(recvbuf, count, MPI_INT, MPI_Send(sendbuf, count, MPI_INT, }
4. Receive(P1), Nimmt Daten entgegen
1, tag, comm); 1, tag, comm, &status);
0, tag, comm, &status); 0, tag, comm);
1. Send(P1)
P1
P0
2. Receive(P0), Nimmt Daten entgegen
4. Receive(P1), Nimmt Daten entgegen
2. Send(P1)
P0
3. Send(P0) 3. Receive(P1), Nimmt Daten entgegen
P1
2. Receive(P0), Nimmt Daten entgegen
3. Send(P0)
2. Send(P1)
P0
P1
1. Receive(P0), Nimmt Daten entgegen
P1 4. Send(P0)
1. Receive(P0), Nimmt Daten entgegen
3. Receive(P1), Nimmt Daten entgegen
1. Send(P1)
P0 4. Send(P0)
Parallele und verteilte Programmierung, T.Rauber, G. Rünger, Springer, 2000, S. 173 31
Kapitel 10 ‒ Programmieren mit MPI
Senden und Empfangen – Beispiel (4a) Bei mehr als zwei Prozessen (Gesamtzahl gerade): Prozesse mit ungerader ID senden zuerst an Prozesse mit gerader ID und empfangen anschließend von Prozessen mit gerader ID. Prozesse mit gerader ID empfangen zuerst von Prozessen mit ungerader ID und senden anschließend an Prozesse mit gerader ID. Beispiel:
Parallele und verteilte Programmierung, T.Rauber, G. Rünger, Springer, 2000, S. 174 32
Kapitel 10 ‒ Programmieren mit MPI
Senden und Empfangen – Beispiel (4a) Bei mehr als zwei Prozessen (Gesamtzahl gerade): Prozesse mit ungerader ID senden zuerst an Prozesse mit gerader ID und empfangen anschließend von Prozessen mit gerader ID. Prozesse mit gerader ID empfangen zuerst von Prozessen mit ungerader ID und senden anschließend an Prozesse mit gerader ID. Beispiel:
Prozess 1
Prozess 2
Prozess 3
Prozess 4
MPI_Send zu 2 MPI_Recv von 4
MPI_Recv von 1 MPI_Send zu 3
MPI_Send zu 4 MPI_Recv von 2
MPI_Recv von 3 MPI_Send zu 1
Parallele und verteilte Programmierung, T.Rauber, G. Rünger, Springer, 2000, S. 174 33
Kapitel 10 ‒ Programmieren mit MPI
Senden und Empfangen – Beispiel (4b)
Send()
P1
Send()
Send()
Send()
Receive()
Receive()
Receive()
P2
P3
P4
Receive()
34
Prozess 1
Prozess 2
Prozess 3
Prozess 4
MPI_Send zu 2 MPI_Recv von 4
MPI_Recv von 1 MPI_Send zu 3
MPI_Send zu 4 MPI_Recv von 2
MPI_Recv von 3 MPI_Send zu 1
Kapitel 10 ‒ Programmieren mit MPI
Senden und Empfangen (3) Hello World v2 in MPI #include #include "mpi.h"
0
int main (int argc, char **argv){ int rank, size, i, buf; MPI_Status status;
1
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); if(rank == 0){ /* Master process */ for(i = 1; i < size; i++){ MPI_Recv(&buf, 1, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status) printf("Got message from node %d\n",buf); } } else { /* Client process */ MPI_Send(&rank, 1, MPI_INT, 0, 0, MPI_COMM_WORLD); } MPI_Finalize(); return 0;
Rang des Empfängers
}
35
1
Kapitel 10 ‒ Programmieren mit MPI
2 2
n …
n
Weiteres Beispiel #include #include "mpi.h"
Gegeben n Prozesse Prozess 1 schickt Nachricht an Prozess 2 Jeder i-te Prozess leitet diese weiter an i+1 Prozess n gibt Nachricht aus.
int main( argc, argv ) { int rank, value, size; MPI_Status status; MPI_Init( &argc, &argv ); MPI_Comm_rank( MPI_COMM_WORLD, &rank ); MPI_Comm_size( MPI_COMM_WORLD, &size );
do { if (rank == 0) { scanf( "%d", &value ); MPI_Send( &value, 1, MPI_INT, rank + } else { MPI_Recv( &value, 1, MPI_INT, rank if (rank < size - 1) { MPI_Send( &value, 1, MPI_INT, rank } printf( "Process %d got %d\n", rank, } } while (value >= 0); MPI_Finalize( ); return 0; }
36
Kapitel 10 ‒ Programmieren mit MPI
1, 0, MPI_COMM_WORLD); 1, 0, MPI_COMM_WORLD, &status); + 1, 0, MPI_COMM_WORLD ); value );
Senden und Empfangen (4) Es gibt vier verschiedene Modi für Sendeoperationen: Standard Send (send). Buffered Send (bsend) Synchronous Send (ssend) Ready Send (rsend)
In allen Fällen kann der Sendepuffer wieder verwendet werden, wenn diese Aufrufe zurückkehren.
37
Kapitel 10 ‒ Programmieren mit MPI
Senden und Empfangen (5) Standard Send Allgemeine Sendeoperation Ob die Nachricht bei Standard Send gepuffert wird oder nicht, liegt an der jeweiligen Implementierung. Wird im Sendeknoten gepuffert, kehrt die Sendeoperation sofort zurück (dann ist der Aufruf lokal) Wird nicht gepuffert, muss unter Umständen gewartet werden, bis ein Empfangspuffer beim Empfangsknoten verfügbar wird oder bis der Empfänger seine Empfangsoperation startet. (dann ist der Aufruf nicht-lokal)
Das Verhalten beim Standard Send kann insbesondere auch von der Nachrichtengröße abhängen.
38
Kapitel 10 ‒ Programmieren mit MPI
Senden und Empfangen (6) Gepuffertes Senden Bei einer gepufferten Sendeoperation trägt das MPI-System des Sendeknotens dafür Sorge, dass die Nachricht irgendwo zwischengespeichert wird. Bei bsend() muss der Puffer dem MPI-System vorher durch den expliziten Aufruf von buf_attach() zur Verfügung gestellt werden.
Eine gepufferte Sendeoperation ist eine lokale Operation, d.h. sie wird immer beendet, ganz gleich, ob eine passende Empfangsoperation im Zielknoten vorliegt oder nicht. Ein Pufferüberlauf wird als Fehler behandelt. MPI_System
bsend() Sender
39
Kapitel 10 ‒ Programmieren mit MPI
Puffer
Empfänger
Senden und Empfangen (7) Synchrones Senden Eine synchrone Sendeoperation kehrt erst dann zurück, wenn der Empfänger einen passende Empfangsauftrag abgesetzt hat. Es muss aber noch kein Byte der Nachricht beim Empfänger angekommen sein. ssend: Der Aufruf ist nicht-lokal, d.h. ob (und wann) er
zurückkehrt, hängt vom Verhalten des Senders ab. recv started ?
ssend() proceed ! Sender
40
Kapitel 10 ‒ Programmieren mit MPI
[wait()] recv() Empfänger
Senden und Empfangen (8) Ready Send Einsparung der Abfrage, ob der Sender schon bereit ist Kann zur Leistungsverbesserung verwendet werden Einsparung zweier Rendezvous-Nachrichten
Ready Send entspricht Standard Send, aber das MPI-System des Senders geht davon aus, dass beim Empfänger ein passender Empfangsauftrag bereits vorliegt. Andernfalls ist das Verhalten der Anwendung undefiniert Folgen: Blockieren, Verlust der Nachricht, etc. Es wird dann kein Fehler signalisiert
Prinzipiell kann in einem korrekten Programm jedes Ready Send durch ein Standard Send ersetzt werden, ohne die Semantik zu verändern. 41
Kapitel 10 ‒ Programmieren mit MPI
Senden und Empfangen (9) SendReceive Operation Datenaustausch (gleichzeitiges Senden und Empfangen): MPI_Sendrecv(sendbuf, sendcount, sendtype, dest, sendtag, recvbuf, recvcount, recvtype, source, recvtag, comm, &status);
X
Sender
42
Kapitel 10 ‒ Programmieren mit MPI
Receiver
Weitere Details (1) Auftrennen der Aufrufe zum Senden und Empfangen: Sendeaufruf Empfangsaufruf
→ →
Sendestart + Abschluss Empfangsstart + Abschluss
Zwischen Start und Abschluss der Kommunikationsoperation können andere Aufgaben vom Prozess ausgeführt werden Die Kommunikation wird von MPI (bzw. dem Kommunikationssystem, meist von der Hardware) asynchron ausgeführt.
Beachte: Bei Initiieren der Sendeoperation wird Modus festgelegt isend(), ibsend(), irsend(), issend() Dagegen nur ein Aufruf zum Initiieren der Empfangsoperation: irecv()
unabhängig vom Sendemodus und davon, ob synchron oder asynchron gesendet wurde!
43
Kapitel 10 ‒ Programmieren mit MPI
Weitere Details (2) Die ixsend() und irecv() Aufrufe (mit x = „b“, „s“, „r“ oder „“) selbst sind lokal, d.h. kehren gewöhnlich sofort zurück. Auf die Beendigung einer Operation kann mit MPI_Wait() gewartet werden. Mit MPI_Test() kann überprüft werden, ob die Operation bereits abgeschlossen ist (lokaler Aufruf). Jede asynchrone Operation muss zu Ende geführt werden, d.h. es ist entweder MPI_Test() solange aufzurufen, bis sie abgeschlossen ist, oder
MPI_Wait() aufzurufen.
Auch die gemischte Variante: MPI_Test(), MPI_Test(), …, MPI_Wait() ist möglich.
Bei MPI_Test() und MPI_Wait() wird die gemeinte Operation durch ein Handle identifiziert, das von den ixsend() bzw. irecv() zurückgegeben wird.
44
Kapitel 10 ‒ Programmieren mit MPI
Empfangstests Idee: Schaue nach, ob es eine Nachricht gibt, die einem bestimmten Muster entspricht Danach je nach Status behandeln
Blockierender Empfangstest: MPI_Probe(int source, int tag, MPI_Comm comm, MPI_Status *status) Wartet, bis eine Nachricht verfügbar ist, die mit einem MPI_Recv() mit gleichem source und tag empfangen würde (nicht-lokaler Aufruf).
Nicht blockierender Empfangstest: MPI_Iprobe(int source, int tag, MPI_Comm comm, MPI_Status *status)
Testet, ob eine Nachricht verfügbar ist, die mit einem MPI_Recv() mit gleichem source und tag empfangen würde (lokaler Aufruf). 45
Kapitel 10 ‒ Programmieren mit MPI
MPI Datentypen (für C) MPI_CHAR, MPI_UNSIGNED_CHAR MPI_SHORT, MPI_UNSIGNED_SHORT MPI_INT, MPI_UNSIGNED MPI_LONG, MPI_UNSIGNED_LONG MPI_FLOAT MPI_DOUBLE MPI_LONG_DOUBLE MPI_BYTE MPI_PACKED
abgeleitete Datentypen (engl. derived data types)
46
Kapitel 10 ‒ Programmieren mit MPI
Abgeleitete Datentypen (1) Idee: Ein allgemeiner Datentyp besteht aus zwei Komponenten: einer Sequenz von Basistypen einer Sequenz von Abständen (gerechnet von einer Basisadresse buf an).
Dieses Komponentenpaar wird als Typemap mit der Signatur Typesig bezeichnet: Typemap = {(type0,disp0), … ,(typen-1,dispn-1)} Typesig = {type0, … ,typen-1}
Typemap und Basisadresse buf werden verwendet, um
einen passenden Kommunikationspuffer zu beschreiben. 47
Kapitel 10 ‒ Programmieren mit MPI
Abgeleitete Datentypen (2): „Contiguous“ und „Vector“ MPI_Type_contiguous(int count, MPI_Type oldtype, MPI_Type *newtype) Beispiel: Datentyp für RGB-Farben mit 3 Ganzzahlen MPI_Type_contignous(3, MPI_INT, &RGB_Color); MPI_Type_vector(int count, int blocklength, int stride, MPI_Type oldtype, MPI_Type *newtype) count=2 blocklength=3 stride=5
oldtype newtype
e1
e2
e3
Block 1 48
Kapitel 10 ‒ Programmieren mit MPI
(Anzahl der Blöcke) (Elemente pro Block), (Elemente zwischen dem Anfang von zwei Blöcken) e1
e2
e3 Block 2
Abgeleitete Datentypen (3): MPI_Type_contiguous MPI_Type_contiguous(…) liefert einen Identifikator für einen neuen,
abgeleiteten Datentyp. Der abgeleitete Datentyp besteht aus einer Menge gleicher Elemente eines bereits existierenden Datentyps. Die Elemente sind im Speicher hintereinander angeordnet. Beispiel: Datentyp zum Speichern von 4 Byte Werten (z.B. einer IP-Adresse) MPI_Datatype ipAddress_type; // Identifikator des neuen Typs MPI_Type_contiguous(4, MPI_BYTE, &ipAddress_type); MPI_Type_commit(&ipAddress_type); // Datentyp anmelden // … Datentyp verwenden … MPI_Type_free(&ipAddress_type); // Datentyp freigeben
Anmerkung: Wird der Typ mit MPI_Type_free(…) freigegeben, werden noch laufende Kommunikationen, die diesen Typ verwenden, nicht beeinflusst. Von diesem Typ abgeleitete Typen werden ebenfalls nicht beeinflusst. 49
Kapitel 10 ‒ Programmieren mit MPI
Kollektive Operationen (1) Synchronisation einer Prozessgruppe: MPI_Barrier(MPI_Comm comm) Achtung: Eine Barriere garantiert nur folgendes: Kein Prozess verlässt die Barriere, bevor nicht alle anderen Prozesse die Barriere betreten haben. Das heißt, dass nicht alle Prozesse die Barriere notwendigerweise zum gleichen Zeitpunkt verlassen.
P3
P0
P4
P2 P1
P5
Barriere Zeit
50
Kapitel 10 ‒ Programmieren mit MPI
P0
P1
P2
P3
P4
P5
Kollektive Operationen (2) Broadcast: Verteilen der Daten vom Prozess mit Kennung root zu allen anderen Prozessen in der Gruppe (einschließlich root). Kann von jedem der Prozesse aufgerufen werden. MPI_Bcast(void *buf, int count, MPI_Datatype datatype, int root, MPI_Comm comm)
Daten
A0
A0 A0
Prozesse
Prozess mit Rang root:
A0 A0 Broadcast
A0 A0
buf 51
Kapitel 10 ‒ Programmieren mit MPI
buf
Kollektive Operationen (3) Scatter: Verteilen von Elementen aus einem Kommunikationspuffer des Prozesses root an verschiedene Prozesse: MPI_Scatter(void *sendbuf, int sendcnt, MPI_Type sendtype,void *recvbuf, int recvcnt, MPI_Type recvtype, int root, MPI_Comm comm)
Der i-te Datenblock aus sendbuf wird an den i-ten Prozess gesendet. Ergebnis dasselbe, als ob root n einzelne Sendeoperationen auf Teile seines Puffers ausgeführt hätte. Daten
A0 A1 A 2 A3 A4 A5
A0 A1
Prozesse
root:
A2 A3 Scatter
A4 A5
sendbuf 52
Kapitel 10 ‒ Programmieren mit MPI
recvbuf
Kollektive Operationen (4) Gather: Aufsammeln von Kommunikationspuffern verschiedener Prozesse: MPI_Gather(void *sendbuf, int sendcnt, MPI_Type sendtype, void *recvbuf, int recvcnt, MPI_Type recvtype, int root, MPI_Comm comm)
Der Sendepuffer des i-ten Prozesses wird an der i-ten Position von recvbuf in root gespeichert. Ergebnis dasselbe, als ob folgende Einzeloperationen ausgeführt worden wären Jeder Prozess (inkl. root) sendet Inhalt seines Sendepuffers an root Der Prozess root empängt alle Nachrichten und speichert sie in der Reihenfolge der Ränge ab
Prozesse
Daten
A0
root:
B0 C0 D0 E0
sendbuf
F0 53
A0 B0 C 0 D 0 E0 F0
Kapitel 10 ‒ Programmieren mit MPI
Gather
recvbuf
Kollektive Operationen – Beispiel Matrixmultiplikation Gegeben: Matrix A, B der Größe N x N Prinzip bei 4 Prozessoren:
A
B
A 0 1 2 3
Scatter A, broadcast B, multipliziere Streifen lokal P0:
B
P1:
B
P2:
B
P3:
B
A0*B -> C A1*B->C A2*B->C A3*B->C
Gather: Setzt Streifen mit Ergebnis zu Matrix C zusammen
54
Kapitel 10 ‒ Programmieren mit MPI
C A0*B -> C A1*B->C A2*B->C A3*B->C
Kollektive Operationen – Beispiel für Scatter, Gather und Broadcast main(int argc, char *argv[]) { Berechne das Produkt MPI_Init (&argc, &argv); Matrizen matrixA und MPI_Comm_rank(MPI_COMM_WORLD, &myRank); MPI_Comm_size(MPI_COMM_WORLD, &commSize); matrixB parallel. MPI_Scatter(matrixA, N*N/commSize, MPI_INT, matrixA, N*N/commSize, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(matrixB, N*N, MPI_INT, 0, MPI_COMM_WORLD); for (i = 0; i < N/commSize; i++) { for (j = 0; j < N; j++) { matrixC[i][j] = 0; for (k = 0; k < N; k++) { matrixC[i][j] += matrixA[i][k] * matrixB[k][j]; } /* Ende k */ } /* Ende j */ } /* Ende i*/ MPI_Gather(matrixC, N*N/commSize, MPI_INT, matrixC, N*N/commSize, MPI_INT, 0, MPI_COMM_WORLD); MPI_Finalize(); }
55
Kapitel 10 ‒ Programmieren mit MPI
der
Kollektive Operationen (5) Allgather: Elemente von allen werden bei allen gesammelt (wie Gather, aber das Ergebnis findet sich hinterher bei jedem Prozess) MPI_Allgather(void *sbuf, int scnt, MPI_Type stype, void *rbuf, int rcnt, MPI_Type rtype, MPI_Comm comm)
Prozesse
Daten
A0
A 0 B 0 C 0 D 0 E 0 F0
B0
A0 B0 C 0 D 0 E0 F0
C0
A0 B0 C 0 D 0 E0 F0
D0
A0 B0 C 0 D 0 E0 F0 Allgather
E0 F0
A0 B0 C 0 D 0 E0 F0
sendbuf 56
A0 B0 C 0 D 0 E0 F0
Kapitel 10 ‒ Programmieren mit MPI
recvbuf
Kollektive Operationen (6) Alltoall: Alle erhalten von allen anderen jeweils ein bestimmtes Element Kann auch durch mehrere Send/Receive-Operationen ausgedrückt werden MPI_Alltoall(void *sbuf, int scnt, MPI_Type stype, void *rbuf, int rcnt, MPI_Type rtype, MPI_Comm comm) Prozesse
Daten
A0 A1 A2 A3 A4 A5
A0 B0 C 0 D 0 E0 F0
B0 B1 B2 B3 B4 B5
A1 B1 C 1 D 1 E1 F1
C0 C1 C2 C3 C4 C5 D0 D1 D2 D3 D4 D5
A2 B2 C 2 D 2 E2 F2 A3 B3 C 3 D 3 E3 F3
E0 E1 E2 E3 E4 E5 F0 F1 F2 F3 F4 F5
sendbuf 57
Kapitel 10 ‒ Programmieren mit MPI
Alltoall
A4 B4 C 4 D 4 E4 F4 A5 B5 C 5 D 5 E5 F5
recvbuf
Reduktionsoperationen (1) Globale Operationen auf verteilten Daten: MPI_Reduce(void *sendbuf, void *recvbuf, int count, MPI_Type datatype, MPI_Op operator, int root, MPI_Comm comm) op = MPI_MAX
op = MPI_SUM root
21
1
58
2
3
4
Kapitel 10 ‒ Programmieren mit MPI
5
root
8
6
1
5
3
8
5
2
Reduktionsoperationen (4) Vordefinierte Operatoren: Maximum, Minimum, Summe, Produkt MPI_MAX, MPI_MIN, MPI_SUM, MPI_PROD
Boolesche Operatoren (logische/bitweise Verknüpfung) MPI_LAND, MPI_LOR, MPI_LXOR,
MPI_BAND MPI_BOR MPI_BXOR
Maximum/Minimum inkl. Position MPI_MAXLOC, MPI_MINLOC
(als Datentyp wird ein Paar verwendet)
Es sind auch selbstdefinierte (assoziative) Operatoren möglich.
59
Kapitel 10 ‒ Programmieren mit MPI
Reduktionsoperationen (5) Globale Operationen auf verteilten Daten: MPI_Allreduce(void *sendbuf, void *recvbuf, int count, MPI_Type datatype, MPI_Op operator, MPI_Comm comm)
Wie Reduce, aber alle erhalten das Ergebnis.
8
8
8
8
8
8
21
21
21
21
21
21
1
5
3
8
5
2
1
2
3
4
5
6
op = MAX
60
Kapitel 10 ‒ Programmieren mit MPI
op = SUM
Reduktionsoperationen (6) Globale Operationen auf verteilten Daten mit Zwischenergebnissen (Prozess i erhält das Ergebnis einer Reduktionsoperation auf den Prozessen 1...i, also inklusiv) Präfix-Reduktion auf in der Gruppe verteilten Daten MPI_Scan(void *sendbuf, void *recvbuf, int count, MPI_Type datatype, MPI_Op operator, MPI_Comm comm)
61
1
5
1
5
5
8
3 8 op = MAX
Kapitel 10 ‒ Programmieren mit MPI
8
8
1
3
6
10
5
2
1
2
3 4 op = SUM
15
21
5
6
Zur Erinnerung – Numerische Integration P1
P2
P3
P4
1
4.04.0 f(x) = 2 dx = (1+x ) 2) (1+x
4.0
0
Pi
2.0
i
0.0 62
X
1.0
Kapitel 10 ‒ Programmieren mit MPI
Beispiel – Berechnung von π (1a) #include #include "mpi.h" int main (int argc , char ** argv) { double width; Folgendes Beispiel berechnet Pi double sum, lsum; näherungsweise. Zum Einsammeln der int intervals, i; Teilergebnisse werden MPI_Send() und int procCount , procRank; MPI_Receive() verwendet. MPI_Status status; /*Initialisierung der MPI-Umgebung */ if (MPI_Init(&argc,&argv) != MPI_SUCCESS) { exit(1); } /* Ermitteln von Größe der Gruppe und des Rangs */ MPI_Comm_size(MPI_COMM_WORLD, &procCount); MPI_Comm_rank(MPI_COMM_WORLD, &procRank);
63
Kapitel 10 ‒ Programmieren mit MPI
Beispiel – Berechnung von π (1b) intervals = atoi(argv[1]); width = 1.0 / intervals; intervalsPerProc = intervals / procCount; // zusätzlich intervalsPerProc für „letzten“ Prozessor anpassen procStart = procRank * intervalsPerProc; lsum = 0; for(i = procStart; i < procStart+intervalsPerProc; i++) { double x = (i+0.5) * width; lsum += 4.0/(1.0+x*x); } lsum *=width; if (procRank!= 0) MPI_Send(&lsum, 1, MPI_DOUBLE, 0, 0 , MPI_COMM_WORLD); else { sum = lsum; for( i = 1; i < procCount; ++i ) { MPI_Recv(&lsum, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status ); sum += lsum; } } MPI_Finalize(); return(0) }
64
Kapitel 10 ‒ Programmieren mit MPI
P1
P2
P3
P4
4.0
2.0
0.0
X
1.0
Beispiel – Berechnung von π (2a) #include #include "mpi.h" int main (int argc , char ** argv) { double width; double sum, lsum; int intervals, i; int procCount , procRank; MPI_Status status;
Folgendes Beispiel berechnet Pi näherungsweise. Zum Einsammeln der Teilergebnisse wird MPI_Reduce() verwendet.
/*Initialisierung der MPI-Umgebung */ if (MPI_Init(&argc,&argv) != MPI_SUCCESS) { exit(1); } /* Ermitteln von Größe der Gruppe und des Rangs */ MPI_Comm_size(MPI_COMM_WORLD, &procCount); MPI_Comm_rank(MPI_COMM_WORLD, &procRank);
65
Kapitel 10 ‒ Programmieren mit MPI
Beispiel – Berechnung von π (2b) intervals = atoi(argv[1]); width = 1.0 / intervals; lsum = 0; for(i = procRank; i < intervals; i += procCount) { double x = (i+0.5) Sendepuffer * width; der
lokalen Variablen lsum, die von allen an root gesendet werden
lsum += 4.0/(1.0+x*x); } lsum *= width;
Ergebnispuffer im Prozess root
MPI_Reduce(&lsum, &sum, 1, MPI_FLOAT, MPI_SUM, 0, MPI_COMM_WORLD); MPI_Finalize(); return 0; }
66
Kapitel 10 ‒ Programmieren mit MPI
root aggregiert Werte mit MPI_SUM und schreibt sie in sum
Ausblick: MPI-2 Neue Version des Standards, erweitert MPI-1 in einigen Bereichen: Loslösung vom starren Prozessmodell: Starten weiterer Prozesse zur Laufzeit möglich. Mit den neu gestarteten Prozessen kann kommuniziert werden. Kommunikation mit schon laufenden MPI-Anwendungen möglich Kommunikation über sog. Ports. Finden benannter Ports über eine Registry
Einseitige Kommunikation: Put, Get, Accumulate, Methoden zur Synchronisation Parallele Datei-Ein- und Ausgabeoperation Binding für C++ … weitere Verbesserungen, z.B. bei der Konstruktion von Datentypen.
67
Kapitel 10 ‒ Programmieren mit MPI
MPI-2: Einseitige Kommunikation Mit der kollektiven Operation MPI_Win_create(void* base, …, MPI_Communicator comm, MPI_Win *win)
definiert jeder Prozess aus comm einen Speicherbereich, auf den von den anderen Prozessen aus mit MPI_Put() oder MPI_Get() zugegriffen werden kann. MPI_Put() und MPI_Get() sind einseitige Operationen. Sie werden vom Quellprozess (source) unabhängig vom Zielprozess (target)
ausgeführt. Als Zielprozess wird der Prozess bezeichnet, auf dessen Speicherbereich zugegriffen wird. Synchronisation: Die Aufrufe zur einseitigen Kommunikation müssen von Aufrufen zur Synchronisation eingerahmt werden. Erst nach Abschluss dieser sind alle Datentransfers abgeschlossen, und die Speicherbereiche enthalten gültige Daten bzw. sind wiederverwendbar. 68
Kapitel 10 ‒ Programmieren mit MPI
MPI-2: Einseitige Kommunikation Source
Target
Speicher
Fenster
Fenster
Ablauf: Knoten „source“ greift mittels get() und put() auf das Fenster des Knotens „target“ zu. Für Quell- bzw. Zieldaten können im Prinzip beliebige Speicherbereiche verwendet werden. Knoten „target“ stellt sein Fenster zur Verfügung. Evtl. geänderte Daten sind erst nach Synchronisation sichtbar.
69
Kapitel 10 ‒ Programmieren mit MPI
Neuerungen in MPI 2.2 Aktuell ist MPI in der Version 2.2 vom 4. September 2009. Der Standard enthält unter anderem folgende neue Funktionen: Durchführen von lokalen Reduktionen ohne Kommunikation Zwei neue Varianten von MPI_reduce_scatter: Verteilen von gleich großen Blöcken an alle Prozesse Verteilen von unterschiedlich großen Blöcken an alle Prozesse
Gleichzeitiger/Nebenläufiger Zugriff auf den Senden-Puffer Folgender Code ist erst ab MPI 2.2 erlaubt: int sendbuf; MPI_Request req[2]; MPI_Isend(&sendbuf, 1, MPI_INT, 1, 1, MPI_COMM_WORLD, &req[0]); MPI_Isend(&sendbuf, 1, MPI_INT, 2, 1, MPI_COMM_WORLD, &req[1]); MPI_Waitall(2, &req);
70
Kapitel 10 ‒ Programmieren mit MPI
Ausblick: MPI-3 Erweiterung des Standards um folgende Funktionalität: Entfernter Speicherzugriff Ausfallsicherheit Weitere Varianten kollektiver Operationen
71
Kapitel 10 ‒ Programmieren mit MPI
Werkzeuge: Eine Auswahl Eclipse-Projekte Parallel Tools Platform (PTP) Setzt auf Eclipse CDT auf Enthält parallelen Debugger Analysewerkzeuge für MPI, OpenMP, UPC http://www.eclipse.org/ptp
gEclipse Bietet unter anderem: Werkzeuge für Fehlerfindung in Grid-Anwendungen Funktionen zur Visualisierung von Berechnungsergebnissen
http://www.geclipse.org/
72
Kapitel 10 ‒ Programmieren mit MPI
Werkzeuge: Marmot – MPI Correctness Checker Bibliothek für C und Fortran-Anwendungen, welche MPI1.2 verwenden Marmot erkennt Falsche Verwendung von MPI Nicht-portable Befehlskonstrukte Eventuelle Verklemmungen (durch Timeouts) und Wettlaufsituationen
Bettina Krammer et al. "MPI Correctness Checking with Marmot". In Proc. of 2nd HLRS Parallel Tools Workshop. Stuttgart, July 7-8 2008. 73
Kapitel 10 ‒ Programmieren mit MPI
Werkzeuge: SCALASCA Werkzeug zur inkrementellen Geschwindigkeitsanalyse auf großen Rechnersystemen. Unterstützung für MPI und OpenMP. Zu Analysierende Anwendung muss zuvor instrumentiert werden Benutzer kann Detailgrad der Analyse selbst festlegen, bspw. Geschwindigkeitsanalyse für bestimmte Pfade in der Anwendung.
M. Geimer, F. Wolf, B. J. N. Wylie, E. Abraham, D. Becker, B. Mohr: The SCALASCA Performance Toolset Architecture, Proc. Int'l Workshop on Scalable Tools for High-End Computing (STHEC, Kos, Greece), June 2008. 74
Kapitel 10 ‒ Programmieren mit MPI
Weitere Informationen Message Passing Interface Forum http://www.mpi-forum.org/ Argonne National Lab (University of Chicago) http://www-unix.mcs.anl.gov/mpi/ Open MPI: http://www.open-mpi.org/ Werkzeuge: Resch et al., Tools for High Performance Computing, Springer, 2008 Code-Beispiele: MPI-1: http://www-unix.mcs.anl.gov/mpi/usingmpi/examples/main.htm MPI-2: http://www-unix.mcs.anl.gov/mpi/usingmpi2/examples/main.htm
75
Kapitel 10 ‒ Programmieren mit MPI
Weitere Beispiele Beispiel-Implementierungen zur Berechnung von Mandelbrot- und Juliamengen: http://www.webacre.com/index.jsp?content=/cs/mandelbrot&footer=blank&splash=blank https://svn.mcs.anl.gov/repos/mpi/mpich2/branches/release/mpich2-1.2.1/examples
Linux-Live-CDs für Cluster: Cluster by Night: http://www.dirigibleflightcraft.com/research/CbN/ PelicanHPC: http://pareto.uab.es/mcreel/ParallelKnoppix/ uvm…
76
Kapitel 10 ‒ Programmieren mit MPI