web-dev-qa-db-fra.com

envoi de blocs de tableau 2D en C en utilisant MPI

Comment envoyez-vous des blocs de tableau 2D à différents processeurs? Supposons que la taille du tableau 2D soit 400x400 et je souhaite envoyer des blocs de tailles 100X100 à différents processeurs. L'idée est que chaque processeur effectuera le calcul sur son bloc séparé et renverra son résultat au premier processeur pour le résultat final.
J'utilise MPI dans les programmes C.

47
Sundevil

Permettez-moi de commencer en disant que vous ne voulez généralement pas vraiment faire cela - dispersez et collectez d'énormes blocs de données à partir d'un processus "maître". Normalement, vous voulez que chaque tâche se dérobe à sa propre pièce du puzzle, et vous devriez viser à ce qu'aucun processeur n'ait besoin d'une "vue globale" de l'ensemble des données; dès que vous en avez besoin, vous limitez l'évolutivité et la taille du problème. Si vous faites cela pour les E/S - un processus lit les données, puis les diffuse, puis les recueille pour l'écriture, vous voudrez peut-être éventuellement examiner MPI-IO.

Pour arriver à votre question, cependant, MPI a de très belles façons d'extraire des données arbitraires de la mémoire, et de les disperser/rassembler vers et depuis un ensemble de processeurs. Malheureusement, cela nécessite un bon nombre de = MPI concepts - MPI Types, étendues et opérations collectives. De nombreuses idées de base sont discutées dans la réponse à cette question - MPI_Type_create_subarray et MPI_Gather .

Mise à jour - Dans la lumière froide du jour, c'est beaucoup de code et pas beaucoup d'explication. Permettez-moi donc de développer un peu.

Considérez un tableau global entier 1d que la tâche 0 a que vous souhaitez distribuer à un certain nombre de tâches MPI, afin qu'elles obtiennent chacune un morceau dans leur tableau local. Supposons que vous ayez 4 tâches, et le tableau global est [01234567]. Vous pourriez avoir la tâche 0 envoyer quatre messages (dont un à lui-même) pour le distribuer, et quand il est temps de le réassembler, recevoir quatre messages pour le regrouper; mais cela évidemment prend beaucoup de temps sur un grand nombre de processus. Il existe des routines optimisées pour ces types d'opérations - les opérations de dispersion/collecte. Donc, dans ce cas 1d, vous feriez quelque chose comme ceci:

int global[8];   /* only task 0 has this */
int local[2];    /* everyone has this */
const int root = 0;   /* the processor with the initial global data */

if (rank == root) {
   for (int i=0; i<7; i++) global[i] = i;
}

MPI_Scatter(global, 2, MPI_INT,      /* send everyone 2 ints from global */
            local,  2, MPI_INT,      /* each proc receives 2 ints into local */
            root, MPI_COMM_WORLD);   /* sending process is root, all procs in */
                                     /* MPI_COMM_WORLD participate */

Après cela, les données des processeurs ressembleraient à

task 0:  local:[01]  global: [01234567]
task 1:  local:[23]  global: [garbage-]
task 2:  local:[45]  global: [garbage-]
task 3:  local:[67]  global: [garbage-]

C'est-à-dire que l'opération de dispersion prend le tableau global et envoie des blocs 2-int contigus à tous les processeurs.

Pour réassembler le tableau, nous utilisons l'opération MPI_Gather(), qui fonctionne exactement de la même façon mais en sens inverse:

for (int i=0; i<2; i++) 
   local[i] = local[i] + rank;

MPI_Gather(local,  2, MPI_INT,      /* everyone sends 2 ints from local */
           global, 2, MPI_INT,      /* root receives 2 ints each proc into global */
           root, MPI_COMM_WORLD);   /* recv'ing process is root, all procs in */
                                    /* MPI_COMM_WORLD participate */

et maintenant les données ressemblent

task 0:  local:[01]  global: [0134679a]
task 1:  local:[34]  global: [garbage-]
task 2:  local:[67]  global: [garbage-]
task 3:  local:[9a]  global: [garbage-]

Gather ramène toutes les données, et voici un 10 parce que je n'ai pas réfléchi suffisamment à ma mise en forme lors du démarrage de cet exemple.

