Scatter Masortingx Blocks de différentes tailles en utilisant MPI

(Supposons que toutes les masortingces sont stockées dans un ordre de rangée majeur.) Un exemple illustrant le problème consiste à répartir une masortingce 10×10 sur une grid 3×3, afin que la taille des sous-masortingces de chaque nœud ressemble

|-----+-----+-----| | 3x3 | 3x3 | 3x4 | |-----+-----+-----| | 3x3 | 3x3 | 3x4 | |-----+-----+-----| | 4x3 | 4x3 | 4x4 | |-----+-----+-----| 

J’ai vu de nombreux articles sur Stackoverflow (tels que l’ envoi de blocs de tableau 2D en C en utilisant MPI et la masortingce de partition MPI en blocs ). Mais ils ne traitent que des blocs de même taille (dans ce cas, nous pouvons simplement utiliser MPI_Type_vector ou MPI_Type_create_subarray et un MPI_Scatterv appel MPI_Scatterv ).

Alors, je me demande quel est le moyen le plus efficace dans MPI de disperser une masortingce dans une grid de processeurs où chaque processeur a un bloc d’une taille spécifiée.

MPI_Type_create_darray scriptum J’ai également examiné MPI_Type_create_darray , mais il semble que vous ne pouvez pas spécifier la taille de bloc pour chaque processeur.

Pour ce faire, vous devez effectuer au moins une étape supplémentaire dans MPI.

Le problème est que les plus générales des routines de rassemblement / dispersion, MPI_Scatterv et MPI_Gatherv , vous permettent de passer un “vecteur” (v) de comptages / déplacements, plutôt qu’un seul comptage pour Scatter et Gather, mais les types sont tous supposés. être le même. Ici, il n’y a pas moyen de contourner cela; les configurations de mémoire de chaque bloc sont différentes et doivent donc être traitées selon un type différent. S’il n’y avait qu’une seule différence entre les blocs – certains avaient un nombre différent de colonnes ou d’ autres un nombre différent de lignes – il suffirait alors d’utiliser des nombres différents. Mais avec des colonnes et des rangées différentes, les comptes ne le feront pas; vous devez vraiment pouvoir spécifier différents types.

Donc, ce que vous voulez vraiment est une routine MPI_Scatterw (où w signifie vv; par exemple, les comptes et les types sont des vecteurs) qui est souvent discutée mais jamais implémentée. Mais une telle chose n’existe pas. Le plus proche que vous puissiez obtenir est l’appel beaucoup plus général MPI_Alltoallw , qui permet un envoi et une réception tout à fait généraux des données; comme l’indique la spécification, “la fonction MPI_ALLTOALLW généralise plusieurs fonctions MPI en sélectionnant soigneusement les arguments d’entrée. Par exemple, si tous les processus sauf un ont sendcounts (i) = 0, la fonction MPI_SCATTERW est ainsi créée”. .

Vous pouvez donc faire cela avec MPI_Alltoallw en faisant en sorte que tous les processus autres que celui contenant toutes les données à l’origine (nous supposerons qu’il s’agit du rang 0 ici) ont envoyé tous leurs comptes d’envoi à zéro. Toutes les tâches auront également un compte de réception égal à zéro, à l’exception de la première: la quantité de données obtenue à partir du rang zéro.

