Senden von Blöcken eines 2D-Arrays in C mit MPI

Lesezeit: 18 Minuten

Wie senden Sie Blöcke eines 2-D-Arrays an verschiedene Prozessoren? Angenommen, die Größe des 2D-Arrays beträgt 400 x 400, und ich möchte Blöcke der Größe 100 x 100 an verschiedene Prozessoren senden. Die Idee ist, dass jeder Prozessor Berechnungen an seinem separaten Block durchführt und sein Ergebnis für das Endergebnis an den ersten Prozessor zurücksendet.
Ich verwende MPI in C-Programmen.

  • Du solltest die Antwort akzeptieren…

    – Gsamaras

    29. Juni 2015 um 15:47 Uhr

Benutzeravatar von Jonathan Dursi
Jonathan Dursi

Lassen Sie mich zunächst sagen, dass Sie dies im Allgemeinen nicht wirklich tun möchten – große Datenmengen von einem “Master” -Prozess zu zerstreuen und zu sammeln. Normalerweise möchten Sie, dass jede Aufgabe an ihrem eigenen Teil des Puzzles tuckert, und Sie sollten darauf abzielen, dass nie ein Prozessor eine “globale Ansicht” der gesamten Daten benötigt; Sobald Sie dies benötigen, schränken Sie die Skalierbarkeit und die Problemgröße ein. Wenn Sie dies für I/O tun – ein Prozess liest die Daten, verteilt sie dann und sammelt sie dann zum Schreiben wieder ein, sollten Sie sich schließlich mit MPI-IO befassen.

Um zu Ihrer Frage zu kommen, MPI hat jedoch sehr gute Möglichkeiten, beliebige Daten aus dem Speicher zu ziehen und sie zu und von einer Reihe von Prozessoren zu verteilen / zu sammeln. Leider erfordert dies eine ganze Reihe von MPI-Konzepten – MPI-Typen, Extents und kollektive Operationen. Viele der grundlegenden Ideen werden in der Antwort auf diese Frage diskutiert – MPI_Type_create_subarray und MPI_Gather.

Aktualisieren – Im kalten Licht des Tages ist dies eine Menge Code und nicht viel Erklärung. Lassen Sie mich also etwas erweitern.

Stellen Sie sich ein globales 1d-Integer-Array vor, das Task 0 hat und das Sie an eine Reihe von MPI-Tasks verteilen möchten, sodass sie jeweils ein Stück in ihrem lokalen Array erhalten. Angenommen, Sie haben 4 Aufgaben und das globale Array ist [01234567]. Sie könnten Task 0 vier Nachrichten senden lassen (darunter eine an sich selbst), um dies zu verteilen, und wenn es Zeit ist, es wieder zusammenzusetzen, vier Nachrichten erhalten, um es wieder zusammenzufassen; aber das wird bei einer großen Anzahl von Prozessen natürlich sehr zeitaufwändig. Es gibt optimierte Routinen für diese Art von Operationen – Scatter/Gather-Operationen. In diesem 1d-Fall würden Sie also so etwas tun:

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 */

Danach würden die Daten der Prozessoren aussehen

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

Das heißt, die Scatter-Operation nimmt das globale Array und sendet zusammenhängende 2-int-Blöcke an alle Prozessoren.

Um das Array wieder zusammenzusetzen, verwenden wir die MPI_Gather() Operation, die genauso funktioniert, aber umgekehrt:

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 */

und jetzt sehen die Daten aus

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 bringt alle Daten zurück, und hier ist a 10, weil ich meine Formatierung zu Beginn dieses Beispiels nicht sorgfältig genug durchdacht habe.

Was passiert, wenn die Anzahl der Datenpunkte die Anzahl der Prozesse nicht gleichmäßig teilt und wir unterschiedliche Anzahlen von Elementen an jeden Prozess senden müssen? Dann brauchen Sie eine verallgemeinerte Version von Scatter, MPI_Scatterv(), mit dem Sie die Anzahl für jeden Prozessor und die Verschiebungen angeben können – wo im globalen Array dieses Datenelement beginnt. Nehmen wir also an, Sie hätten eine Reihe von Charakteren [abcdefghi] mit 9 Zeichen, und Sie wollten jedem Prozess zwei Zeichen zuweisen, mit Ausnahme des letzten, der drei erhielt. Dann bräuchte man

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);

Jetzt sehen die Daten so aus

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