Que se passe-t-il si le nombre de points de données ne divise pas également le nombre de processus et que nous devons envoyer différents nombres d'éléments à chaque processus? Ensuite, vous avez besoin d'une version généralisée de scatter, MPI_Scatterv(), qui vous permet de spécifier les nombres pour chaque processeur et les déplacements - où, dans le tableau global, ce morceau de données commence. Supposons donc que vous disposiez d'un tableau de caractères [abcdefghi] Avec 9 caractères, et que vous alliez attribuer à chaque processus deux caractères à l'exception du dernier, qui en a obtenu trois. Ensuite, vous auriez besoin

char global[9];   /* only task 0 has this */
char local[3]={'-','-','-'};    /* everyone has this */
int  mynum;                     /* how many items */
const int root = 0;   /* the processor with the initial global data */

if (rank == 0) {
   for (int i=0; i<8; i++) global[i] = 'a'+i;
}

int counts[4] = {2,2,2,3};   /* how many pieces of data everyone has */
mynum = counts[rank];
int displs[4] = {0,2,4,6};   /* the starting point of everyone's data */
                             /* in the global array */

MPI_Scatterv(global, counts, displs, /* proc i gets counts[i] pts from displs[i] */
            MPI_INT,      
            local, mynum, MPI_INT;   /* I'm receiving mynum MPI_INTs into local */
            root, MPI_COMM_WORLD);

Maintenant, les données ressemblent

task 0:  local:[ab-]  global: [abcdefghi]
task 1:  local:[cd-]  global: [garbage--]
task 2:  local:[ef-]  global: [garbage--]
task 3:  local:[ghi]  global: [garbage--]

