Schauen Sie sich Mr. Robotas diesen Tweet bezüglich MPI an und wie funktioniert jeder Prozess zusammen (hauptsächlich hinsichtlich der Zusammenstellung der Standardausgabe)? Ich war neugierig auf die Pufferkontrolle und den Punkt, also habe ich eine grobe Umfrage durchgeführt.
Dieses Mal habe ich nicht im Detail über die Umgebung geschrieben. Nehmen Sie also bitte den Rücken und verwenden Sie ihn selbst, ohne ihn zu nehmen. Bitte beachten Sie außerdem, dass die verwendeten Begriffe dem Text ganz beigefügt sind.
Außerdem habe ich die Linux-Umgebung ausprobiert und es gibt drei Arten von MPI: Intel, SGI-MPT und OpenMPI. Auch Fortran wird nicht erwähnt. Für C / C ++.
Zunächst zum Thema "Pufferung".
Bei der Ausgabe von Daten aus der Standardausgabe erfolgt dies über stdout für C und std :: cout für C ++. Selbst wenn Sie printf oder std :: ostream :: operator << aufrufen, wird die Ausgabe sofort wiedergegeben. Es wird nicht immer gemacht. Dies liegt daran, dass das Aufrufen der OS-APIs (Schreiben und Senden), die tatsächlich für die Ausgabe in kleinen Teilen zuständig sind, im Allgemeinen hinsichtlich der Leistung nachteilig ist und in der Standardbibliothek zu einem gewissen Grad in einem Puffer gespeichert und zusammengefasst wird. Das liegt daran, dass ich versuche, alles auf einmal zu spucken. Dieses Verhalten wird als ** Pufferung ** bezeichnet.
Es gibt drei Arten der Pufferung:
Für C stdio erfolgt die Steuerung mit setbuf / setvbuf. Die Standardsteuerung hängt von der Datei / dem Gerät ab, an die / das der Standardausgang angeschlossen, für TTY / PTY leitungsgepuffert und ansonsten vollständig gepuffert ist. Der Spülvorgang wird mit der Spülfunktion ausgeführt.
Bei C ++ iostream wird gesteuert, ob das Flag std :: ios_base :: unitbuf auf std :: cout (std :: cout.setf oder unsetf) gesetzt werden soll. Wenn es gesetzt ist, ist es ungepuffert, wenn es nicht gesetzt ist, ist es vollständig gepuffert und es ist keine Zeile gepuffert. Der Standardwert ist voll gepuffert. Die Spüloperation wird mit dem E / A-Manipulator std :: flush oder std :: endl ausgeführt.
Wie Sie sehen können, sind die Steuerelemente für C und C ++ getrennt. Bei gemischtem C / C ++ - Code ist es jedoch normalerweise ein Problem, wenn die Ausgabereihenfolge gestört ist, sodass beide standardmäßig synchronisiert sind. Ich werde. Mit anderen Worten, selbst wenn es auf der C ++ - Seite vollständig gepuffert ist, wird es auf die C-Seite gezogen, wenn dies auf der C-Seite nicht der Fall ist. Dieses Verhalten kann jedoch von std :: ios_base :: sync_with_stdio (false) überschrieben werden.
Grob gesagt ist MPI, dass, wenn Sie die Knoten angeben, die mobilisiert werden können, und die Anzahl der auszuführenden Prozesse, mehrere Knoten dasselbe (oder sogar heterogene) Programm starten und kooperative Berechnungen durchführen können. Es ist eine Bibliothek und eine Gruppe von Werkzeugen.
Daher gibt es verschiedene Hochgeschwindigkeitsnetzwerke wie InfiniBand als Kooperation zwischen den gestarteten Programmen, aber diesmal denke ich über den "Standard-Ausgabefluss" nach. Daher reicht es aus, die drei in der folgenden Abbildung gezeigten Faktoren zu berücksichtigen.
** Dies ist ein beliebiger Begriff **,
Es wird klassifiziert als. In der obigen Abbildung sind die Vorderseite und die anderen so gezeichnet, als würden sie auf verschiedenen Knoten ausgeführt, sie können jedoch gleich sein.
Intel MPI Erstens ist Intel MPI. Es sieht aus wie in der folgenden Abbildung.
Die Rollen und Antworten sind wie folgt.
Nach dem Start stellt der Manager eine TCP / IP-Verbindung zur Front her, aggregiert die vom Worker weitergeleitete Ausgabe und sendet sie an die Front.
SGI MPT Als nächstes kommt SGI MPT.
Die Rollen und Antworten sind wie folgt.
Die Aufteilung der Rollen ähnelt dem zuvor erwähnten Intel MPI. Für den Start des Managers von vorne ist jedoch ein Daemon namens arrayd (auch wenn es sich um einen lokalen Knoten handelt) erforderlich, der mit dem SGI MPT geliefert wird.
OpenMPI Schließlich OpenMPI.
Die Rollen und Antworten sind wie folgt.
Der große Unterschied zu den beiden oben genannten MPIs besteht darin, dass die Kommunikation zwischen Managern und Mitarbeitern zu PTY wird, wie dies beim Umgang mit lokalen Knoten der Fall ist.
Wenn Sie mit einem MPI-Programm ausgeben, werden wir untersuchen, was passiert, wenn es schließlich aggregiert und nach vorne ausgegeben wird.
Wie oben beschrieben, arbeiten drei Arten von Programmen, Front Manager und Worker, bei der Ausführung von MPI zusammen. Und ** Worker Output wird über den Manager nach vorne aggregiert **. Daher ist es auch erforderlich, die Pufferung für jede Route zu organisieren. Das ist,
Es gibt drei Orte.
Intel MPI Im Fall von Intel MPI wird die Ausgabe jedes Arbeiters sogar in der Mitte der Zeile gemischt. Es sieht also so aus, als ob die Pufferung deaktiviert ist.
Dies liegt daran, dass die MPI-Bibliothek während MPI_Init intern setbuf / setvbuf aufruft, um den Worker in einen ungepufferten Zustand zu versetzen **. Mit anderen Worten, der Worker-> Manager-Teil ist zum Puffern deaktiviert, und das Manager-> Front-, Front-> Endausgabeziel wird verschüttet, da es ohne besondere Steuerung ist. Es sieht also so aus, als ob die Pufferung insgesamt deaktiviert ist.
Daher können Sie nach MPI_Init die Pufferung aktivieren, indem Sie setbuf / setvbuf aufrufen und die Pufferung neu konfigurieren. Darüber hinaus scheint das Flag von std :: cout weder in MPI_Init noch in MPI: Init geändert zu werden. Wenn es sich also um eine reine C ++ - Anwendung handelt, wird die Pufferung durch Deaktivieren der C, C ++ - Synchronisation aktiviert. ..
-ordered-output Use this option to avoid intermingling of data output from the MPI processes. This option affects both the standard output and the standard error streams. NOTE When using this option, end the last output line of each process with the end-of-line '\n' character. Otherwise the application may stop responding.
SGI MPT Im Fall von SGI MPT ist die Ausgabe zeilenweise organisiert, was einem zeilengepufferten Verhalten entspricht.
Der Mechanismus dahinter ist etwas kompliziert.
Mit anderen Worten, es wird durch die Bemühungen der Rezeption gepuffert. Umgekehrt kann es sein, dass Sie nicht möchten, dass die MPI-Anwendung (Worker) den Puffer ohne Erlaubnis steuert.
OpenMPI OpenMPI verhält sich wie SGI MPT wie zeilengepuffert.
Dieser Mechanismus ist sehr einfach, da die Ausgabe zwischen Worker und Manager PTY ist und das Standardsteuerelement dafür standardmäßig zeilengepuffert ist. Andere Manager-> Front, Front-> Endgültiges Ausgabeziel scheint nichts Besonderes zu steuern. Mit anderen Worten, OpenMPI selbst befasst sich nicht speziell mit der Puffersteuerung und überlässt sie der Standardbibliothek.
Ich habe also den Unterschied in der Kontrolle in jedem MPI gesehen.
Obwohl es bei jedem MPI Unterschiede gibt, ist es meiner Meinung nach besser, setbuf / setvbuf unmittelbar nach MPI_Init aufzurufen, wenn Sie sicherstellen möchten, dass die Pufferung effektiv ist.
Im Folgenden werden die Quelle und das Betriebsprotokoll als Referenz für das Verhalten mit Intel MPI als Referenz aufgeführt.
Betriebsprotokoll
$ cat /etc/centos-release
CentOS Linux release 7.4.1708 (Core)
$ mpirun --version
Intel(R) MPI Library for Linux* OS, Version 2018 Update 1 Build 20171011 (id: 17941)
Copyright (C) 2003-2017, Intel Corporation. All rights reserved.
$ icpc --version
icpc (ICC) 18.0.1 20171018
Copyright (C) 1985-2017 Intel Corporation. All rights reserved.
$ mpiicpc -std=gnu++11 -o test test.cpp
$ mpirun -np 2 ./test
abababababababababbababababababababababa
abababababababababababababababababababab
a
bbabaababababababababababababababababab
a
bababbaababababbaababababababababababab
babaabababababababababababababababababab
$ mpirun -np 2 ./test --nosync
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
$ mpirun -np 2 ./test --setvbuf
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
$ mpirun -np 2 ./test --nosync --unitbuf
abababbaabababababababababababababababab
a
bababababababababababababababababababab
babababababababababababababababababababa
ababababbabababababababababababababababa
abababababababababababababababababababab
$ mpiicpc -std=gnu++11 -o test2 test2.cpp
$ mpirun -np 2 ./test2
abababababbaababababababbaababababababab
babaabababbaabababababababababababababab
babababababababababababababababababababa
ababababbaabababababbabaabababababababab
a
bababababababababababababababababababab
$ mpirun -np 2 ./test2 -f
aaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbb
$ mpirun -np 2 ./test2 -l
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
$
test.cpp
#include <mpi.h>
#include <iostream>
#include <thread>
#include <string>
#include <cstdio>
static char stdoutbuf[8192];
int main(int argc, char **argv) {
MPI::Init(argc,argv);
MPI::COMM_WORLD.Set_errhandler(MPI::ERRORS_THROW_EXCEPTIONS);
int rank = MPI::COMM_WORLD.Get_rank();
for ( int i=1; i<argc; i++ ) {
std::string opt(argv[i]);
if ( opt == "--nosync" ) {
// detach C++-iostream from C-stdio
std::ios_base::sync_with_stdio(false);
}
else if ( opt == "--setvbuf" ) {
// re-setvbuf for C-stdio
std::setvbuf(stdout,stdoutbuf,_IOFBF,sizeof(stdoutbuf));
}
else if ( opt == "--unitbuf" ) {
// disable buffering on C++-iostream
std::cout.setf(std::ios_base::unitbuf);
}
else if ( rank == 0 ) {
std::cerr << "invalid option: " << opt << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
char c='a'+rank;
for ( int i=0; i<5; i++ ) {
MPI::COMM_WORLD.Barrier();
for ( int j=0; j<20; j++ ) {
std::cout << c;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::cout << std::endl;
}
MPI::Finalize();
}
test2.cpp
#include <mpi.h>
#include <iostream>
#include <thread>
#include <string>
#include <cstdio>
static char stdoutbuf[8192];
int main(int argc, char **argv) {
MPI::Init(argc,argv);
MPI::COMM_WORLD.Set_errhandler(MPI::ERRORS_THROW_EXCEPTIONS);
int rank = MPI::COMM_WORLD.Get_rank();
if ( argc > 1 ) {
std::string opt(argv[1]);
if ( opt == "-f" ) {
// full buffered
std::setvbuf(stdout,stdoutbuf,_IOFBF,sizeof(stdoutbuf));
}
else if ( opt == "-l" ) {
// line buffered
std::setvbuf(stdout,stdoutbuf,_IOLBF,sizeof(stdoutbuf));
}
}
char c='a'+rank;
for ( int i=0; i<5; i++ ) {
MPI::COMM_WORLD.Barrier();
for ( int j=0; j<20; j++ ) {
std::cout << c;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::cout << '\n';
}
std::cout << std::flush;
MPI::Finalize();
}
Recommended Posts