GRADO EN INGENIERÍA DE COMPUTADORES Departamento de …atc2.aut.uah.es/~frutos/areinco/pdf/MPI.pdf · Desde el punto de vista del modelo de programación: Acceso directo sólo a
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
1
Prof. Dr. José Antonio de Frutos Redondo Curso 2013-2014
GRADO EN INGENIERÍA DE COMPUTADORES Arquitectura e Ingeniería de Computadores
Departamento de Automática Programación paralela MPI
n Antes de comenzar a usar la librería debemos llamar a la función MPI_Init. n Sólo debe hacerse una vez.
n Para terminar, debe llamarse a la MPI_Finalize. # include “mpi.h” ... main(int argc, char *argv[]) { ... /* Antes de este punto, no llamar a ninguna función MPI */
MPI_Init(&argc, &argv); ... MPI_Finalize(); /* Pasado este punto, no llamar a ninguna función MPI */ ... }
/* saludos.c -- Envía un mensaje desde todos los procesos con rango != 0 al proceso de rango
0. Éste imprime los mensajes recibidos. */ #include <stdio.h> #include <string.h> #include "mpi.h" main(int argc, char* argv[]) { int mi_rango; /* rango de este proceso */ int p; /* número total de procesos */ int fuente; /* rango del remitente */ int dest; /* rango del destinatario */ int tag = 0; /* etiqueta para el mensaje */ char mensaje[256]; /* buffer para el mensaje */ MPI_Status status; /* status de return */
MPI_Init(&argc, &argv); /* Arrancar MPI */ /* Hallar rango del proceso actual */ MPI_Comm_rank(MPI_COMM_WORLD, &mi_rango);
/* Número de procesos */ MPI_Comm_size(MPI_COMM_WORLD, &p); if (mi_rango != 0) { /* Crear mensaje */ sprintf(mensaje, “Saludos desde el proceso '%d'", mi_rango); dest = 0;
/* Usar strlen+1 para transmitir también '\0' */ MPI_Send(mensaje, strlen(mensaje)+1, MPI_CHAR, dest, tag,
n La función de MPI_Recv tiene el siguiente prototipo: MPI_Recv(void *mensaje, /* è */
int cuenta, /* ç */
MPI_Datatype tipodato, /* ç */
int remitente, /* ç */
int etiqueta, /* ç */
MPI_Comm grupo_com, /* ç */
MPI_Status *status); /* è */
n Atención: el parámetro cuenta especifica el número máximo de elementos de tipo tipodato que caben en el buffer mensaje. n Si se envía un mensaje más largo, se produce un error de
n La llamada a la función MPI_Bcast puede resultar en n recepción de datos, si el rango del proceso es distinto de raiz. n envío de datos, si el rango del proceso es igual a raiz.
n El valor de tipodato y cuenta ha de ser igual para todos. n Los procesos afectados serán todos los que se encuentren
en el grupo de comunicaciones grupo_com. n El sistema MPI garantiza que si un proceso difunde varios
mensajes (varias llamadas a MPI_Bcast), éstos serán recibidos por los demás procesos en el mismo orden en que fueron emitidos.
/* trap.c – Integración mediante trapecios. Algoritmo: 1. Cada proceso se autoasigna su propio intervalo de integración. 2. Cada proceso integra f(x) en su propio intervalo con el método de los trapecios.
3a. Cada proceso con rango != 0 envía su resultado al proceso 0. 3b. El proceso 0 suma los resultados de los cálculos realizados por los demás e imprime el resultado.
*/ #include <stdio.h> #include "mpi.h" /* Prototipo de la función que integra */ float Trap(float local_a, float local_b, int local_n, float h);
main(int argc, char *argv[]) { int mi_rango; /* El rango de mi proceso */ int p; /* Número total de procesos */
float a = 0.0; /* Extremo izquierdo */ float b = 1.0; /* Extremo derecho */ int n = 1024; /* Número de trapecios */ float h; /* Base de cada trapecio */ float local_a; /* Extremo izdo. de mi proceso */ float local_b; /* Extermo dcho. de mi proceso */
int local_n; /* Número de trapecios para mi cálculo */ float integral; /* Resultado de la integral en mi intervalo */ float total; /* Integral total */ int fuente; /* Proceso que remite el resultado */ int dest = 0; /* Todos los resultados van al proceso 0 */ int tag = 0;
MPI_Status status; MPI_Init(&argc, &argv); /* Arrancamos MPI */ MPI_Comm_rank(MPI_COMM_WORLD, &mi_rango); /* Obtengo mi propio rango */ MPI_Comm_size(MPI_COMM_WORLD, &p); /* Obtengo nº total de procesos */
h = (b-a)/n; /* h es el mismo para todos los procesos */ local_n = n/p; /* igual que el número de trapecios */
/* La longitud del intervalo de integración de cada proceso es igual a local_n*h. Así pues, mi intervalo empieza y acaba en: */
local_a = a + mi_rango*local_n*h; local_b = local_a + local_n*h; /* Calculo la integral en mi intervalo */ integral = Trap(local_a, local_b, local_n, h);
n Para resolver este problema, MPI proporciona la función MPI_Reduce con el siguiente prototipo: MPI_Reduce(void *operando, /* ç */
void *resultado, /* è */
int cuenta, /* ç */
MPI_Datatype tipodato, /* ç */
MPI_Op operacion, /* ç */
int raiz, /* ç */
MPI_Comm grupo_com); /* ç */
n MPI_Reduce combina los operandos almacenados en operando usando la operación operacion y almacena el resultado en resultado en el proceso raiz. Tanto operando como resultado se refieren a cuenta elementos de tipo tipodato.
n MPI_Reduce ha de ser llamado en todos los procesos del grupo de comunicación grupo_com y cuenta, tipodato y operacion han de valer lo mismo en todos los procesos.
n El argumento operacion puede valer n MPI_SUM
n MPI_PROD
n MPI_MAX
n MPI_MIN
n ...
n Obsérvese que la variable resultado sólo tiene sentido en el proceso raiz. Aun así, los demás procesos también han de especificarla.
n Podemos, entonces, sustituir if (mi_rango == 0) { /* Sumo los cálculos de cada proceso, que me envían los demás */ total = integral; for (fuente = 1; fuente < p; fuente++) { MPI_Recv(&integral, 1, MPI_FLOAT, fuente, tag, MPI_COMM_WORLD, &status); total = total + integral; } } else { /* Envío mi resultado al proceso 0 */ MPI_Send(&integral, 1, MPI_FLOAT, dest, tag, MPI_COMM_WORLD); }
n En algunos casos nos interesa que la reducción se efectúa en todos los procesos. Para ello existe la función MPI_Allreduce cuyo prototipo es:
MPI_Allreduce(void *operando, /* ç */
void *resultado, /* è */
int cuenta, /* ç */
MPI_Datatype tipodato, /* ç */
MPI_Op operacion, /* ç */
MPI_Comm grupo_com); /* ç */
n Se usa exactamente igual que MPI_Reduce, pero el resultado de la reducción se acumula en resultado en todos los procesos pertenecientes al grupo de comunicación grupo_com. Por ello no es necesario el parámetro raiz, como en el otro caso.
n Otras funciones de comunicación colectiva son las siguientes: MPI_Gather(void *buffer_envio, /* ç */
int cuenta_envio, /* ç */
MPI_Datatype tipo_envio, /* ç */
void *buffer_recepcion, /* è */
int cuenta_recepcion, /* ç */
MPI_Datatype tipo_recepcion, /* ç */
int raiz, /* ç */
MPI_Comm grupo_com); /* ç */
n Cada proceso del grupo de comunicación grupo_com envía los contenidos de buffer_envio al proceso raiz.
n El proceso raiz concatena los datos recibidos por orden de rango en buffer_recepcion, es decir, los datos del proceso 0, a continuación los del 1, etc.
n El proceso con rango raiz distribuye los contenidos del buffer buffer_envio en tantos segmentos como procesos haya, cada uno con un tamaño de cuenta_envio items.
n Los argumentos de envío son significativos sólo en el proceso raiz.
/* Nuestros datos son de un solo elemento cada uno */ long_bloque[0] = long_bloque[1] = long_bloque[2] = 1; /* Nuestras variables son dos flotantes y un entero */ typelist[0] = typelist[1] = MPI_FLOAT; typelist[2] = MPI_INT;
/* El primer elemento a lo ponemos a desplazamiento 0 */ desplazamientos[0] = 0;
/* Calculamos los otros desplazamientos respecto de a */ MPI_Address(&a, &start_address); MPI_Address(&b, &address); desplazamientos[1] = address - start_address; MPI_Address(&n, &address); desplazamientos[2] = address - start_address;
/* Construimos el tipo derivado */ MPI_Type_struct(3, long_bloque, desplazamientos, lista_de_tipos, &nuevo_tipo); /* Informamos al sistema del nuevo tipo de datos */ MPI_Type_commit(&nuevo_tipo);
n Mediante este mecanismo, el sistema realiza unos cambios internos en la representación de nuevo_tipo para mejorar el rendimiento.
n Estos cambios no son necesarios si nuevo_tipo es usado solo como paso intermedio para construir un tipo más complicado. Por eso esta función va aparte.
n Otro constructor: MPI_Type_vector(int cuenta, /* ç */
int long_bloque, /* ç */
int espaciado, /* ç */
MPI_Datatype tipo_elem, /* ç */
MPI_Datatype *tipo_nuevo); /* è */
n El nuevo tipo consiste en elementos igualmente espaciados de un vector.
n El parámetro cuenta es el número de elementos del nuevo tipo, long_bloque es el número de entradas en cada elemento, espaciado es el número de elementos de tipo tipo_elem entre sucesivos elementos del tipo tipo_nuevo.
/* Integración de un sistema por el algoritmo de Gauss-Seidel. Algoritmo: 1. Se realiza una división por bloques. El número de procesos ha de ser un divisor de la constante MATRIZ.
2. Cada proceso inicializa su bloque. A cada bloque se le añaden dos filas “fantasma” para intercambiar datos con los procesos asociados a los bloques superior e inferior.
3a. Cada proceso realiza el cómputo del promedio, intercambiando los resultados con sus vecinos.
3b. El proceso 0 “reduce” las diferencias de todos, para ver si se ha de terminar. En tal caso, difunde el valor done=0 a todos los demás.
4. Cada proceso imprime su bloque en un fichero. */
main(int argc, char *argv[]) { float *myA = NULL; /* Matriz en este proceso */ int pid = 0; /* Rango del proceso */
int nprocs = 0; /* Número de procesos usados */ MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &pid); MPI_Comm_size(MPI_COMM_WORLD, &nprocs); myA = (float *)malloc(sizeof(float)*(2 + MATRIZ)*(2 + MATRIZ/nprocs));
Inicializa(myA, pid, nprocs); /* Inicializamos la matriz */ MPI_Barrier(MPI_COMM_WORLD); /* Espera que se inicialicen todos */ Resuelve(myA, pid, nprocs); /* Resolvemos */ MPI_Barrier(MPI_COMM_WORLD); /* Espera que acaben todos */ PrintData(myA, pid, nprocs); /* Imprimimos los resultados en ficheros */ MPI_Finalize();