Ecriture de tableaux dissortingbués à l’aide de MPI-IO et d’une topologie cartésienne

J’ai un code MPI qui implémente la décomposition de domaine 2D pour calculer des solutions numériques à une PDE. Actuellement, j’écris certains tableaux dissortingbués 2D pour chaque processus (par exemple, array_x -> proc000x.bin). Je veux réduire cela à un seul fichier binary.

array_0, array_1,

tableau_2, tableau_3,

Supposons que ce qui précède illustre une topologie cartésienne avec 4 processus (2×2). Chaque tableau 2D a une dimension (nx + 2, nz + 2). +2 signifie que des couches “fantômes” ont été ajoutées à tous les côtés à des fins de communication.

Je voudrais extraire les tableaux principaux (omettre les couches fantômes) et les écrire dans un seul fichier binary avec un ordre comme,

tableau_0, tableau_1, tableau_2, tableau_3 -> sortie.bin

Si possible, il serait préférable de l’écrire comme si j’avais access à la grid globale et écrivais ligne par ligne, c’est-à-dire

ligne 0 de tableau_0, ligne 0 de tableau_1, ligne 1 de tableau_0 rangée_1 de tableau_1 ….

La tentative ci-dessous tente de remplacer l’ancien des deux formats de sortie du fichier array_test.c

#include  #include  #include  /* 2D array allocation */ float **alloc2D(int rows, int cols); float **alloc2D(int rows, int cols) { int i, j; float *data = malloc(rows * cols * sizeof(float)); float **arr2D = malloc(rows * sizeof(float *)); for (i = 0; i < rows; i++) { arr2D[i] = &(data[i * cols]); } /* Initialize to zero */ for (i= 0; i < rows; i++) { for (j=0; j < cols; j++) { arr2D[i][j] = 0.0; } } return arr2D; } int main(void) { /* Creates 5x5 array of floats with padding layers and * attempts to write distributed arrays */ /* Run toy example with 4 processes */ int i, j, row, col; int nx = 5, ny = 5, npad = 1; int my_rank, nproc=4; int dim[2] = {2, 2}; /* 2x2 cartesian grid */ int period[2] = {0, 0}; int coord[2]; int reorder = 1; float **A = NULL; MPI_Comm grid_Comm; /* Initialize MPI */ MPI_Init(NULL, NULL); MPI_Comm_size(MPI_COMM_WORLD, &nproc); MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /* Establish cartesian topology */ MPI_Cart_create(MPI_COMM_WORLD, 2, dim, period, reorder, &grid_Comm); /* Get cartesian grid indicies of processes */ MPI_Cart_coords(grid_Comm, my_rank, 2, coord); row = coord[1]; col = coord[0]; /* Add ghost layers */ nx += 2 * npad; ny += 2 * npad; A = alloc2D(nx, ny); /* Create derived datatype for interior grid (output grid) */ MPI_Datatype grid; int start[2] = {npad, npad}; int arrsize[2] = {nx, ny}; int gridsize[2] = {nx - 2 * npad, ny - 2 * npad}; MPI_Type_create_subarray(2, arrsize, gridsize, start, MPI_ORDER_C, MPI_FLOAT, &grid); MPI_Type_commit(&grid); /* Fill interior grid */ for (i = npad; i < nx-npad; i++) { for (j = npad; j < ny-npad; j++) { A[i][j] = my_rank + i; } } /* MPI IO */ MPI_File fh; MPI_Status status; char file_name[100]; int N, offset; sprintf(file_name, "output.bin"); MPI_File_open(grid_Comm, file_name, MPI_MODE_CREATE | MPI_MODE_WRONLY, MPI_INFO_NULL, &fh); N = (nx - 2 * npad) * (ny - 2 *npad); offset = (row * 2 + col) * N * sizeof(float); MPI_File_set_view(fh, offset, MPI_FLOAT, grid, "native", MPI_INFO_NULL); MPI_File_write_all(fh, &A[0][0], N, MPI_FLOAT, MPI_STATUS_IGNORE); MPI_File_close(&fh); /* Cleanup */ free(A[0]); free(A); MPI_Type_free(&grid); MPI_Finalize(); return 0; } 

Comstack avec

 mpicc -o array_test array_test.c 

Fonctionne avec

 mpiexec -n 4 array_test 

Pendant que le code est compilé et exécuté, la sortie est incorrecte. Je suppose que j’ai mal interprété l’utilisation du type de données dérivé et de l’écriture de fichier dans ce cas. J’apprécierais un peu d’aide pour comprendre mes erreurs.

L’erreur que vous faites ici est que vous avez la mauvaise vue de fichier. Au lieu de créer un type représentant le partage du fichier dont le processeur actuel est responsable, vous utilisez le masque correspondant aux données locales que vous souhaitez écrire.

Vous avez en réalité deux masques très distincts à considérer:

  1. Le masque pour les données locales, à l’exclusion des couches de halo; et
  2. Le masque pour les données globales, comme il se doit, une fois assemblé dans le fichier.

Le premier correspond à cette mise en page:
limite de données et de halo
Ici, les données que vous souhaitez exporter dans le fichier pour un processus donné sont en bleu foncé et la couche de halo qui ne doit pas être écrite dans le fichier est en bleu plus clair.

Le dernier correspond à cette mise en page:
mise en page du fichier
Ici, chaque couleur correspond aux données locales provenant d’un processus différent, tel que réparti sur la grid cartésienne 2D.

