MPI – Imprimer dans un ordre

J’essaie d’écrire une fonction en C où chaque processeur imprime ses propres données. Voici ce que j’ai:

void print_mesh(int p,int myid,int** U0,int X,int Y){ int i,m,n; for(i=0;i<p;i++){ if(myid==i){ printf("myid=%d\n",myid); for(n=0;n<X;n++){ for(m=0;m<Y;m++){ printf("%d ",U0[n][m]); } printf("\n"); } } else MPI_Barrier(MPI_COMM_WORLD); } } 

Cela ne fonctionne pas pour une raison quelconque. Les tableaux sont imprimés tous mélangés. Avez-vous une idée de pourquoi cela ne fonctionne pas? Des idées qui fonctionnent? Si possible, je ne souhaite pas envoyer l’ensemble du tableau dans un processus maître. Aussi, je ne veux pas utiliser de fonctions précompilées.

Il n’y a aucun moyen de garantir que les messages provenant de nombreux processus différents arriveront dans le “bon” ordre lorsqu’ils arriveront à un autre processus. C’est essentiellement ce qui se passe ici.

Même si vous n’envoyez pas explicitement de messages, lorsque vous imprimez quelque chose à l’écran, il doit être envoyé au processus sur votre système local ( mpiexec ou mpirun ), où il peut être imprimé à l’écran. MPI n’a aucun moyen de savoir quel est le bon ordre pour ces messages, il les imprime donc au fur et à mesure de leur arrivée.

Si vous souhaitez que vos messages soient imprimés dans un ordre spécifique, vous devez les envoyer tous à un rang qui peut les imprimer dans l’ordre de votre choix. Tant qu’un seul rang effectue l’impression, tous les messages sont classés correctement.

Il est à noter que vous trouverez probablement des réponses indiquant que vous pouvez mettre une nouvelle ligne à la fin de votre chaîne ou utiliser flush () pour vous assurer que les tampons sont vidés, mais cela ne garantira pas la commande sur l’extrémité distante pour les raisons mentionnées ci-dessus.

Donc, vous pouvez faire quelque chose comme ça:

 MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &size); MPI_Comm_rank(MPI_COMM_WORLD, &rank); if (rank == 0) { MPI_Send(&message, 1, MPI_INT, 1, 0, MPI_COMM_WORLD); printf("1 SIZE = %d RANK = %d MESSAGE = %d \n",size,rank, message); } else { int buffer; MPI_Status status; MPI_Probe(MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, &status); MPI_Get_count(&status, MPI_INT, &buffer); if (buffer == 1) { printf("2 SIZE = %d RANK = %d MESSAGE = %d \n",size,rank, message); MPI_Recv(&message, buffer, MPI_INT, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, &status); if (rank + 1 != size) { MPI_Send(&message, 1, MPI_INT, ++rank, 0, MPI_COMM_WORLD); } }; }; MPI_Finalize(); 

Après exécution:

 $ mpirun -n 5 ./a.out 1 SIZE = 5 RANK = 0 MESSAGE = 999 2 SIZE = 5 RANK = 1 MESSAGE = 999 2 SIZE = 5 RANK = 2 MESSAGE = 999 2 SIZE = 5 RANK = 3 MESSAGE = 999 2 SIZE = 5 RANK = 4 MESSAGE = 999 

La réponse de Святослав Павленко m’a inspiré: utiliser les communications MPI bloquantes pour appliquer la sortie série dans le temps. Alors que Wesley Bland a une remarque sur le fait que MPI n’est pas construit pour une sortie série. Donc, si nous voulons sortir des données, il est logique que chacune des données de sortie du processeur (non en collision). Sinon, si l’ordre des données est important (et que ce n’est pas trop grand), il est recommandé de tout envoyer sur cpu (par exemple rang 0), qui formate ensuite les données correctement.

Pour moi, cela semble être un peu exagéré, en particulier lorsque les données peuvent être des chaînes de longueur variable, ce qui est trop souvent ce que std::cout << "a=" << some_varible << " b=" << some_other_variable est souvent. Donc, si nous voulons des impressions rapides dans l’ordre, nous pouvons exploiter la réponse de Святослав Павленко pour créer un stream de sortie série. Cette solution fonctionne bien, mais ses performances évoluent mal avec de nombreux processeurs. Ne l'utilisez donc pas en sortie de données!

 #include  #include  #include  

MPI Ménage:

 int mpi_size; int mpi_rank; void init_mpi(int argc, char * argv[]) { MPI_Init(& argc, & argv); MPI_Comm_size(MPI_COMM_WORLD, & mpi_size); MPI_Comm_rank(MPI_COMM_WORLD, & mpi_rank); } void finalize_mpi() { MPI_Finalize(); } 