Sie haben jetzt scatterv verwendet, um die unregelmäßigen Datenmengen zu verteilen. Die Verschiebung ist in jedem Fall zwei*Ränge (gemessen in Zeichen; die Verschiebung ist in der Einheit der Typen, die für eine Streuung gesendet oder für eine Sammlung empfangen werden; es ist im Allgemeinen nicht in Bytes oder so etwas) vom Anfang des Arrays und der Zählungen sind {2,2,2,3}. Wenn es der erste Prozessor gewesen wäre, den wir 3 Zeichen haben wollten, hätten wir counts={3,2,2,2} gesetzt und Displacements wären {0,3,5,7} gewesen. Gatherv funktioniert wieder genauso, aber umgekehrt; die counts- und displs-Arrays würden gleich bleiben.

Nun, für 2D ist dies etwas kniffliger. Wenn wir 2d-Sublocks eines 2d-Arrays senden möchten, sind die Daten, die wir jetzt senden, nicht mehr zusammenhängend. Wenn wir (sagen wir) 3×3 Unterblöcke eines 6×6-Arrays an 4 Prozessoren senden, haben die Daten, die wir senden, Löcher:

2D Array

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

Actual layout in memory

   [000111000111000111222333222333222333]

(Beachten Sie, dass es beim Hochleistungsrechnen darauf ankommt, das Layout der Daten im Speicher zu verstehen.)

Wenn wir die mit „1“ markierten Daten an Aufgabe 1 senden wollen, müssen wir drei Werte überspringen, drei Werte senden, drei Werte überspringen, drei Werte senden, drei Werte überspringen, drei Werte senden. Eine zweite Komplikation besteht darin, wo die Unterregionen aufhören und beginnen; Beachten Sie, dass Region „1“ nicht dort beginnt, wo Region „0“ aufhört; nach dem letzten Element des Bereichs “0” ist die nächste Stelle im Speicher halbwegs durch den Bereich “1”.

Lassen Sie uns zuerst das erste Layoutproblem angehen – wie man nur die Daten herauszieht, die wir senden wollen. Wir könnten immer einfach alle Daten der “0”-Region in ein anderes, zusammenhängendes Array kopieren und dieses senden; Wenn wir es sorgfältig genug planten, könnten wir das sogar so machen, dass wir anrufen könnten MPI_Scatter auf die Ergebnisse. Aber wir möchten lieber nicht unsere gesamte Hauptdatenstruktur auf diese Weise transponieren.

Bisher sind alle MPI-Datentypen, die wir verwendet haben, einfache – MPI_INT spezifiziert (sagen wir) 4 Bytes in einer Reihe. Mit MPI können Sie jedoch Ihre eigenen Datentypen erstellen, die beliebig komplexe Datenlayouts im Speicher beschreiben. Und dieser Fall – rechteckige Unterregionen eines Arrays – ist so häufig, dass es dafür einen speziellen Aufruf gibt. Für den zweidimensionalen Fall, den wir oben beschreiben,

    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);

Dadurch wird ein Typ erstellt, der nur die Region “0” aus dem globalen Array auswählt; Wir könnten jetzt genau diese Daten an einen anderen Prozessor senden

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

und der empfangende Prozess könnte es in einem lokalen Array empfangen. Beachten Sie, dass der empfangende Prozess dies kann, wenn er es nur in einem 3×3-Array empfängt nicht beschreiben, was es als eine Art von empfängt newtype; die das Speicherlayout nicht mehr beschreibt. Stattdessen empfängt es nur einen Block von 3*3 = 9 Ganzzahlen:

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

Beachten Sie, dass wir dies auch für andere Unterregionen tun könnten, indem wir entweder einen anderen Typ (mit verschiedenen start array) für die anderen Blöcke oder einfach durch Senden am Startpunkt des jeweiligen Blocks:

    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" */