Pour comprendre ce que vous devez créer pour atteindre ce résultat final, vous devez réfléchir en arrière:

  1. Votre dernier appel à la routine IO doit être MPI_File_write_all(fh, &A[0][0], 1, interior, MPI_STATUS_IGNORE); . Vous devez donc définir votre type d’ interior manière à exclure la limite du halo. Heureusement, la grid types que vous avez déjà créée fait exactement cela. Nous allons donc l’utiliser.
  2. Mais maintenant, vous devez avoir la vue sur le fichier pour permettre cet appel MPI_Fie_write_all() . Donc, la vue doit être comme décrit dans la deuxième image. Nous allons donc créer un nouveau type de MPI le représentant. Pour cela, MPI_Type_create_subarray() est ce dont nous avons besoin.

Voici le synopsis de cette fonction:

 int MPI_Type_create_subarray(int ndims, const int array_of_sizes[], const int array_of_subsizes[], const int array_of_starts[], int order, MPI_Datatype oldtype, MPI_Datatype *newtype) Create a datatype for a subarray of a regular, multidimensional array INPUT PARAMETERS ndims - number of array dimensions (positive integer) array_of_sizes - number of elements of type oldtype in each dimension of the full array (array of positive integers) array_of_subsizes - number of elements of type oldtype in each dimension of the subarray (array of positive integers) array_of_starts - starting coordinates of the subarray in each dimension (array of nonnegative integers) order - array storage order flag (state) oldtype - array element datatype (handle) OUTPUT PARAMETERS newtype - new datatype (handle) 

Pour notre vue de fichier cartésien 2D, voici ce dont nous avons besoin pour ces parameters d’entrée:

  • ndims : 2 comme la grid est 2D
  • array_of_sizes : ce sont les dimensions du tableau global à afficher, à savoir { nnx*dim[0], nny*dim[1] }
  • array_of_subsizes : ce sont les dimensions du partage local des données à générer, à savoir { nnx, nny }
  • array_of_start : il s’agit des coordonnées de départ x, y du partage local dans la grid globale, à savoir { nnx*coord[0], nny*coord[1] }
  • order : la commande est C donc il faut que ce soit MPI_ORDER_C
  • oldtype : les données sont float donc ce doit être MPI_FLOAT

Maintenant que nous avons notre type pour la vue fichier, nous l’appliquons simplement avec MPI_File_set_view(fh, 0, MPI_FLOAT, view, "native", MPI_INFO_NULL); et la magie est faite.

Votre code complet devient:

 #include  #include  #include  /* 2D array allocation */ float **alloc2D(int rows, int cols); float **alloc2D(int rows, int cols) { int i, j; float *data = malloc(rows * cols * sizeof(float)); float **arr2D = malloc(rows * sizeof(float *)); for (i = 0; i < rows; i++) { arr2D[i] = &(data[i * cols]); } /* Initialize to zero */ for (i= 0; i < rows; i++) { for (j=0; j < cols; j++) { arr2D[i][j] = 0.0; } } return arr2D; } int main(void) { /* Creates 5x5 array of floats with padding layers and * attempts to write distributed arrays */ /* Run toy example with 4 processes */ int i, j, row, col; int nx = 5, ny = 5, npad = 1; int my_rank, nproc=4; int dim[2] = {2, 2}; /* 2x2 cartesian grid */ int period[2] = {0, 0}; int coord[2]; int reorder = 1; float **A = NULL; MPI_Comm grid_Comm; /* Initialize MPI */ MPI_Init(NULL, NULL); MPI_Comm_size(MPI_COMM_WORLD, &nproc); MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /* Establish cartesian topology */ MPI_Cart_create(MPI_COMM_WORLD, 2, dim, period, reorder, &grid_Comm); /* Get cartesian grid indicies of processes */ MPI_Cart_coords(grid_Comm, my_rank, 2, coord); row = coord[1]; col = coord[0]; /* Add ghost layers */ nx += 2 * npad; ny += 2 * npad; A = alloc2D(nx, ny); /* Create derived datatype for interior grid (output grid) */ MPI_Datatype grid; int start[2] = {npad, npad}; int arrsize[2] = {nx, ny}; int gridsize[2] = {nx - 2 * npad, ny - 2 * npad}; MPI_Type_create_subarray(2, arrsize, gridsize, start, MPI_ORDER_C, MPI_FLOAT, &grid); MPI_Type_commit(&grid); /* Fill interior grid */ for (i = npad; i < nx-npad; i++) { for (j = npad; j < ny-npad; j++) { A[i][j] = my_rank + i; } } /* Create derived type for file view */ MPI_Datatype view; int nnx = nx-2*npad, nny = ny-2*npad; int startV[2] = { coord[0]*nnx, coord[1]*nny }; int arrsizeV[2] = { dim[0]*nnx, dim[1]*nny }; int gridsizeV[2] = { nnx, nny }; MPI_Type_create_subarray(2, arrsizeV, gridsizeV, startV, MPI_ORDER_C, MPI_FLOAT, &view); MPI_Type_commit(&view); /* MPI IO */ MPI_File fh; MPI_File_open(grid_Comm, "output.bin", MPI_MODE_CREATE | MPI_MODE_WRONLY, MPI_INFO_NULL, &fh); MPI_File_set_view(fh, 0, MPI_FLOAT, view, "native", MPI_INFO_NULL); MPI_File_write_all(fh, &A[0][0], 1, grid, MPI_STATUS_IGNORE); MPI_File_close(&fh); /* Cleanup */ free(A[0]); free(A); MPI_Type_free(&view); MPI_Type_free(&grid); MPI_Finalize(); return 0; }