OpenMPI
-
So,
jetzt ist es soweit - ich habe zunächst erfolgreich mittel MPI berechnet und nun geht es an die Matrix Vektor Multiplikation.
Die Idee dahinter ist zunächst recht einleuchtend: Den Vektor an alle Threads schicken und anschließend erhält jeder Thread eine Zeile der Matrix, um quasi daraus das Skalarprodukt zu bilden und das Ergebnis zurückzuschicken.
Ich habe mal ein broadcast mittels
std::vectorgeschrieben und hoffe, dass meine Erklärung richtig ist: DiesesMPI::COMM_WORLD.Bcastin Zeile 16 erwartet den Datentyp (MPI::DOUBLE), wo dieser liegt (b.data()) und wie viele davon, d.h. durch den Datentyp ermittelt die Schrittweite und wieviele Schritte von der genannten Adresse weitergegangen werden muss (b.size()).Der Knackpunkt ist nun, dass die Elemente des Vektors als zusammenhängend im Speicher liegen müssen.
Und genau das ist doch auch der Punkt bei einer Matrix: Wenn ich eine Matrix habe, dann liegen als Block n mal m Elemente im Speicher. Wenn ich z.B. die 3. Zeile haben möchte, dann muss ich ...
... beim ersten Element anfangen
... 2 mal m Elemente weitergehen, denn je m Elemente bilden eine Zeile
... und von dieser Adresse ab bis m Elemente weiter dieses sendenUnd genau dafür gibt es z.B. bei boost multi_array diese
views.Also vermute oder hoffe ich, dass z.B. boost oder vermadillo für sowas Interfaces haben, um nur bestimmte Einträge (z.B. Zeilen) eines Blocks (die ganze Matrix) zu verschicken.
#include <iostream> #include <vector> #include <mpi.h> int main(int argc, char* argv[]) { std::vector<double> b = { 1.0, 2.0, 3.0 }; int size(0), rank(0); MPI::Init(argc, argv); size = MPI::COMM_WORLD.Get_size(); rank = MPI::COMM_WORLD.Get_rank(); MPI::COMM_WORLD.Bcast( b.data(), b.size(), MPI::DOUBLE, 0 ); for(double x : b) std::cout << x << std::endl; MPI::Finalize(); return 0; }Viele Grüße,
-- Klaus.
-
Aber wo ist jetzt das Problem?
Generell gibt es für Matrizen zwei Möglichkeiten sie zu speichern.
Einmalint N = x; int M = y; std::vector<std::vector<double>> matrix(N, std::vector<double>(M, 0.0)); matrx[i][j] = x;Finde ich persönlich semantisch sehr sauber und auch (bis auf die Deklaration) relativ schön zu schreiben. Ist aber aus technischer Sicht nicht sehr effizient.
Zweitens:
int N = x; int M = y; std::vector<double> matrix(N*M, 0.0); matrix[i*M + j] = 0.0;(kann mich bei der Indexberechnung vertan haben, denke grad nicht genauer drüber nach, aber das Prinzip sollte klar sein, glaube ich)
Ist so erstmal nicht so schön, aber läuft gut. Andererseits, wenn man das in eine Klasse einkapselt ist es wieder sauber ;).
Bei beiden Möglichkeiten kommst du relativ leicht an eine Zeile (oder Spalte, je nachdem wie du es interpretierst, hauptsache du bleibst konsistent), jedoch nicht an eine Spalte (bzw. Zeile...). und das ist auch echt doof bei Matrizen, aber da kannst du nichts gegen machen.
-
Hallo,
okay. Also ich habe mir da was zusammengebastelt.
Das einzige was ich jetzt nicht verstehe: Wozu ein Broadcast? Denn wenn ich mpi langsam anfange richtig zu verstehen, dann startet jeder Prozess das komplette Programm. D.h. wenn ich mittels
mpirun -np 3 ./mainstarte, dann laufen drei Prozesse (Threads?) und dabei jedes Mal das komplette Programm. Also 'jeder Prozess' kennt A und b! Ich muss da nix broadcasten!Das einzige was das folgende Programm nun tut ist, dass jeder Prozess einen eigenen Eintrag des Vektors c berechnet und am Ende führe ich mittels
Reducealles zusammen.Nur der Sinn von
broadcasterschließt sich mir nicht. Vielleicht ist es hier (entgegen des Beispiels im Buch) auch eigentlich nicht angebracht bzw. unnötig?
#include <iostream> #include <numeric> #include <vector> #include <mpi.h> int main(int argc, char* argv[]) { /* Ab = c */ std::vector<double> A = { 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0 }; std::vector<double> b = { 1.0, 2.0, 3.0 }; std::vector<double> c( b.size(), 0.0 ); std::vector<double> my_c( b.size(), 0.0 ); int size(0), rank(0); MPI::Init(argc, argv); size = MPI::COMM_WORLD.Get_size(); rank = MPI::COMM_WORLD.Get_rank(); for(int i = 0; i < b.size(); ++i) { int offset = rank * b.size(); my_c[rank] = std::inner_product( A.begin() + offset, A.begin() + offset + b.size(), b.begin(), 0.0); } MPI::COMM_WORLD.Reduce( my_c.data(), c.data(), c.size(), MPI::DOUBLE, MPI::SUM, 0); if( rank == 0 ) for(double x : c) std::cout << x << std::endl; MPI::Finalize(); return 0; }Viele Grüße,
-- Klaus.
-
Meinst du allgemein den Sinn von Broadcast? Das Programm ist nicht immer im Anfangszustand. Nachdem die einzelnen Prozesse unterschiedliche Dinge berechnet haben, muessen sie in der Regel auch die Ergebnisse austauschen. Ein Broadcast ist da - je nach genauer Situation - genau richtig. Eine einfache Beispielanwendung, bei der ein Broadcast genau das richtige Mittel ist, ist folgendes: Das Programm liest eine Konfigurations- und/oder Skriptdatei ein. Es ist aber doof, diese von zig Prozessen gleichzeitig lesen und verarbeiten zu lassen. Stattdessen liest nur ein Prozess (meistens waehlt man dafuer die Nummer 0) die Daten ein und schickt sie dann an alle Prozesse. Dieses Beispiel wird noch eine Nummer extremer, wenn man sich vorstellt, dass die Konfigurationsdaten moeglicherweise interaktiv vom Nutzer eingegeben werden sollen. Wenn da jeder Prozess fragen wuerde, dann muesste der Nutzer fuer jeden Prozess diese Daten eingeben (und was passiert, wenn er unterschiedlichen Prozessen dann inkonsistente Daten gibt?).
-
Guten Morgen SeppJ,
ich denke die Bedeutung von Broadcast ist mir bewusst.
Ich versuche meine Frage zu präzisieren (Stream of consciousness): Wenn ich an OpenMP denke, dann ist ziemlich klar, ab wann parallel gerechnet wird, nämlich wenn
#pragma omp parallel { }erscheint. Möchte ich, dass z.B. jeder Thread eine lokale Kopie erhält, muss ich für einen ordentlichen Copyconstructor von T sorgen und schreiben
#pragma omp parallel firstprivate(T) { }Bei MPI erscheint mir der Spieß nun umgedreht: Jeder Thread startet das Programm selbst, d.h. es gibt kein 'vor / nach der Parallelisierung' wie bei OpenMP durch die
pragma omp parallelUmgebung möglich.Wenn ich wie in deinem Beispiel möchte, dass nur ein Thread eine Datei einliest, dann muss ich das aktiv z.B. so gestalten
MPI::Init(argc, argv); rank = MPI::COMM_WORLD.Get_rank(); if( rank == 0 ) /* declared Master Thread */ // read fileSo gesehen gibt es auch keinen Master Thread, sondern es gibt lediglich einen Thread, den ich (manuell) auszeichne, wie eben gezeigt mit der if Abfrage.
Ist klar was ich meine?

Gruß,
-- Klaus.
-
Ich denke schon. Und genau hierfür brauchst du den Broadcast nicht. Du kensnt ja schon alle Startwerte, die hart im Code codiert sind (blöde Satzformulierung oO). D.h. jeder Prozess (bei MPI sind das richtige Prozesse, nicht nur Threads) kennt die zu Beginn.
Mithilfe des Ranks (d.h. der Nummer des aktuellen Prozesses) kannst du dir dann deine Daten aus den Vektoren rausziehen, die du berechnen willst und sie zum Ende hin zusammenführen.
Und so machst du das ja auch schon...
-
Also ich verstehe die Anwendung von Broadcast immer noch nicht so recht. Ich habe in Anlehnung an SeppJs Vorschlag ein Miniprogramm, in dem der Master einen Vektor initialisiert und dann an alle anderen Programme 'broadcasten' soll:
Jetzt kriege ich als Fehlermeldung, dass
b not declared in this scope, was auch irgendwie einleuchtet, denn der Vektor wird nur in der if-Abfrage deklariert.Ja aber wie soll ich dann Daten, die nur der Master initialisiert verbreiten? Das riecht ein wenig danach, dass ich allgemein den Vektor b deklarieren muss, so dass jedes Programm ihn kennt, nur der Master den Vektor mit Inhalt füllt und diesen dann 'broadcastet'.
Also ein zweites Programm, siehe weiter unten: Da wird zu Beginn der Vektor b der Größe 3 mit Nullen angelegt, der Master ersetzt den Inhalt und verteilt dann. Wenn ich allerdings das Programm ausführe, dann zeit die range for Schleife nur ein Mal den neuen Inhalt an, nämlich beim Master.
Also wie soll dieses Broadcasten funktionieren? Das muss doch eine Art 'globales Send and Receive' sein, nur ich sehe vom Syntax nicht, wie die Daten tatsächlich bei den anderen Programm ankommen bzw. verarbeitet werden können (wie im Falle eines einfachen
std::cout, um den Inhalt anzuzeigen).Edit:
Okay, das Broadcast muss einfach aus der if-Abfrage heraus!

