Envoi de colonnes d’une masortingce à l’aide de MPI_Scatter

J’essaie d’écrire un programme de multiplication masortingce-vecteur à l’aide de MPI. J’essaie d’envoyer des colonnes de la masortingce pour séparer les processus et calculer le résultat localement. En fin de compte, je fais une MPI_Reduce utilisant l’opération MPI_SUM .

L’envoi de lignes d’une masortingce est facile, car C stocke les tableaux dans un ordre de lignes majeur, mais les colonnes ne le sont pas (si vous ne les envoyez pas une par une). J’ai lu la question ici:

MPI_Scatter – Envoi de colonnes d’un tableau 2D

Jonathan Dursi a suggéré d’utiliser de nouveaux types de données MPI et voici ce que j’ai fait en adaptant son code à mes propres besoins:

  double masortingx[10][10]; double mytype[10][10]; int part_size; // stores how many cols a process needs to work on MPI_Datatype col, coltype; // ... MPI_Type_vector(N, 1, N, MPI_DOUBLE, &col); MPI_Type_commit(&col); MPI_Type_create_resized(col, 0, 1*sizeof(double), &coltype); MPI_Type_commit(&coltype); // ... MPI_Scatter(masortingx, part_size, coltype, mypart, part_size, coltype, 0, MPI_COMM_WORLD); // calculations... MPI_Reduce(local_result, global_result, N, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); 

Cela fonctionne parfaitement, mais je ne peux pas dire que je comprends vraiment comment cela fonctionne.

  1. Comment MPI_Type_vector est- MPI_Type_vector stocké dans la mémoire?
  2. Comment MPI_Type_create_resized() fonctionne-t-il et que fait-il exactement?

S’il vous plaît gardez à l’esprit que je suis un débutant total dans MPI. Merci d’avance.

Ma réponse à cette question décrit longuement cette question : le fait que beaucoup de gens ont ces questions est la preuve que ce n’est pas évident et que les idées demandent un certain temps pour s’y habituer.

La chose importante à savoir est la configuration de la mémoire décrite dans le type de données MPI. La séquence d’appel de MPI_Type_vector est la suivante:

 int MPI_Type_vector(int count, int blocklength, int ssortingde, MPI_Datatype old_type, MPI_Datatype *newtype_p) 

Il crée un nouveau type qui décrit une disposition de la mémoire dans laquelle chaque élément de ssortingde , un bloc d’éléments de blocklength de bloc est blocklength et le nombre total de ces blocs. Les éléments ici sont en unités, quel que soit l’ old_type . Donc, par exemple, si vous avez appelé (nommez les parameters ici, ce que vous ne pouvez pas réellement faire en C, mais 🙂

  MPI_Type_vector(count=3, blocklength=2, ssortingde=5, old_type=MPI_INT, &newtype); 

Ensuite, newtype décrirait une disposition en mémoire comme ceci:

  |<----->| block length +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | X | X | | | | X | X | | | | X | X | | | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |<---- stride ----->| count = 3 

où chaque carré est un bloc de mémoire de taille entière, probablement 4 octets. Notez que la foulée est la distance en nombres entiers du début d’un bloc au début du suivant, pas la distance entre les blocs.

Ok, alors dans ton cas tu as appelé

  MPI_Type_vector(N, 1, N, MPI_DOUBLE, &col); 

qui prendra count = N blocs, chacun de taille blocklength=1 MPI_DOUBLE s, avec un espace entre le début de chaque bloc de ssortingde=N MPI_DOUBLE s. En d’autres termes, il faudra chaque nième doublé, un total de N fois; parfait pour extraire une colonne d’un tableau de doubles NxN (stocké de manière contiguë). Une vérification pratique consiste à voir combien de données sont défilées ( count*ssortingde = N*N qui est la taille complète de la masortingce, check) et combien de données sont réellement incluses ( count*blocksize = N , qui est la taille d’une colonne, cochez.)

Si tout ce que vous deviez faire était d’appeler MPI_Send et MPI_Recv pour échanger des colonnes individuelles, vous auriez terminé; vous pourriez utiliser ce type pour décrire la disposition de la colonne et tout irait bien. Mais il y a encore une chose.

Vous voulez appeler MPI_Scatter , qui envoie le premier type (par exemple) au processeur 0, le second type au processeur 1, etc. Si vous utilisez un simple tableau 1d, il est facile de savoir où se trouvent les “prochaines” données. le type est; si vous diffusez 1 int sur chaque processeur, le “prochain” int commence immédiatement après la fin du premier int.

Mais votre nouvelle colonne type a une étendue totale qui va du début de la colonne à N*N MPI_DOUBLE s plus tard – si MPI_Scatter suit la même logique (c’est le cas), il commencerait à rechercher la colonne “suivante” en dehors du mémoire masortingces entièrement, et ainsi de suite avec le suivant et suivant. Non seulement vous n’obtiendrez pas la réponse que vous souhaitiez, mais le programme échouerait probablement.

La solution consiste à indiquer à MPI que la “taille” de ce type de données aux fins de calculer où se situe le “suivant” est la taille en mémoire entre le début d’une colonne et le début de la colonne suivante; c’est-à-dire exactement un MPI_DOUBLE . Cela n’affecte pas la quantité de données envoyées, ce qui représente 1 colonne de données; cela n’affecte que le calcul “suivant en ligne”. Avec des colonnes (ou des lignes) dans un tableau, vous pouvez simplement envoyer cette taille à la taille de pas appropriée en mémoire et MPI choisira la colonne suivante à envoyer. Sans cet opérateur de redimensionnement, votre programme planterait probablement.

Lorsque vous avez des structures de données plus complexes, comme dans les blocs 2d d’un exemple de tableau 2d lié à ci-dessus, il n’y a pas une seule taille de pas entre les éléments “suivants”; vous devez toujours faire l’astuce de redimensionnement pour que la taille soit une unité utile, mais vous devez ensuite utiliser MPI_Scatterv plutôt que scatter pour spécifier explicitement les emplacements à partir desquels envoyer.