MPI_Type_create_subarray y MPI_Gather

9 minutos de lectura

avatar de usuario
riff

Tengo que resolver un pequeño problema de mpi. Tengo 4 procesos esclavos y cada uno de ellos quiere enviar un subarreglo 2d (CHUNK_ROWS X CHUNK_COLUMNS) al maestro 0. El maestro 0 recopila todos los fragmentos en ddd[ROWS][COLUMNS] e imprimirlo. Quiero usar 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;
}

Gracias a todos.

avatar de usuario
Jonathan Dursi

Entonces, esto es un poco más sutil y requiere cierta comprensión de cómo el colectivo Gather coloca los tipos complejos.

Si miras a lo sumo ejemplos de MPI_Gather, son matrices unidimensionales y es bastante fácil interpretar lo que debería suceder; está obteniendo (digamos) 10 entradas de cada proceso, y Gather es lo suficientemente inteligente como para poner las 10 entradas del rango 0 al principio, las 10 del rango 1 en las posiciones 10-19 en la matriz, y así sucesivamente.

Sin embargo, los diseños más complejos como este son un poco más complicados. Primero, el diseño de datos desde el punto de vista del remitente es diferente del diseño de datos de los receptores. En el punto de vista del remitente, comienza en el elemento de matriz [1][2]ir [1][5] (en una matriz de tamaño 7×7), luego salte a los elementos de la matriz [2][3][2][5]etc. Hay bloques de datos CHUNK_ROWS, cada uno separado por 2 enteros.

Ahora considere cómo el receptor tiene que recibirlos. Digamos que está recibiendo datos de rango 0. Va a recibir eso en elementos de matriz. [0][0]-[0][4] — Hasta ahora, todo bien; pero luego va a recibir el siguiente bloque de datos en [1][0]-[1][4], en una matriz de tamaño 10×10. Eso es un salto sobre 5 elementos. El diseño en la memoria es diferente. Así que el receptor tendrá que estar recibiendo en un diferente Subarray escriba desde el que envían los remitentes, porque el diseño de la memoria es diferente.

Entonces, aunque podría estar enviando desde algo que se parece a esto:

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

recibirás en algo que se parece a esto:

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

Crucialmente, note la diferencia en el sizes formación.

Ahora nos estamos acercando un poco más. Observe que su línea MPI_Gather cambia a algo como esto:

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

Hubo un par de cosas que no funcionaron en la versión anterior, MPI_Gather(DEBUG_CH[0],1,subarray,ddd[0],CHUNK_ROWS*CHUNK_COLUMNS,MPI_INT,0,MPI_COMM_WORLD); — primero, tenga en cuenta que está haciendo referencia ddd[0]pero para cada rango excepto el rango 0, ddd=NULL, por lo que esto fallará. Así que crea una nueva variable llamada say recvptry en el rango cero, establezca eso en ddd[0]. (No importa dónde piensen los otros procesos, ya que no están recibiendo). Además, creo que no quieres recibir CHUNK_ROWS*CHUNK_COLUMS MPI_INTsporque eso los colocaría de forma contigua en la memoria, y entiendo que usted quiere que se distribuyan de la misma manera que en las tareas de los trabajadores, pero en una matriz más grande.

Bien, ahora estamos llegando a alguna parte, pero lo anterior todavía no funcionará, por una razón interesante. Para los ejemplos de matriz 1d, es bastante fácil averiguar dónde van los datos de rangos n. La forma en que se calcula es encontrando el grado de los datos que se reciben, y comenzando el siguiente elemento justo después de ese. Pero eso no funcionará aquí. “Justo después” del final de los datos del rango cero no es donde deberían comenzar los datos del rango uno ([0][5]) pero en vez, [4][5] — el elemento después del último elemento en el subarreglo de rango 0s. ¡Aquí, los datos que está recibiendo de diferentes rangos se superponen! Así que vamos a tener que jugar con la extensión de los tipos de datos y especificar manualmente dónde comienzan los datos de cada rango. La segunda es la parte fácil; tu usas el MPI_Gatherv función cuando necesite especificar manualmente la cantidad de datos de cada procesador, o adónde van. La primera es la parte más complicada.

MPI le permite especificar los límites inferior y superior de un tipo de datos determinado: dónde, dada una parte de la memoria, iría el primer bit de datos para este tipo y dónde “termina”, que aquí solo significa dónde está el siguiente. podría empezar. (Los datos pueden extenderse más allá del límite superior del tipo, lo que diría que hace que esos nombres sean engañosos, pero así son las cosas). Puede especificar que esto sea lo que quiera que sea conveniente para usted; ya que estaremos tratando con elementos en un int matriz, hagamos que la extensión de nuestro tipo tenga un tamaño MPI_INT.

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

(Tenga en cuenta que solo tenemos que hacer esto para el tipo recibido; desde el tipo de envío, ya que solo estamos enviando uno de ellos, no importa).

Ahora, usaremos la recopilación para especificar dónde comienza cada elemento, en unidades del “tamaño” de este nuevo tipo redimensionado, que es solo 1 entero. Entonces, si queremos que algo entre en la matriz grande en [0][5], el desplazamiento desde el inicio de la matriz grande es 5; si queremos que entre allí en la posición [5][5]el desplazamiento es 55.

Finalmente, observe que todos los colectivos de recolección y dispersión asumen que incluso el proceso “maestro” o coordinador está participando. Es más fácil hacer que esto funcione si incluso el coordinador tiene su propia parte de la matriz global.

Así que con eso, lo siguiente funciona para mí:

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

  • Guau. Creo que no entendí cómo funciona mpi_gather… Muchas gracias. Voy a imprimir su respuesta y tomarla como una nota.

    – Riff

    7 abr 2011 a las 19:48

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad