MPI_Type_create_subarray и MPI_Gather - PullRequest
       50

MPI_Type_create_subarray и MPI_Gather

11 голосов
/ 07 апреля 2011

Я должен решить небольшую проблему MPI.У меня есть 4 подчиненных процесса, и каждый из них хочет отправить 2d-подмассив (CHUNK_ROWS X CHUNK_COLUMNS) мастеру 0. Мастер 0 собирает все чанки в ddd [ROWS] [COLUMNS] и печатает его.Я хочу использовать MPI_Gather ()

#include <mpi.h>
#include <iostream>
using namespace std;

#define ROWS 10
#define COLUMNS 10
#define CHUNK_ROWS 5
#define CHUNK_COLUMNS 5
#define TAG 0

int** alloca_matrice(int righe, int colonne)
{
int** matrice=NULL;
int i;

matrice = (int **)malloc(righe * sizeof(int*));

if(matrice != NULL){
  matrice[0] = (int *)malloc(righe*colonne*sizeof(int));
  if(matrice[0]!=NULL)
    for(i=1; i<righe; i++)
        matrice[i] = matrice[0]+i*colonne;
  else{
    free(matrice);
    matrice = NULL;
  }
}
else{
  matrice = NULL;
}
return matrice;

}

int main(int argc, char* argv[])
{

int my_id, numprocs,length,i,j;
int ndims, sizes[2],subsizes[2],starts[2];
int** DEBUG_CH=NULL;
int** ddd=NULL;
char name[BUFSIZ];
MPI_Datatype subarray=NULL;
//MPI_Status status;
MPI_Init(&argc, &argv) ;    
MPI_Comm_rank(MPI_COMM_WORLD, &my_id) ;
MPI_Comm_size(MPI_COMM_WORLD, &numprocs) ;  // Ottiene quanti processi sono attivi
MPI_Get_processor_name(name, &length);    

if(my_id!=0){
  //creo una sottomatrice ripulita dalle ghost cells
  ndims=2;
  sizes[0] = CHUNK_ROWS+2;
  sizes[1] = CHUNK_COLUMNS+2;
  subsizes[0] = CHUNK_ROWS;
  subsizes[1] = CHUNK_COLUMNS;
  starts[0] = 1;
  starts[1] = 1;
  MPI_Type_create_subarray(ndims,sizes,subsizes,starts,MPI_ORDER_C,MPI_INT,&subarray);
  MPI_Type_commit(&subarray);

  DEBUG_CH = alloca_matrice(CHUNK_ROWS+2,CHUNK_COLUMNS+2);
  for(i=0; i<CHUNK_ROWS+2; i++){
    for(j=0; j<CHUNK_COLUMNS+2; j++){
        if(i==0 || i==CHUNK_ROWS+1 || j==0 || j==CHUNK_COLUMNS+1)
            DEBUG_CH[i][j] = 5;
        else
            DEBUG_CH[i][j] = 1;
    }
  }
//MPI_Send(DEBUG_CH[0],1,subarray,0,TAG,MPI_COMM_WORLD);
}
if(my_id==0){
 ddd = alloca_matrice(ROWS,COLUMNS);
}

MPI_Gather(DEBUG_CH[0],1,subarray,ddd[0],CHUNK_ROWS*CHUNK_COLUMNS,MPI_INT,0,MPI_COMM_WORLD);
if(!my_id){
  for(i=0; i<ROWS; i++){
    for(j=0; j<COLUMNS; j++){
        printf("%d ",ddd[i][j]);
    }
    printf("\n");
  }
}

if(my_id)
 MPI_Type_free(&subarray);

MPI_Finalize();                             // Chiusura di MPI.
return 0;
}

Спасибо всем.

1 Ответ

20 голосов
/ 07 апреля 2011

Так что это немного более тонко и требует некоторого понимания того, как коллектив Gather размещает сложные типы.

Если вы посмотрите на большинство примеров MPI_Gather , они представляют собой одномерные массивы, и довольно легко интерпретировать, что должно произойти; вы получаете (скажем) 10 дюймов от каждого процесса, и Gather достаточно умен, чтобы поместить 10 дюймов с ранга 0 в начале, 10 с ранга 1 в позиции 10-19 в массиве и т. д.

Однако более сложные макеты немного сложнее. Во-первых, макет данных с точки зрения отправителя отличается от макета данных от получателей. С точки зрения отправителя, вы начинаете с элемента массива [1][2], переходите к [1][5] (в массиве размером 7x7), затем переходите к элементам массива [2][3] - [2][5] и т. Д. Существуют блоки данных CHUNK_ROWS, каждый из которых разделен двумя значениями.