Classe d'usage général permettant le chaînage de messages MPI

 template class MPIChain{ // Uses a chained MPI message (T) to coordinate serial execution of code (the content of the message is irrelevant). private: T message_out; // The messages aren't really used here T message_in; int size; int rank; public: void next(){ // Send message to next core (if there is one) if(rank + 1 < size) { // MPI_Send - Performs a standard-mode blocking send. MPI_Send(& message_out, 1, MPI_T, rank + 1, 0, MPI_COMM_WORLD); } } void wait(int & msg_count) { // Waits for message to arrive. Message is well-formed if msg_count = 1 MPI_Status status; // MPI_Probe - Blocking test for a message. MPI_Probe(MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, & status); // MPI_Get_count - Gets the number of top level elements. MPI_Get_count(& status, MPI_T, & msg_count); if(msg_count == 1) { // MPI_Recv - Performs a standard-mode blocking receive. MPI_Recv(& message_in, msg_count, MPI_T, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, & status); } } MPIChain(T message_init, int c_rank, int c_size): message_out(message_init), size(c_size), rank(c_rank) {} int get_rank() const { return rank;} int get_size() const { return size;} }; 

Nous pouvons maintenant utiliser notre classe MPIChain pour créer notre classe qui gère le stream de sortie:

 class ChainStream : public MPIChain { // Uses the MPIChain class to implement a ostream with a serial operator<< implementation. private: std::ostream & s_out; public: ChainStream(std::ostream & os, int c_rank, int c_size) : MPIChain(0, c_rank, c_size), s_out(os) {}; ChainStream & operator<<(const std::string & os){ if(this->get_rank() == 0) { this->s_out << os; // Initiate chain of MPI messages this->next(); } else { int msg_count; // Wait untill a message arrives (MPIChain::wait uses a blocking test) this->wait(msg_count); if(msg_count == 1) { // If the message is well-formed (ie only one message is recieved): output ssortingng this->s_out << os; // Pass onto the next member of the chain (if there is one) this->next(); } } // Ensure that the chain is resolved before returning the stream MPI_Barrier(MPI_COMM_WORLD); // Don't output the ostream! That would break the serial-in-time exuction. return *this; }; }; 

Notez le MPI_Barrier à la fin de l' operator<< . Cela empêche le code de démarrer une deuxième chaîne de sortie. Même si cela pouvait être déplacé en dehors de l' operator<< , j'ai pensé que je le mettrais ici, car c'est supposé être une sortie série de toute façon ....

Mettre tous ensemble:

 int main(int argc, char * argv[]) { init_mpi(argc, argv); ChainStream cs(std::cout, mpi_rank, mpi_size); std::ssortingngstream str_1, str_2, str_3; str_1 << "FIRST: " << "MPI_SIZE = " << mpi_size << " RANK = " << mpi_rank << std::endl; str_2 << "SECOND: " << "MPI_SIZE = " << mpi_size << " RANK = " << mpi_rank << std::endl; str_3 << "THIRD: " << "MPI_SIZE = " << mpi_size << " RANK = " << mpi_rank << std::endl; cs << str_1.str() << str_2.str() << str_3.str(); // Equivalent to: //cs << str_1.str(); //cs << str_2.str(); //cs << str_3.str(); finalize_mpi(); } 

Notez que nous concaténons les chaînes str_1 , str_2 , str_3 avant de leur envoyer l'instance ChainStream . Normalement, on ferait quelque chose comme:

 std::cout << "a" << "b" << "c"" << std::endl 

mais ceci s'applique à l' operator<< de gauche à droite et nous voulons que les chaînes soient prêtes pour la sortie avant d'être exécutées séquentiellement dans chaque processus.

 g++-7 -O3 -lmpi serial_io_obj.cpp -o serial_io_obj mpirun -n 10 ./serial_io_obj 

Les sorties:

 FIRST: MPI_SIZE = 10 RANK = 0 FIRST: MPI_SIZE = 10 RANK = 1 FIRST: MPI_SIZE = 10 RANK = 2 FIRST: MPI_SIZE = 10 RANK = 3 FIRST: MPI_SIZE = 10 RANK = 4 FIRST: MPI_SIZE = 10 RANK = 5 FIRST: MPI_SIZE = 10 RANK = 6 FIRST: MPI_SIZE = 10 RANK = 7 FIRST: MPI_SIZE = 10 RANK = 8 FIRST: MPI_SIZE = 10 RANK = 9 SECOND: MPI_SIZE = 10 RANK = 0 SECOND: MPI_SIZE = 10 RANK = 1 SECOND: MPI_SIZE = 10 RANK = 2 SECOND: MPI_SIZE = 10 RANK = 3 SECOND: MPI_SIZE = 10 RANK = 4 SECOND: MPI_SIZE = 10 RANK = 5 SECOND: MPI_SIZE = 10 RANK = 6 SECOND: MPI_SIZE = 10 RANK = 7 SECOND: MPI_SIZE = 10 RANK = 8 SECOND: MPI_SIZE = 10 RANK = 9 THIRD: MPI_SIZE = 10 RANK = 0 THIRD: MPI_SIZE = 10 RANK = 1 THIRD: MPI_SIZE = 10 RANK = 2 THIRD: MPI_SIZE = 10 RANK = 3 THIRD: MPI_SIZE = 10 RANK = 4 THIRD: MPI_SIZE = 10 RANK = 5 THIRD: MPI_SIZE = 10 RANK = 6 THIRD: MPI_SIZE = 10 RANK = 7 THIRD: MPI_SIZE = 10 RANK = 8 THIRD: MPI_SIZE = 10 RANK = 9 

La norme MPI ne spécifie pas comment la sortie standard de différents nœuds doit être collectée et fflush n’aide pas.

Si vous avez besoin d’imprimer de gros volumes dans l’ordre, la meilleure solution est probablement de ne pas les rassembler tous et de les imprimer en même temps, car cela générerait du trafic sur le réseau. Une meilleure solution consiste à créer quelque chose de similaire à un anneau virtuel dans lequel chaque processus attend un jeton du processus précédent, l’imprime et l’envoie au prochain. Bien sûr, le premier processus ne doit pas attendre, il est imprimé et envoyé au suivant.

Quoi qu’il en soit, en cas de très gros volumes de sortie, où il n’ya probablement aucun sens d’imprimer des sorties sur vidéo, vous devez utiliser MPI-IO comme suggéré par Jonathan Dursi.