Vous avez maintenant utilisé scatterv pour distribuer les quantités irrégulières de données. Le déplacement dans chaque cas est de deux * rang (mesuré en caractères; le déplacement est en unité des types envoyés pour un scatter ou reçu pour un regroupement; il n'est généralement pas en octets ou quelque chose) depuis le début du tableau, et le les nombres sont {2,2,2,3}. Si c’était le premier processeur que nous voulions avoir 3 caractères, nous aurions mis count = {3,2,2,2} et les déplacements auraient été {0,3,5,7}. Gatherv fonctionne à nouveau exactement de la même manière mais à l'envers; les tableaux de comptages et de déplacements resteraient les mêmes.

Maintenant, pour 2D, c'est un peu plus délicat. Si nous voulons envoyer des sous-verrous 2D d'un tableau 2D, les données que nous envoyons maintenant ne sont plus contiguës. Si nous envoyons (disons) des sous-blocs 3x3 d'un tableau 6x6 à 4 processeurs, les données que nous envoyons ont des trous:

2D Array

   ---------
   |000|111|
   |000|111|
   |000|111|
   |---+---|
   |222|333|
   |222|333|
   |222|333|
   ---------

Actual layout in memory

   [000111000111000111222333222333222333]

(Notez que tout calcul haute performance se résume à comprendre la disposition des données en mémoire.)

Si nous voulons envoyer les données marquées "1" à la tâche 1, nous devons ignorer trois valeurs, envoyer trois valeurs, ignorer trois valeurs, envoyer trois valeurs, ignorer trois valeurs, envoyer trois valeurs. Une deuxième complication est celle où les sous-régions s'arrêtent et commencent; notez que la région "1" ne démarre pas là où la région "0" s'arrête; après le dernier élément de la région "0", l'emplacement suivant en mémoire est à mi-chemin dans la région "1".

Abordons d'abord le premier problème de mise en page - comment extraire uniquement les données que nous voulons envoyer. Nous pourrions toujours simplement copier toutes les données de la région "0" dans un autre tableau contigu et l'envoyer; si nous le planifions assez soigneusement, nous pourrions même le faire de telle manière que nous pourrions appeler MPI_Scatter sur les résultats. Mais nous préférons ne pas avoir à transposer l'ensemble de notre structure de données principale de cette façon.

Jusqu'à présent, tous les types de données MPI que nous avons utilisés sont simples - MPI_INT spécifie (disons) 4 octets d'affilée. Cependant, MPI vous permet créez vos propres types de données qui décrivent des dispositions de données arbitrairement complexes en mémoire. Et ce cas - les sous-régions rectangulaires d'un tableau - est suffisamment commun pour qu'il y ait un appel spécifique à cela. Pour le cas à 2 dimensions que nous décrivons ci-dessus,

    MPI_Datatype newtype;
    int sizes[2]    = {6,6};  /* size of global array */
    int subsizes[2] = {3,3};  /* size of sub-region */
    int starts[2]   = {0,0};  /* let's say we're looking at region "0",
                                 which begins at index [0,0] */

    MPI_Type_create_subarray(2, sizes, subsizes, starts, MPI_ORDER_C, MPI_INT, &newtype);
    MPI_Type_commit(&newtype);

Cela crée un type qui sélectionne uniquement la région "0" du tableau global; nous pourrions envoyer juste ce morceau de données maintenant à un autre processeur

    MPI_Send(&(global[0][0]), 1, newtype, dest, tag, MPI_COMM_WORLD);  /* region "0" */

et le processus de réception pourrait le recevoir dans un tableau local. Notez que le processus de réception, s'il ne le reçoit que dans un tableau 3x3, peut pas décrire ce qu'il reçoit comme un type de newtype; qui ne décrit plus la disposition de la mémoire. Au lieu de cela, il ne fait que recevoir un bloc de 3 * 3 = 9 entiers:

    MPI_Recv(&(local[0][0]), 3*3, MPI_INT, 0, tag, MPI_COMM_WORLD);

Notez que nous pourrions également le faire pour d'autres sous-régions, soit en créant un type différent (avec un tableau start) différent pour les autres blocs, soit simplement en envoyant au point de départ du bloc particulier:

    MPI_Send(&(global[0][3]), 1, newtype, dest, tag, MPI_COMM_WORLD);  /* region "1" */
    MPI_Send(&(global[3][0]), 1, newtype, dest, tag, MPI_COMM_WORLD);  /* region "2" */
    MPI_Send(&(global[3][3]), 1, newtype, dest, tag, MPI_COMM_WORLD);  /* region "3" */

Enfin, notez que nous avons besoin que global et local soient des morceaux de mémoire contigus ici; c'est-à-dire, &(global[0][0]) et &(local[0][0]) (ou, de manière équivalente, *global et *local pointent vers des blocs de mémoire contigus de 6 * 6 et 3 * 3; cela n'est pas garanti par la façon habituelle d'allouer des tableaux multidimensionnels dynamiques. Vous trouverez ci-dessous comment procéder.

Maintenant que nous comprenons comment spécifier des sous-régions, il n'y a plus qu'une chose à discuter avant d'utiliser les opérations de dispersion/collecte, et c'est la "taille" de ces types. Nous ne pouvions pas encore utiliser MPI_Scatter() (ou même scatterv) avec ces types, car ces types ont une étendue de 16 entiers; c'est-à-dire, où ils se terminent est de 16 entiers après avoir commencé - et où ils se terminent ne correspondent pas bien avec où commence le bloc suivant, donc nous ne pouvons pas simplement utiliser scatter - il choisirait le mauvais endroit pour commencer à envoyer des données au processeur suivant.

Bien sûr, nous pourrions utiliser MPI_Scatterv() et spécifier les déplacements nous-mêmes, et c'est ce que nous ferons - sauf que les déplacements sont en unités de la taille du type d'envoi, et cela ne nous aide pas non plus; les blocs commencent à des décalages de (0,3,18,21) entiers depuis le début du tableau global, et le fait qu'un bloc se termine à 16 entiers d'où il commence ne nous permet pas du tout d'exprimer ces déplacements en multiples entiers .

Pour faire face à cela, MPI vous permet de définir l'étendue du type aux fins de ces calculs. Il ne tronque pas le type; il est juste utilisé pour déterminer où l'élément suivant commence étant donné dernier élément. Pour des types comme ceux-ci avec des trous, il est souvent pratique de définir l'étendue pour qu'elle soit quelque chose de plus petit que la distance en mémoire jusqu'à la fin réelle du type.

Nous pouvons définir l'étendue de tout ce qui nous convient. Nous pourrions simplement faire l'étendue 1 entier, puis définir les déplacements en unités d'entiers. Dans ce cas, cependant, j'aime définir l'étendue à 3 entiers - la taille d'une sous-ligne - de cette façon, le bloc "1" commence immédiatement après le bloc "0" et le bloc "3" commence immédiatement après le bloc " 2 ". Malheureusement, cela ne fonctionne pas aussi bien lors du saut du bloc "2" au bloc "3", mais cela ne peut pas être résolu.

Donc, pour disperser les sous-blocs dans ce cas, nous ferions ce qui suit:

    MPI_Datatype type, resizedtype;
    int sizes[2]    = {6,6};  /* size of global array */
    int subsizes[2] = {3,3};  /* size of sub-region */
    int starts[2]   = {0,0};  /* let's say we're looking at region "0",
                                 which begins at index [0,0] */

    /* as before */
    MPI_Type_create_subarray(2, sizes, subsizes, starts, MPI_ORDER_C, MPI_INT, &type);  
    /* change the extent of the type */
    MPI_Type_create_resized(type, 0, 3*sizeof(int), &resizedtype);
    MPI_Type_commit(&resizedtype);

Ici, nous avons créé le même type de bloc qu'auparavant, mais nous l'avons redimensionné; nous n'avons pas changé où le type "commence" (le 0) mais nous avons changé où il "finit" (3 pouces). Nous ne l'avons pas mentionné auparavant, mais le MPI_Type_commit Est requis pour pouvoir utiliser le type; mais il vous suffit de valider le type final que vous utilisez réellement, pas d'étapes intermédiaires. Vous utilisez MPI_Type_free Pour libérer le type lorsque vous avez terminé.

Alors maintenant, enfin, nous pouvons disperser les blocs: les manipulations de données ci-dessus sont un peu compliquées, mais une fois cela fait, le scatterv ressemble à avant:

int counts[4] = {1,1,1,1};   /* how many pieces of data everyone has, in units of blocks */
int displs[4] = {0,1,6,7};   /* the starting point of everyone's data */
                             /* in the global array, in block extents */

MPI_Scatterv(global, counts, displs, /* proc i gets counts[i] types from displs[i] */
            resizedtype,      
            local, 3*3, MPI_INT;   /* I'm receiving 3*3 MPI_INTs into local */
            root, MPI_COMM_WORLD);

Et maintenant, nous avons terminé, après un petit tour de dispersion, de rassemblement et de types dérivés MPI.

Un exemple de code qui montre à la fois l'opération de collecte et de diffusion, avec des tableaux de caractères, suit. Exécution du programme:

$ mpirun -n 4 ./gathervarray
Global array is:
0123456789
3456789012
6789012345
9012345678
2345678901
5678901234
8901234567
1234567890
4567890123
7890123456
Local process on rank 0 is:
|01234|
|34567|
|67890|
|90123|
|23456|
Local process on rank 1 is:
|56789|
|89012|
|12345|
|45678|
|78901|
Local process on rank 2 is:
|56789|
|89012|
|12345|
|45678|
|78901|
Local process on rank 3 is:
|01234|
|34567|
|67890|
|90123|
|23456|
Processed grid:
AAAAABBBBB
AAAAABBBBB
AAAAABBBBB
AAAAABBBBB
AAAAABBBBB
CCCCCDDDDD
CCCCCDDDDD
CCCCCDDDDD
CCCCCDDDDD
CCCCCDDDDD

et le code suit.

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include "mpi.h"

int malloc2dchar(char ***array, int n, int m) {

    /* allocate the n*m contiguous items */
    char *p = (char *)malloc(n*m*sizeof(char));
    if (!p) return -1;

    /* allocate the row pointers into the memory */
    (*array) = (char **)malloc(n*sizeof(char*));
    if (!(*array)) {
       free(p);
       return -1;
    }

    /* set up the pointers into the contiguous memory */
    for (int i=0; i<n; i++)
       (*array)[i] = &(p[i*m]);

    return 0;
}

int free2dchar(char ***array) {
    /* free the memory - the first element of the array is at the start */
    free(&((*array)[0][0]));

    /* free the pointers into the memory */
    free(*array);

    return 0;
}

int main(int argc, char **argv) {
    char **global, **local;
    const int gridsize=10; // size of grid
    const int procgridsize=2;  // size of process grid
    int rank, size;        // rank of current process and no. of processes

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);


    if (size != procgridsize*procgridsize) {
        fprintf(stderr,"%s: Only works with np=%d for now\n", argv[0], procgridsize);
        MPI_Abort(MPI_COMM_WORLD,1);
    }


    if (rank == 0) {
        /* fill in the array, and print it */
        malloc2dchar(&global, gridsize, gridsize);
        for (int i=0; i<gridsize; i++) {
            for (int j=0; j<gridsize; j++)
                global[i][j] = '0'+(3*i+j)%10;
        }


        printf("Global array is:\n");
        for (int i=0; i<gridsize; i++) {
            for (int j=0; j<gridsize; j++)
                putchar(global[i][j]);

            printf("\n");
        }
    }

    /* create the local array which we'll process */
    malloc2dchar(&local, gridsize/procgridsize, gridsize/procgridsize);

    /* create a datatype to describe the subarrays of the global array */

    int sizes[2]    = {gridsize, gridsize};         /* global size */
    int subsizes[2] = {gridsize/procgridsize, gridsize/procgridsize};     /* local size */
    int starts[2]   = {0,0};                        /* where this one starts */
    MPI_Datatype type, subarrtype;
    MPI_Type_create_subarray(2, sizes, subsizes, starts, MPI_ORDER_C, MPI_CHAR, &type);
    MPI_Type_create_resized(type, 0, gridsize/procgridsize*sizeof(char), &subarrtype);
    MPI_Type_commit(&subarrtype);

    char *globalptr=NULL;
    if (rank == 0) globalptr = &(global[0][0]);

    /* scatter the array to all processors */
    int sendcounts[procgridsize*procgridsize];
    int displs[procgridsize*procgridsize];

    if (rank == 0) {
        for (int i=0; i<procgridsize*procgridsize; i++) sendcounts[i] = 1;
        int disp = 0;
        for (int i=0; i<procgridsize; i++) {
            for (int j=0; j<procgridsize; j++) {
                displs[i*procgridsize+j] = disp;
                disp += 1;
            }
            disp += ((gridsize/procgridsize)-1)*procgridsize;
        }
    }


    MPI_Scatterv(globalptr, sendcounts, displs, subarrtype, &(local[0][0]),
                 gridsize*gridsize/(procgridsize*procgridsize), MPI_CHAR,
                 0, MPI_COMM_WORLD);

    /* now all processors print their local data: */

    for (int p=0; p<size; p++) {
        if (rank == p) {
            printf("Local process on rank %d is:\n", rank);
            for (int i=0; i<gridsize/procgridsize; i++) {
                putchar('|');
                for (int j=0; j<gridsize/procgridsize; j++) {
                    putchar(local[i][j]);
                }
                printf("|\n");
            }
        }
        MPI_Barrier(MPI_COMM_WORLD);
    }

    /* now each processor has its local array, and can process it */
    for (int i=0; i<gridsize/procgridsize; i++) {
        for (int j=0; j<gridsize/procgridsize; j++) {
            local[i][j] = 'A' + rank;
        }
    }

    /* it all goes back to process 0 */
    MPI_Gatherv(&(local[0][0]), gridsize*gridsize/(procgridsize*procgridsize),  MPI_CHAR,
                 globalptr, sendcounts, displs, subarrtype,
                 0, MPI_COMM_WORLD);

    /* don't need the local data anymore */
    free2dchar(&local);

    /* or the MPI data type */
    MPI_Type_free(&subarrtype);

    if (rank == 0) {
        printf("Processed grid:\n");
        for (int i=0; i<gridsize; i++) {
            for (int j=0; j<gridsize; j++) {
                putchar(global[i][j]);
            }
            printf("\n");
        }

        free2dchar(&global);
    }


    MPI_Finalize();

    return 0;
}
134
Jonathan Dursi