Теперь рассмотрим, как получатель должен их получать. Допустим, он получает данные ранга 0. Он получит это в элементы массива [0][0]-[0][4] - пока все хорошо; но затем он получит следующий блок данных в [1][0]-[1][4] в массиве размером 10x10. Это перепрыгнуть через 5 элементов. Раскладка в памяти другая. Таким образом, получателю придется получать данные другого типа Subarray, чем отправители, поскольку схема памяти отличается.

Итак, в то время как вы можете отправлять что-то похожее на это:

  sizes[0] = CHUNK_ROWS+2;
  sizes[1] = CHUNK_COLUMNS+2;
  subsizes[0] = CHUNK_ROWS;
  subsizes[1] = CHUNK_COLUMNS;
  starts[0] = 1;
  starts[1] = 1;
  MPI_Type_create_subarray(ndims,sizes,subsizes,starts,MPI_ORDER_C,MPI_INT,&sendsubarray);
  MPI_Type_commit(&sendsubarray);

вы получите что-то похожее на это:

  sizes[0]    = ROWS; sizes[1] = COLUMNS;
  subsizes[0] = CHUNK_ROWS; subsizes[1] = CHUNK_COLUMNS;
  starts[0]   = 0; starts[1] = 0;
  MPI_Type_create_subarray(ndims,sizes,subsizes,starts,MPI_ORDER_C,MPI_INT,&recvsubarray);
  MPI_Type_commit(&recvsubarray);

Важно отметить разницу в массиве sizes.

Теперь мы немного ближе. Обратите внимание, что ваша строка MPI_Gather изменяется примерно так:

MPI_Gather(DEBUG_CH[0],1,sendsubarray,recvptr,1,recvsubarray,0,MPI_COMM_WORLD);

Было несколько вещей, которые не работали с предыдущей версией, MPI_Gather(DEBUG_CH[0],1,subarray,ddd[0],CHUNK_ROWS*CHUNK_COLUMNS,MPI_INT,0,MPI_COMM_WORLD); - во-первых, обратите внимание, что вы ссылаетесь на ddd[0], но для каждого ранга, кроме ранга 0, ddd=NULL, и вот так не удастся. Поэтому создайте новую переменную с именем say recvptr, и в нулевом ранге установите для нее ddd[0] (Неважно, где другие процессы думают, что это происходит, поскольку они не получают.) Кроме того, я думаю, что вы не хотите получать CHUNK_ROWS*CHUNK_COLUMS MPI_INTs, потому что это поместило бы их непрерывно в память, и я понимаю, что вы хотите, чтобы они были выложены так же, как на подчиненных задачах, но в большем массиве.

Хорошо, теперь мы кое-что получаем, но вышеприведенное все же не будет работать по интересной причине. Для примеров массива 1d достаточно просто определить, куда идут данные n-го ранга. Способ расчета - найти экстент полученных данных и запустить следующий элемент сразу после этого. Но это не сработает здесь. «Сразу после» данные конца нулевого ранга находятся не там, где должны начинаться данные ранга 1 ([0][5]), а вместо этого [4][5] - элемент после последнего элемента в подрешетке ранга 0s. Здесь данные, которые вы получаете из разных рангов, перекрываются! Таким образом, нам нужно поэкспериментировать с экстентами типов данных и вручную указать, где начинаются данные каждого ранга. Вторая легкая часть; Вы используете функцию MPI_Gatherv , когда вам нужно вручную указать объем данных от каждого процессора или куда он направляется. Первая сложная часть.

MPI позволяет вам указать нижнюю и верхнюю границы заданного типа данных - где, при наличии фрагмента памяти, первый бит данных для этого типа будет идти, и где он «заканчивается», что здесь означает только где следующий может начаться. (Данные могут выходить за верхнюю границу типа, что, я бы сказал, вводит в заблуждение эти имена, но так обстоит дело.) Вы можете указать, что это будет то, что вам нравится, что делает его удобным для вас; так как мы будем иметь дело с элементами в массиве int, давайте сделаем размер нашего типа MPI_INT размером один.

  MPI_Type_create_resized(recvsubarray, 0, 1*sizeof(int), &resizedrevsubarray);
  MPI_Type_commit(&resizedrecvsubarray);

(Обратите внимание, что мы должны делать это только для полученного типа; из типа отправки, поскольку мы отправляем только один из них, это не имеет значения).

Теперь мы будем использовать collectv, чтобы указать, где начинается каждый элемент - в единицах «размера» этого нового измененного типа, который составляет всего 1 целое число. Поэтому, если мы хотим, чтобы что-то вошло в большой массив в [0][5], смещение от начала большого массива равно 5; если мы хотим, чтобы он находился там в позиции [5][5], смещение составляет 55.