Pour le nombre d’envois du processus 0, nous devons d’abord définir quatre types de types différents (les 4 tailles différentes de sous-tableaux), puis le nombre d’envois sera égal à 1 et la seule partie restante consiste à déterminer les déplacements d’envoi ( qui, contrairement à scatterv, est ici en unités d’octets, car il n’y a pas un seul type utilisable en tant qu’unité):

  /* 4 types of blocks - * blocksize*blocksize, blocksize+1*blocksize, blocksize*blocksize+1, blocksize+1*blocksize+1 */ MPI_Datatype blocktypes[4]; int subsizes[2]; int starts[2] = {0,0}; for (int i=0; i<2; i++) { subsizes[0] = blocksize+i; for (int j=0; j<2; j++) { subsizes[1] = blocksize+j; MPI_Type_create_subarray(2, globalsizes, subsizes, starts, MPI_ORDER_C, MPI_CHAR, &blocktypes[2*i+j]); MPI_Type_commit(&blocktypes[2*i+j]); } } /* now figure out the displacement and type of each processor's data */ for (int proc=0; proc 

Et ça va marcher.

Mais le problème est que la fonction Alltoallw est tellement générale qu’il est difficile pour les implémentations de faire beaucoup dans le domaine de l’optimisation. donc je serais surpris si cela fonctionnait aussi bien qu'une dispersion de blocs de taille égale.

Donc, une autre approche consiste à faire quelque chose comme deux phases de communication.

La plus simple approche consiste à noter que vous pouvez presque obtenir toutes les données là où elles doivent aller avec un seul appel MPI_Scatterv() : dans votre exemple, si nous opérons en unités d’un vecteur colonne unique avec colonne = 1 et lignes = 3 (le nombre de lignes dans la plupart des blocs du domaine), vous pouvez disperser la quasi-totalité des données globales vers les autres processeurs. Les processeurs obtiennent chacun 3 ou 4 de ces vecteurs, qui dissortingbuent toutes les données à l'exception de la toute dernière ligne du tableau global, qui peut être manipulé par un simple second scatterv. Cela ressemble à ceci;

 /* We're going to be operating mostly in units of a single column of a "normal" sized block. * There will need to be two vectors describing these columns; one in the context of the * global array, and one in the local results. */ MPI_Datatype vec, localvec; MPI_Type_vector(blocksize, 1, localsizes[1], MPI_CHAR, &localvec); MPI_Type_create_resized(localvec, 0, sizeof(char), &localvec); MPI_Type_commit(&localvec); MPI_Type_vector(blocksize, 1, globalsizes[1], MPI_CHAR, &vec); MPI_Type_create_resized(vec, 0, sizeof(char), &vec); MPI_Type_commit(&vec); /* The originating process needs to allocate and fill the source array, * and then define types defining the array chunks to send, and * fill out senddispls, sendcounts (1) and sendtypes. */ if (rank == 0) { /* create the vector type which will send one column of a "normal" sized-block */ /* then all processors except those in the last row need to get blocksize*vec or (blocksize+1)*vec */ /* will still have to do something to tidy up the last row of values */ /* we need to make the type have extent of 1 char for scattering */ for (int proc=0; proc 

Jusqu'ici tout va bien. Mais il est dommage que la plupart des processeurs ne soient rien à faire pendant cette dernière "opération de nettoyage" scatterv.

Une approche plus agréable consiste donc à disperser toutes les lignes dans une première phase et à disperser ces données parmi les colonnes dans une seconde phase. Nous créons ici de nouveaux communicateurs, chaque processeur appartenant à deux nouveaux communicateurs - l'un représentant les autres processeurs dans la même ligne de bloc et l'autre dans la même colonne de bloc. Lors de la première étape, le processeur d'origine dissortingbue toutes les lignes du tableau global aux autres processeurs du même communicateur de colonne - ce qui peut être fait en un seul scatterv. Ensuite, ces processeurs, utilisant un seul type de données scatterv et les mêmes colonnes que dans l'exemple précédent, dispersent les colonnes sur chaque processeur dans la même ligne de bloc que celle-ci. Le résultat est deux assez simples scatterv dissortingbuant toutes les données:

 /* create communicators which have processors with the same row or column in them*/ MPI_Comm colComm, rowComm; MPI_Comm_split(MPI_COMM_WORLD, myrow, rank, &rowComm); MPI_Comm_split(MPI_COMM_WORLD, mycol, rank, &colComm); /* first, scatter the array by rows, with the processor in column 0 corresponding to each row * receiving the data */ if (mycol == 0) { int sendcounts[ blocks[0] ]; int senddispls[ blocks[0] ]; senddispls[0] = 0; for (int row=0; row 0) senddispls[row] = senddispls[row-1] + sendcounts[row-1]; } /* the last processor gets one more */ sendcounts[blocks[0]-1] += globalsizes[1]; /* allocate my rowdata */ rowdata = allocchar2darray( sendcounts[myrow], globalsizes[1] ); /* perform the scatter of rows */ MPI_Scatterv(globalptr, sendcounts, senddispls, MPI_CHAR, &(rowdata[0][0]), sendcounts[myrow], MPI_CHAR, 0, colComm); } /* Now, within each row of processors, we can scatter the columns. * We can do this as we did in the previous example; create a vector * (and localvector) type and scatter accordingly */ int locnrows = blocksize; if ( isLastRow(myrow, blocks) ) locnrows++; MPI_Datatype vec, localvec; MPI_Type_vector(locnrows, 1, globalsizes[1], MPI_CHAR, &vec); MPI_Type_create_resized(vec, 0, sizeof(char), &vec); MPI_Type_commit(&vec); MPI_Type_vector(locnrows, 1, localsizes[1], MPI_CHAR, &localvec); MPI_Type_create_resized(localvec, 0, sizeof(char), &localvec); MPI_Type_commit(&localvec); int sendcounts[ blocks[1] ]; int senddispls[ blocks[1] ]; if (mycol == 0) { for (int col=0; col 

ce qui est plus simple et devrait constituer un assez bon équilibre entre performance et robustesse.

L'exécution de ces trois méthodes fonctionne:

 bash-3.2$ mpirun -np 6 ./allmethods alltoall Global array: abcdefg hijklmn opqrstu vwxyzab cdefghi jklmnop qrstuvw xyzabcd efghijk lmnopqr Method - alltoall Rank 0: abc hij opq Rank 1: defg klmn rstu Rank 2: vwx cde jkl Rank 3: yzab fghi mnop Rank 4: qrs xyz efg lmn Rank 5: tuvw abcd hijk opqr bash-3.2$ mpirun -np 6 ./allmethods twophasevecs Global array: abcdefg hijklmn opqrstu vwxyzab cdefghi jklmnop qrstuvw xyzabcd efghijk lmnopqr Method - two phase, vectors, then cleanup Rank 0: abc hij opq Rank 1: defg klmn rstu Rank 2: vwx cde jkl Rank 3: yzab fghi mnop Rank 4: qrs xyz efg lmn Rank 5: tuvw abcd hijk opqr bash-3.2$ mpirun -np 6 ./allmethods twophaserowcol Global array: abcdefg hijklmn opqrstu vwxyzab cdefghi jklmnop qrstuvw xyzabcd efghijk lmnopqr Method - two phase - row, cols Rank 0: abc hij opq Rank 1: defg klmn rstu Rank 2: vwx cde jkl Rank 3: yzab fghi mnop Rank 4: qrs xyz efg lmn Rank 5: tuvw abcd hijk opqr 

Le code implémentant ces méthodes suit; vous pouvez définir des tailles de bloc plus grandes pour votre problème et exécuter sur un nombre réaliste de processeurs afin de déterminer le meilleur choix pour votre application.

 #include  #include  #include  #include "mpi.h" /* auxiliary routines, found at end of program */ char **allocchar2darray(int n, int m); void freechar2darray(char **a); void printarray(char **data, int n, int m); void rowcol(int rank, const int blocks[2], int *row, int *col); int isLastRow(int row, const int blocks[2]); int isLastCol(int col, const int blocks[2]); int typeIdx(int row, int col, const int blocks[2]); /* first method - alltoallw */ void alltoall(const int myrow, const int mycol, const int rank, const int size, const int blocks[2], const int blocksize, const int globalsizes[2], const int localsizes[2], const char *const globalptr, char **localdata) { /* * get send and recieve counts ready for alltoallw call. * everyone will be recieving just one block from proc 0; * most procs will be sending nothing to anyone. */ int sendcounts[ size ]; int senddispls[ size ]; MPI_Datatype sendtypes[size]; int recvcounts[ size ]; int recvdispls[ size ]; MPI_Datatype recvtypes[size]; for (int proc=0; proc 0) senddispls[row] = senddispls[row-1] + sendcounts[row-1]; } /* the last processor gets one more */ sendcounts[blocks[0]-1] += globalsizes[1]; /* allocate my rowdata */ rowdata = allocchar2darray( sendcounts[myrow], globalsizes[1] ); /* perform the scatter of rows */ MPI_Scatterv(globalptr, sendcounts, senddispls, MPI_CHAR, &(rowdata[0][0]), sendcounts[myrow], MPI_CHAR, 0, colComm); } /* Now, within each row of processors, we can scatter the columns. * We can do this as we did in the previous example; create a vector * (and localvector) type and scatter accordingly */ int locnrows = blocksize; if ( isLastRow(myrow, blocks) ) locnrows++; MPI_Datatype vec, localvec; MPI_Type_vector(locnrows, 1, globalsizes[1], MPI_CHAR, &vec); MPI_Type_create_resized(vec, 0, sizeof(char), &vec); MPI_Type_commit(&vec); MPI_Type_vector(locnrows, 1, localsizes[1], MPI_CHAR, &localvec); MPI_Type_create_resized(localvec, 0, sizeof(char), &localvec); MPI_Type_commit(&localvec); int sendcounts[ blocks[1] ]; int senddispls[ blocks[1] ]; if (mycol == 0) { for (int col=0; col 

Je ne sais pas si cela s’applique à vous, mais cela m’a aidé dans le passé et pourrait donc être utile aux autres.

Ma réponse s’applique dans le contexte de parallele IO. Le fait est que, si vous savez que vos access ne se chevauchent pas, vous pouvez écrire / lire avec succès même avec des tailles variables en utilisant MPI_COMM_SELF

Un morceau de code que j’utilise tous les jours contient:

 MPI_File fh; MPI_File_open(MPI_COMM_SELF, path.c_str(), MPI_MODE_CREATE|MPI_MODE_WRONLY, MPI_INFO_NULL, &fh); // Lot of computation to get the size right MPI_Datatype filetype; MPI_Type_create_subarray(gsizes.size(), &gsizes[0], &lsizes[0], &offset[0], MPI_ORDER_C, MPI_FLOAT, &filetype); MPI_Type_commit(&filetype); MPI_File_set_view(fh, 0, MPI_FLOAT, filetype, "native", MPI_INFO_NULL); MPI_File_write(fh, &block->field[0], block->field.size(), MPI_FLOAT, MPI_STATUS_IGNORE); MPI_File_close(&fh);