tutorial - mpi c
MPI_Type_create_subarray y MPI_Gather (1)
Tengo que resolver un pequeño problema mpi. Tengo 4 procesos de esclavos y cada uno de ellos quiere enviar un subcampo 2d (CHUNK_ROWS X CHUNK_COLUMNS) al maestro 0. El maestro 0 recoge todos los fragmentos en ddd [ROWS] [COLUMNS] e imprime. 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.
Así que esto es un poco más sutil, y requiere cierta comprensión de cómo el colectivo Gather coloca tipos complejos.
Si nos fijamos en la mayoría de los ejemplos de MPI_Gather , son de matrices de 1-d, y es bastante fácil interpretar lo que debería suceder; obtiene (digamos) 10 tiros de cada proceso, y Gather es lo suficientemente inteligente como para poner 10 ints del rango 0 al inicio, el 10 del rango 1 en las posiciones 10-19 en el conjunto, y así sucesivamente.
Los diseños más complejos como este son un poco más complicados, sin embargo. En primer lugar, el diseño de los datos desde el punto de vista del remitente es diferente del diseño de los datos de los receptores. En el punto de vista del remitente, comienzas en el elemento de matriz [1][2]
, vas a [1][5]
(en una matriz de tamaño 7x7), luego saltas a los elementos de la matriz [2][3]
- [2][5]
, etc. Hay bloques de datos CHUNK_ROWS, cada uno separado por 2 ints.
Ahora considere cómo el receptor debe recibirlos. Digamos que está recibiendo datos de rango 0. Va a recibir eso en elementos de la matriz [0][0]-[0][4]
- hasta ahora todo bien; pero luego recibirá el siguiente bloque de datos en [1][0]-[1][4]
, en una matriz de tamaño 10x10. Eso es un salto sobre 5 elementos. El diseño en la memoria es diferente. Por lo tanto, el receptor tendrá que recibir un tipo de Subarray
diferente del que Subarray
los remitentes, ya que el diseño de la memoria es diferente.
Así que, aunque podría estar enviando desde algo que se ve así:
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 algo que se ve así:
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, observe la diferencia en la matriz de sizes
.
Ahora nos estamos acercando un poco. 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 con la versión anterior, MPI_Gather(DEBUG_CH[0],1,subarray,ddd[0],CHUNK_ROWS*CHUNK_COLUMNS,MPI_INT,0,MPI_COMM_WORLD);
- en primer lugar, tenga en cuenta que está haciendo referencia a ddd[0]
, pero para cada rango, excepto el rango 0, ddd=NULL
, por lo que esto no funcionará. Entonces crea una nueva variable llamada say recvptr
, y en el rango cero, recvptr
a ddd[0]
. (No importa dónde piensen los otros procesos, ya que no están recibiendo). Además, creo que no desea recibir CHUNK_ROWS*CHUNK_COLUMS
MPI_INTs
, porque eso los colocaría contiguamente en la memoria, y mi la comprensión es que los quiere presentados de la misma manera que en las tareas esclavas, pero en la matriz más grande.
Ok, ahora estamos llegando a algún lado, pero lo anterior aún no funcionará, por una razón interesante. Para los ejemplos de matriz 1d, es bastante fácil averiguar dónde van los datos de los rangos n-ésimo. La forma en que se calcula es encontrando la extensión de los datos que se reciben, y comenzando el siguiente elemento justo después de ese. Pero eso no funcionará aquí. "Justo después de" el final de los datos del rango cero no es donde comenzarán los datos del rango uno ( [0][5]
) sino que, en cambio, [4][5]
- el elemento después del último elemento en el suborden del rango 0s. ¡Aquí, los datos que estás recibiendo de diferentes rangos se superponen! Así que vamos a tener que jugar con las extensiones de los tipos de datos y especificar manualmente dónde comienza cada dato de rango. El segundo es la parte fácil; utiliza la función MPI_Gatherv cuando necesita especificar manualmente la cantidad de datos de cada procesador, o a dónde va. El primero es la parte más difícil.
MPI le permite especificar los límites inferior y superior de un tipo de datos dado, donde, dado un trozo de memoria, iría el primer bit de datos para este tipo, y donde "termina", lo que aquí solo significa que el siguiente podría comenzar. (Los datos pueden extenderse más allá del límite superior del tipo, lo cual, argumentaría, hace que esos nombres sean engañosos, pero esa es la forma de las cosas). Puede especificar que sea lo que quiera que lo haga más conveniente para usted; ya que trataremos con elementos en una matriz int
, hagamos la extensión de nuestro tipo one MPI_INT en tamaño.
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 gatherv 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 vaya allí en la posición [5][5]
, el desplazamiento es 55.
Finalmente, note que los colectivos de reunión y dispersión asumen que incluso el "maestro" está participando. Es más fácil hacer que esto funcione si incluso el maestro tiene su propia pieza del conjunto global.
Entonces 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;
}