Warum auch immer.
#include <iostream> #include <numeric> #include <vector> #include <mpi.h> int main(int argc, char* argv[]) { MPI::Init(argc, argv); int const size = MPI::COMM_WORLD.Get_size(); int const rank = MPI::COMM_WORLD.Get_rank(); std::vector<double> b( 3, 0.0 ); if( rank == 0) { b = { 1.0, 2.0, 3.0 }; } MPI::COMM_WORLD.Bcast( b.data(), b.size(), MPI::DOUBLE, 0); for(double x : b) std::cout << x << std::endl; MPI::Finalize(); return 0; }Gruß,
-- Klaus.
-
Also fuer jemanden, der sich einen Monat damit beschaeftigt hat, ist das schon ein bisschen schwach. Das ist eigentlich der Stoff fuer den ersten Nachmittag. Hilft dir folgendes Beispiel auf die Spruenge?
#include <iostream> #include <vector> #include <mpi.h> using namespace std; int main(int argc, char* argv[]) { MPI_Init(&argc, &argv); int rank, size; MPI_Comm_rank (MPI_COMM_WORLD, &rank); MPI_Comm_size (MPI_COMM_WORLD, &size); vector<int> data; if (rank == 0) // Prozess 0 liest die Daten ein { cout << "Daten eingeben:\n"; for (int value; cin >> value; data.push_back(value)); } // Daten werden verteilt unsigned long num_values = data.size(); MPI_Bcast(&num_values, 1, MPI_UNSIGNED_LONG, 0, MPI_COMM_WORLD); if (rank != 0) { data.resize(num_values); } MPI_Bcast(data.data(), num_values, MPI_INT, 0, MPI_COMM_WORLD); for (unsigned long i = 0; i < num_values; ++i) cout << "Prozess " << rank << " Wert[" << i << "] = " << data[i] << '\n'; MPI_Finalize(); }
-
Mal ganz abseits von der Fragestellung.
Ich hab bisher immer nur was mit der C-Schnittstelle gearbeitet, aber kann es sein, dass die C++ Schnittstelle nicht gerade so toll ist?! Das sieht mir echt nicht schön aus...
-
Skym0sh0 schrieb:
Ich hab bisher immer nur was mit der C-Schnittstelle gearbeitet, aber kann es sein, dass die C++ Schnittstelle nicht gerade so toll ist?! Das sieht mir echt nicht schön aus...
Ich als Noob habe davon natürlich keine Ahnung, finde meinen Sourcecode oben aber ganz hübsch.
Ich wäre ja schon froh, wenn ich mal eine geschlossene Anleitung zu MPI mittels C++(11) finden würde. Es findet sich viel in C oder Fortran und dann muss ich mir die Details mittels google und stackoverflow zusammensuchen.
Daher würde mich interessieren, ob sich die Verwendung von mpi Boost lohnt. Das wirkt dann im Zusammenhang mit C++(11) etwas einheitlicher.
Gruß,
-- Klaus.
-
Skym0sh0 schrieb:
Ich hab bisher immer nur was mit der C-Schnittstelle gearbeitet, aber kann es sein, dass die C++ Schnittstelle nicht gerade so toll ist?! Das sieht mir echt nicht schön aus...
Klaus82 schrieb:
Daher würde mich interessieren, ob sich die Verwendung von mpi Boost lohnt. Das wirkt dann im Zusammenhang mit C++(11) etwas einheitlicher.
Die C++-Bindings in den ueblichen MPI-Implementierungen kannst du vergessen. Die sind ersten deprecated und zweitens sind das praktisch die C-Bindings, bloss dass man statt "MPI_" vor dem Funktionsnamen nun einen richtigen Namespace hat. Also nichts gewonnen.
Boost MPI ist ok. Als ich es das letzte Mal benutzt habe, fehlte leider noch einiges an Funktionalitaet, aber nur so exotische Sachen, die man ohnehin nicht braucht. Dafuer ist es richtiges C++, welches die Moeglichkeiten der Sprache auch nutzt. Dadurch ist es sehr angenehm, damit zu arbeiten und man vermeidet Fehler (zumindest Fehler in der Nutzung (z.B. Finalize oder Init vergessen), nicht in der Logik. Paralleles Programmieren ist manchmal schwer
). Leider ist der Einarbeitungsaufwand recht gross, da man gleich auch noch Boost Serialization und Boost Graph benoetigt (Serialization braucht man dringend, Graph ist eher optional). Das ist zwar letztendlich viel angenehmer, als alle Daten selber zu verpacken oder die Kommunikationsstruktur selber zu programmieren, aber ist schon eine recht grosse Huerde fuer den Einstieg, da die beiden Bibliotheken doch recht komplex sind.
-
Hm,
ich denke ich werde nicht tiefer einsteigen als dieses Broadcast und vielleicht noch send / receive.Dazu scheint es anhand der Beispiele zu genügen die zugehörigen
boostHeader Dateien einzubinden und schließlich den Syntax zu ändern.Das sollte es für meine minimalen Anforderungen schon gewesen sein?
Gruß,
-- Klaus.
-
Klaus82 schrieb:
Das sollte es für meine minimalen Anforderungen schon gewesen sein?
Wir kennen ja deine Anforderungen nicht. Die meisten MPI-Programme bestehen aber zum größten Teil aus einer Mischung aus Send, Receive und Broadcast. Aus was auch sonst? MPI dient der Kommunikation und dies sind die Kommunikationsmethoden. Der Rest von MPI dient dem Aufsetzen und Feintuning der Kommunikation. Wenn einem die Standardeinstellungen passen (90% der Fall), dann braucht man davon nur Init, Finalize, GetRank und ähnliches.
Die wahre Schwierigkeit ist aber das parallele Programmieren an sich. MPI ist nur das Werkzeug und seine Benutzung ist der mit Abstand einfachste Teil der parallelen Programmierung.