Наконец, обратите внимание, что коллективы коллектива и рассеяния предполагают, что в этом участвует даже «мастер». Проще всего заставить это работать, даже если у мастера есть свой собственный кусок глобального массива.

Итак, у меня работает следующее:

#include <mpi.h>
#include <iostream>
#include <cstdlib>
using namespace std;

#define ROWS 10
#define COLUMNS 10
#define CHUNK_ROWS 5
#define CHUNK_COLUMNS 5
#define TAG 0

int** alloca_matrice(int righe, int colonne)
{
    int** matrice=NULL;
    int i;

    matrice = (int **)malloc(righe * sizeof(int*));

    if(matrice != NULL){
        matrice[0] = (int *)malloc(righe*colonne*sizeof(int));
        if(matrice[0]!=NULL)
            for(i=1; i<righe; i++)
                matrice[i] = matrice[0]+i*colonne;
        else{
            free(matrice);
            matrice = NULL;
        }
    }
    else{
        matrice = NULL;
    }
    return matrice;

}

int main(int argc, char* argv[])
{

    int my_id, numprocs,length,i,j;
    int ndims, sizes[2],subsizes[2],starts[2];
    int** DEBUG_CH=NULL;
    int** ddd=NULL;
    int *recvptr=NULL;
    char name[BUFSIZ];
    MPI_Datatype sendsubarray;
    MPI_Datatype recvsubarray;
    MPI_Datatype resizedrecvsubarray;
    //MPI_Status status;
    MPI_Init(&argc, &argv) ;    
    MPI_Comm_rank(MPI_COMM_WORLD, &my_id) ;
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs) ;  // Ottiene quanti processi sono attivi
    if (numprocs != 4) {
        MPI_Abort(MPI_COMM_WORLD,1);
    }
    MPI_Get_processor_name(name, &length);    

    //creo una sottomatrice ripulita dalle ghost cells
    ndims=2;
    sizes[0] = CHUNK_ROWS+2;
    sizes[1] = CHUNK_COLUMNS+2;
    subsizes[0] = CHUNK_ROWS;
    subsizes[1] = CHUNK_COLUMNS;
    starts[0] = 1;
    starts[1] = 1;
    MPI_Type_create_subarray(ndims,sizes,subsizes,starts,MPI_ORDER_C,MPI_INT,&sendsubarray);
    MPI_Type_commit(&sendsubarray);

    DEBUG_CH = alloca_matrice(CHUNK_ROWS+2,CHUNK_COLUMNS+2);
    for(i=0; i<CHUNK_ROWS+2; i++){
        for(j=0; j<CHUNK_COLUMNS+2; j++){
            if(i==0 || i==CHUNK_ROWS+1 || j==0 || j==CHUNK_COLUMNS+1)
                DEBUG_CH[i][j] = 5;
            else
                DEBUG_CH[i][j] = my_id;
        }
    }

    recvptr=DEBUG_CH[0];
    if(my_id==0){
        ddd = alloca_matrice(ROWS,COLUMNS);
        sizes[0]    = ROWS; sizes[1] = COLUMNS;
        subsizes[0] = CHUNK_ROWS; subsizes[1] = CHUNK_COLUMNS;
        starts[0]   = 0; starts[1] = 0;
        MPI_Type_create_subarray(2,sizes,subsizes,starts,MPI_ORDER_C,MPI_INT,&recvsubarray);
        MPI_Type_commit(&recvsubarray);
        MPI_Type_create_resized(recvsubarray, 0, 1*sizeof(int), &resizedrecvsubarray);
        MPI_Type_commit(&resizedrecvsubarray);
        recvptr = ddd[0];
    }

    int counts[5]={1,1,1,1};
    int disps[5] ={0,5,50,55};
    MPI_Gatherv(DEBUG_CH[0],1,sendsubarray,recvptr,counts,disps,resizedrecvsubarray,0,MPI_COMM_WORLD);
    if(!my_id){
        for(i=0; i<ROWS; i++){
            for(j=0; j<COLUMNS; j++){
                printf("%d ",ddd[i][j]);
            }
            printf("\n");
        }
    }

    if(my_id == 0) {
        MPI_Type_free(&resizedrecvsubarray);
        MPI_Type_free(&recvsubarray);
        free(ddd[0]);
        free(ddd);
    } else {
        MPI_Type_free(&sendsubarray);
        free(DEBUG_CH[0]);
        free(DEBUG_CH);
    }

    MPI_Finalize();                             // Chiusura di MPI.
    return 0;
}
...