Je viens de trouver plus facile de vérifier de cette façon.

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include "mpi.h"

/*
 This is a version with integers, rather than char arrays, presented in this
 very good answer: http://stackoverflow.com/a/9271753/2411320
 It will initialize the 2D array, scatter it, increase every value by 1 and then gather it back.
*/

int malloc2D(int ***array, int n, int m) {
    int i;
    /* allocate the n*m contiguous items */
    int *p = malloc(n*m*sizeof(int));
    if (!p) return -1;

    /* allocate the row pointers into the memory */
    (*array) = malloc(n*sizeof(int*));
    if (!(*array)) {
       free(p);
       return -1;
    }

    /* set up the pointers into the contiguous memory */
    for (i=0; i<n; i++)
       (*array)[i] = &(p[i*m]);

    return 0;
}

int free2D(int ***array) {
    /* free the memory - the first element of the array is at the start */
    free(&((*array)[0][0]));

    /* free the pointers into the memory */
    free(*array);

    return 0;
}

int main(int argc, char **argv) {
    int **global, **local;
    const int gridsize=4; // size of grid
    const int procgridsize=2;  // size of process grid
    int rank, size;        // rank of current process and no. of processes
    int i, j, p;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);


    if (size != procgridsize*procgridsize) {
        fprintf(stderr,"%s: Only works with np=%d for now\n", argv[0], procgridsize);
        MPI_Abort(MPI_COMM_WORLD,1);
    }


    if (rank == 0) {
        /* fill in the array, and print it */
        malloc2D(&global, gridsize, gridsize);
        int counter = 0;
        for (i=0; i<gridsize; i++) {
            for (j=0; j<gridsize; j++)
                global[i][j] = ++counter;
        }


        printf("Global array is:\n");
        for (i=0; i<gridsize; i++) {
            for (j=0; j<gridsize; j++) {
                printf("%2d ", global[i][j]);
            }
            printf("\n");
        }
    }
    //return;

    /* create the local array which we'll process */
    malloc2D(&local, gridsize/procgridsize, gridsize/procgridsize);

    /* create a datatype to describe the subarrays of the global array */
    int sizes[2]    = {gridsize, gridsize};         /* global size */
    int subsizes[2] = {gridsize/procgridsize, gridsize/procgridsize};     /* local size */
    int starts[2]   = {0,0};                        /* where this one starts */
    MPI_Datatype type, subarrtype;
    MPI_Type_create_subarray(2, sizes, subsizes, starts, MPI_ORDER_C, MPI_INT, &type);
    MPI_Type_create_resized(type, 0, gridsize/procgridsize*sizeof(int), &subarrtype);
    MPI_Type_commit(&subarrtype);

    int *globalptr=NULL;
    if (rank == 0)
        globalptr = &(global[0][0]);

    /* scatter the array to all processors */
    int sendcounts[procgridsize*procgridsize];
    int displs[procgridsize*procgridsize];

    if (rank == 0) {
        for (i=0; i<procgridsize*procgridsize; i++)
            sendcounts[i] = 1;
        int disp = 0;
        for (i=0; i<procgridsize; i++) {
            for (j=0; j<procgridsize; j++) {
                displs[i*procgridsize+j] = disp;
                disp += 1;
            }
            disp += ((gridsize/procgridsize)-1)*procgridsize;
        }
    }


    MPI_Scatterv(globalptr, sendcounts, displs, subarrtype, &(local[0][0]),
                 gridsize*gridsize/(procgridsize*procgridsize), MPI_INT,
                 0, MPI_COMM_WORLD);

    /* now all processors print their local data: */

    for (p=0; p<size; p++) {
        if (rank == p) {
            printf("Local process on rank %d is:\n", rank);
            for (i=0; i<gridsize/procgridsize; i++) {
                putchar('|');
                for (j=0; j<gridsize/procgridsize; j++) {
                    printf("%2d ", local[i][j]);
                }
                printf("|\n");
            }
        }
        MPI_Barrier(MPI_COMM_WORLD);
    }

    /* now each processor has its local array, and can process it */
    for (i=0; i<gridsize/procgridsize; i++) {
        for (j=0; j<gridsize/procgridsize; j++) {
            local[i][j] += 1; // increase by one the value
        }
    }

    /* it all goes back to process 0 */
    MPI_Gatherv(&(local[0][0]), gridsize*gridsize/(procgridsize*procgridsize),  MPI_INT,
                 globalptr, sendcounts, displs, subarrtype,
                 0, MPI_COMM_WORLD);

    /* don't need the local data anymore */
    free2D(&local);

    /* or the MPI data type */
    MPI_Type_free(&subarrtype);

    if (rank == 0) {
        printf("Processed grid:\n");
        for (i=0; i<gridsize; i++) {
            for (j=0; j<gridsize; j++) {
                printf("%2d ", global[i][j]);
            }
            printf("\n");
        }

        free2D(&global);
    }


    MPI_Finalize();

    return 0;
}

Production:

linux16:>mpicc -o main main.c
linux16:>mpiexec -n 4 main Global array is:
 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15 16
Local process on rank 0 is:
| 1  2 |
| 5  6 |
Local process on rank 1 is:
| 3  4 |
| 7  8 |
Local process on rank 2 is:
| 9 10 |
|13 14 |
Local process on rank 3 is:
|11 12 |
|15 16 |
Processed grid:
 2  3  4  5
 6  7  8  9
10 11 12 13
14 15 16 17
1
gsamaras