Beachten Sie schließlich, dass global und lokal hier zusammenhängende Speicherblöcke sein müssen; das ist, &(global[0][0]) und &(local[0][0]) (oder gleichwertig, *global und *local zeigen auf zusammenhängende 6*6- und 3*3-Speicherblöcke; dies wird durch die übliche Art der Zuweisung dynamischer Multi-D-Arrays nicht garantiert. Wie das geht, wird unten gezeigt.

Nachdem wir nun verstanden haben, wie Unterregionen angegeben werden, gibt es nur noch eine Sache zu besprechen, bevor Scatter/Gather-Operationen verwendet werden, und das ist die “Größe” dieser Typen. Wir konnten nicht einfach benutzen MPI_Scatter() (oder sogar scatterv) mit diesen Typen noch, weil diese Typen einen Umfang von 16 ganzen Zahlen haben; Das heißt, wo sie enden, sind 16 Ganzzahlen nach ihrem Start – und wo sie enden, stimmt nicht gut mit dem Beginn des nächsten Blocks überein, also können wir nicht einfach Scatter verwenden – es würde die falsche Stelle auswählen, um mit dem Senden von Daten zu beginnen zum nächsten Prozessor.

Könnten wir natürlich gebrauchen MPI_Scatterv() und die Verschiebungen selbst angeben, und das werden wir tun – außer dass die Verschiebungen in Einheiten der Größe des Sendetyps angegeben sind, und das hilft uns auch nicht; Die Blöcke beginnen bei Offsets von (0,3,18,21) Ganzzahlen vom Anfang des globalen Arrays, und die Tatsache, dass ein Block 16 Ganzzahlen von seinem Anfang entfernt endet, lässt uns diese Verschiebungen überhaupt nicht in ganzzahligen Vielfachen ausdrücken .

Um dies zu bewältigen, können Sie mit MPI den Umfang des Typs für diese Berechnungen festlegen. Der Typ wird nicht abgeschnitten; Es wird nur verwendet, um herauszufinden, wo das nächste Element beginnt, wenn das letzte Element gegeben ist. Bei Typen wie diesen mit Löchern ist es häufig praktisch, die Ausdehnung auf einen kleineren Wert als die Distanz im Speicher zum tatsächlichen Ende des Typs einzustellen.

Wir können den Umfang so einstellen, dass er für uns bequem ist. Wir könnten einfach die Ausdehnung 1 ganzzahlig machen und dann die Verschiebungen in Einheiten von ganzen Zahlen festlegen. In diesem Fall möchte ich den Umfang jedoch auf 3 Ganzzahlen setzen – die Größe einer Unterzeile – auf diese Weise beginnt Block “1” unmittelbar nach Block “0” und Block “3” beginnt unmittelbar nach Block ” 2″. Leider funktioniert es beim Springen von Block “2” zu Block “3” nicht ganz so gut, aber das lässt sich nicht ändern.

Um die Unterblöcke in diesem Fall zu verteilen, würden wir Folgendes tun:

    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);

Hier haben wir denselben Blocktyp wie zuvor erstellt, aber wir haben seine Größe geändert; Wir haben nicht geändert, wo der Typ “beginnt” (die 0), aber wir haben geändert, wo er “endet” (3 Ints). Wir haben das vorher nicht erwähnt, aber die MPI_Type_commit ist erforderlich, um den Typ verwenden zu können; aber Sie müssen nur den endgültigen Typ festschreiben, den Sie tatsächlich verwenden, keine Zwischenschritte. Sie nutzen MPI_Type_free um den Typ zu befreien, wenn Sie fertig sind.

Jetzt können wir endlich die Blöcke verstreuen: Die obigen Datenmanipulationen sind ein wenig kompliziert, aber sobald sie fertig sind, sieht die Verstreuung genauso aus wie zuvor:

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);

Und jetzt sind wir fertig, nach einer kleinen Tour durch Scatter-, Gather- und MPI-abgeleitete Typen.

Es folgt ein Beispielcode, der sowohl die Sammel- als auch die Streuoperation mit Zeichenarrays zeigt. Ausführen des Programms:

$ 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

und der Code folgt.

#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;
}

  • Das kommt hier in irgendeiner Version immer wieder vor; Ich hoffe, eine Antwort zu schreiben, auf die wir die Leute immer wieder hinweisen können. Aber danke 🙂

    – Jonathan Dursi

    14. Februar 2012 um 16:11 Uhr

  • Ich bin mit Fortran MPI ziemlich vertraut, habe dies aber als zukünftige Referenz favorisiert. Außerdem schließe ich mich Morts Kommentar an.

    – milancurcic

    14. Februar 2012 um 16:14 Uhr


  • Dieser ganze Prozess ist einfacher in Fortran, das mehrdimensionale Arrays in die Sprache eingebaut hat; etwas, das C immer wieder nicht einschließt. Und Sie beide geben bereits ziemlich starke Antworten auf SO …

    – Jonathan Dursi

    14. Februar 2012 um 16:21 Uhr


  • Tatsächlich ist das Problem so, dass auf einem 400×400-Raster ein 5×5-Raster von Lichtquellen abgebildet wird. Die Lichtquellen variieren in der Höhe (h) entlang einer Richtung und der Leistung (p) in der anderen Richtung. Wir müssen den Punkt der minimalen Beleuchtung im Gitter finden. Beleuchtung an jedem Punkt (x,y) wegen der Lichtquelle bei (xl, yl) ist gegeben durch I = h*p/(r^3) wobei r^2 = (x-xl)^2 + (y-yl )^2 + h^2 .

    – Sonnenteufel

    15. Februar 2012 um 5:59 Uhr

  • Ich wollte wissen, was der beste Ansatz ist, um dies mit MPI zu lösen.

    – Sonnenteufel

    15. Februar 2012 um 6:00 Uhr

Ich fand es nur einfacher, es so zu überprüfen.

#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;
}

Ausgabe:

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

1411310cookie-checkSenden von Blöcken eines 2D-Arrays in C mit MPI

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy