Departamento de Automática Arquitectura e Ingeniería de Computadores Tema 7 Programación paralela de paso de mensajes Prof. Dr. José Antonio de Frutos Redondo Dr. Raúl Durán Díaz Curso 2010-2011 Arquitectura e Ingeniería de Computadores Programación con MPI Introducción y repaso MPI mediante un ejemplo Funciones MPI elementales Comunicación colectiva Agrupación de datos para comunicaciones La entrada/salida © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 2 Arquitectura e Ingeniería de Computadores Modelo de paso de mensajes Recordemos: Desde el punto de vista hardware, estas máquinas: Desde el punto de vista del modelo de programación: Están típicamente construidas por medio de computadores completos (procesador + memoria), incluyendo E/S. Interconexión entre ellos por medio de redes (estáticas o dinámicas). Comunicación por medio de operaciones explícitas de E/S. Acceso directo sólo a direcciones privadas (memoria local). Comunicación por intercambio de mensajes. Existe intervención apreciable del sistema operativo. Normalmente se programa por intermedio de librerías. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 3 Arquitectura e Ingeniería de Computadores MPI MPI significa “Message Passing Interface”. Es un conjunto de funciones en C (o subrutinas en FORTRAN) con las que se puede implementar un programa usando paso de mensajes. Nosotros usaremos C. MPI permite coordinar la ejecución del programa en múltiples procesadores con memoria distribuida. MPI está normalizada: Cualquier programa escrito con esta librería funcionará sobre cualquier máquina en que MPI esté instalado. La normalización ha sido realizada por un equipo independiente con participantes de la industria, de la universidad y laboratorios de investigación. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 4 Arquitectura e Ingeniería de Computadores MPI En el comienzo de todo programa debemos especificar: # include “mpi.h” Todas las funciones y constantes de MPI comienzan con el prefijo MPI_. Con ello, disponemos de las funciones y constantes definidas en la librería. En el caso de las funciones sigue una letra mayúscula y las demás minúsculas: MPI_Init. En el caso de constantes, son todas mayúsculas: MPI_CHAR. Todas las funciones devuelven un entero int salvo que se diga lo contrario. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 5 Arquitectura e Ingeniería de Computadores MPI Antes de comenzar a usar la librería debemos llamar a la función MPI_Init. Sólo debe hacerse una vez. 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 */ ... } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 6 Arquitectura e Ingeniería de Computadores MPI Llamamos grupo de comunicación (communicator) a una familia de procesos que tienen permitido el intercambio de mensajes. Existe un grupo de comunicación por defecto, llamado MPI_COMM_WORLD. En él se encuentran todos los procesos en el momento de arranque. Dentro de cada grupo de comunicación, cada proceso se identifica por su rango (un identificador numérico) que se puede obtener con MPI_Comm_rank. El número total de procesos en un grupo de comunicación se puede obtener con MPI_Comm_size. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 7 Arquitectura e Ingeniería de Computadores MPI Para enviar y recibir mensajes, utilizamos las funciones básicas: MPI_Send MPI_Recv Los mensajes pueden sólo intercambiarse dentro del mismo grupo de comunicación. Cada mensaje enviado lleva una etiqueta identificadora (tag) que es única dentro de cada grupo de comunicación. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 8 Arquitectura e Ingeniería de Computadores Un ejemplo sencillo de MPI Antes de poder ejecutar el ejemplo, cada máquina debe tener acceso a una copia. O bien, todas ellas tienen acceso a la misma copia (disco compartido). ¿Qué ocurre cuando lanzamos una ejecución? Cada máquina comienza la ejecución de su copia. Cada máquina realiza una ejecución independiente. Cada proceso puede ejecutar distintas zonas del programa si la lógica de éste depende de la identificación (rango) del proceso. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 9 Arquitectura e Ingeniería de Computadores Un ejemplo sencillo de 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; int p; int fuente; int dest; int tag = 0; char mensaje[256]; MPI_Status status; © J. A. de Frutos Redondo, R. Durán 2005 V1.4 /* /* /* /* /* /* /* rango de este proceso número total de procesos rango del remitente rango del destinatario etiqueta para el mensaje buffer para el mensaje status de return */ */ */ */ */ */ */ 7. Programación paralela de paso de mensajes 10 Un ejemplo sencillo de MPI Arquitectura e Ingeniería de Computadores 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, MPI_COMM_WORLD); } else { /* mi_rango == 0 */ for (fuente = 1; fuente < p; fuente++) { MPI_Recv(mensaje, sizeof(mensaje), MPI_CHAR, fuente, tag, MPI_COMM_WORLD, &status); printf("%s\n", mensaje); } } MPI_Finalize(); /* Finalizar MPI */ } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 11 Arquitectura e Ingeniería de Computadores Funciones elementales MPI La función de MPI_Send tiene el siguiente prototipo: MPI_Send(void *mensaje, int cuenta, MPI_Datatype tipodato, int destino, int etiqueta, MPI_Comm grupo_com); Í Í Í Í Í Í */ */ */ */ */ */ Los tipos de datos son, por ejemplo: MPI_CHAR MPI_SHORT MPI_FLOAT ..... © J. A. de Frutos Redondo, R. Durán 2005 V1.4 /* /* /* /* /* /* signed char signed short int float 7. Programación paralela de paso de mensajes 12 Arquitectura e Ingeniería de Computadores Funciones elementales MPI 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); /* /* /* /* /* /* /* Î Í Í Í Í Í Î */ */ */ */ */ */ */ Atención: el parámetro cuenta especifica el número máximo de elementos de tipo tipodato que caben en el buffer mensaje. Si se envía un mensaje más largo, se produce un error de desbordamiento. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 13 Arquitectura e Ingeniería de Computadores Funciones elementales MPI La variable de tipo MPI_Status tiene, al menos, los siguientes campos: MPI_SOURCE MPI_TAG MPI_ERROR También contiene información acerca del tamaño del mensaje recibido, que se obtiene mediante la función: MPI_Get_count(MPI_Status *status, MPI_Datatype datatype, int *cuenta); Atención: cuenta obtendrá el número de elementos de tipo datatype (y no el número de bytes). © J. A. de Frutos Redondo, R. Durán 2005 V1.4 /* Í */ /* Í */ /* Î */ 7. Programación paralela de paso de mensajes 14 Arquitectura e Ingeniería de Computadores Funciones elementales MPI Hemos usado también las funciones MPI_Comm_rank y MPI_Comm_size: MPI_Comm_rank(MPI_Comm int Como ya veremos, el comunicador es una estructura que, dicho de modo sencillo, agrupa los procesos que pueden intercambiar mensajes. comunicador, *tamanyo); /* Í */ /* Î */ Devuelve en la variable tamanyo el número de procesos enganchados al comunicador comunicador. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 /* Í */ /* Î */ Devuelve al proceso que la llama (en la variable rango) su rango dentro del comunicador comunicador. MPI_Comm_size(MPI_Comm int comunicador, *rango); 7. Programación paralela de paso de mensajes 15 Arquitectura e Ingeniería de Computadores Ejemplo más completo de MPI #include <stdlib.h> #include “mpi.h” #define ETIQ_TRAB 1 #define ETIQ_MUERTE 2 /* Tipos de datos locales */ typedef int unidad_de_trabajo_t; typedef double unidad_resultado_t; /* Funciones locales */ static static static static void maestro(void); void esclavo(void); unidad_de_trabajo_t siguiente_item(void); unidad_resultado_t trabaja(unidad_de_trabajo_t trabajo); © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 16 Arquitectura e Ingeniería de Computadores Ejemplo más completo de MPI int main(int argc, char **argv) { int mirango; /* Inicializar MPI */ MPI_Init(&argc, &argv); /* Mi identidad en el grupo de comunicación estándar */ MPI_Comm_rank(MPI_COMM_WORLD, &mirango); if (mirango == 0) { maestro(); /* Soy el capataz */ } else { esclavo(); /* Soy un obrero */ } /* Cerrar MPI */ MPI_Finalize(); return 0; } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 17 Arquitectura e Ingeniería de Computadores Ejemplo más completo de MPI static void maestro(void) { int tareas, rango; unidad_de_trabajo_t trabajo; unidad_resultado_t resultado; MPI_Status status; /* Ver cuántos procesos hay en el grupo de comunicación */ MPI_Comm_size(MPI_COMM_WORLD, &tareas); /* Enviar a cada obrero una unidad de trabajo */ for (rango = 1; rango < tareas; ++rango) { /* Buscar el siguiente item en la cola de trabajos */ trabajo = siguiente_item(); /* Enviárselo al obrero que toque */ MPI_Send(&trabajo, /* buffer del mensaje */ 1, /* un item de datos */ MPI_INT, /* el item es un entero */ rango, /* rango del proceso destino */ ETIQ_TRAB, /* etiqueta del mensaje */ MPI_COMM_WORLD); /* grupo de trabajo (es el predefinido) */ } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 18 Arquitectura e Ingeniería de Computadores Ejemplo más completo de MPI /* Bucle para ir recogiendo trabajo hecho y enviando más, hasta que se finalicen las tareas */ trabajo = siguiente_item(); while (trabajo != NULL) { /* Recoger trabajo del obrero */ MPI_Recv(&resultado, /* buffer del mensaje */ 1, /* un item de datos */ MPI_DOUBLE, /* el item es un real doble */ MPI_ANY_SOURCE, /* recibir de cualquier remitente */ MPI_ANY_TAG, /* cualquier tipo de mensaje */ MPI_COMM_WORLD, /* grupo de trabajo (es el predefinido) */ &status); /* info del mensaje recibido */ /* Enviar más trabajo */ MPI_Send(&trabajo, /* buffer del mensaje */ 1, /* un item de datos */ MPI_INT, /* el dato es un entero */ status.MPI_SOURCE, /* devolver al que nos lo acaba de enviar */ ETIQ_TRAB, /* etiqueta del mensaje */ MPI_COMM_WORLD); /* grupo de trabajo (es el predefinido) */ /* Buscar el siguiente item en la cola de trabajos */ trabajo = siguiente_item(); } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 19 Arquitectura e Ingeniería de Computadores Ejemplo más completo de MPI /* No hay más tareas. Falta sólo recibir los trabajos que todavía estén pendientes. */ for (rango = 1; rango < tareas; ++rango) { MPI_Recv(&resultado, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status); } /* Ordenar a los obreros que se cancelen, enviándoles un mensaje con la etiqueta ETIQ_MUERTE. */ for (rango = 1; rango < tareas; ++rango) { MPI_Send(0, 0, MPI_INT, rango, ETIQ_MUERTE, MPI_COMM_WORLD); } } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 20 Arquitectura e Ingeniería de Computadores Ejemplo más completo de MPI static void esclavo(void) { unidad_de_trabajo_t trabajo; unidad_resultado_t resultado; MPI_Status status; while (1) { /* Recibir una orden de trabajo del capataz */ MPI_Recv(&trabajo, 1, MPI_INT, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status); /* Comprobar tipo de orden. Terminar... */ if (status.MPI_TAG == ETIQ_MUERTE) { return; } /* o realizar la tarea, y... */ resultado = trabaja(trabajo); /* devolver el resultado. */ MPI_Send(&resultado, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD); } } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 21 Arquitectura e Ingeniería de Computadores Ejemplo más completo de MPI static unidad_de_trabajo_t siguiente_item(void) { /* Código para obtener un nuevo item de la cola de trabajos */ } static unidad_resultado_t trabaja(unidad_de_trabajo_t trabajo) { /* Código para realizar el trabajo y obtener el resultado */ } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 22 Arquitectura e Ingeniería de Computadores Comunicación colectiva En este programa ejemplo la comunicación entre los procesos es lenta: Todos los obreros comienzan simultáneamente, más o menos. El capataz ha de ir distribuyendo el trabajo a los obreros, que han de hacer cola para que les llegue el turno de recibir su tarea. El último ha de esperar a todos los anteriores. Si hay p procesos, el último ha de esperar un tiempo O(p). Esto es indeseable: queremos que todos dispongan de trabajo cuanto antes. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 23 Arquitectura e Ingeniería de Computadores Comunicación colectiva Se puede mejorar la comunicación organizando los procesos en forma de árbol y haciendo que todos participen en la distribución. Por ejemplo, podríamos subdividir así el trabajo: 0 0 1 0 0 2 4 2 © J. A. de Frutos Redondo, R. Durán 2005 V1.4 1 6 1 3 5 3 7 7. Programación paralela de paso de mensajes 24 Arquitectura e Ingeniería de Computadores Comunicación colectiva Así pues, para implementar este código necesitamos saber: si un proceso ha de recibir y de quién; si un proceso ha de enviar y a quién. Esto puede ser complicado si no hay una forma canónica de hacerlo. El ejemplo anterior es simplemente una de las posibilidades. Sin un conocimiento de la topología del sistema, no se puede saber qué esquema de comunicación es mejor. Un paso de mensajes que involucra a todos los procesos de un grupo de comunicación se llama comunicación colectiva. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 25 Arquitectura e Ingeniería de Computadores Comunicación colectiva Para resolver este problema, MPI proporciona en cada sistema una función que permite difundir una información a todos los procesos de una forma óptima. MPI sólo especifica la interfaz y el resultado de llamarla. La implementación se deja al constructor de la librería para un entorno determinado. La función que realiza esta misión se llama MPI_Bcast y tiene el siguiente prototipo: MPI_Bcast(void *mensaje, int cuenta, MPI_Datatype tipodato, int raiz, MPI_Comm grupo_com); © J. A. de Frutos Redondo, R. Durán 2005 V1.4 /* /* /* /* /* ÍÎ */ Í */ Í */ Í */ Í */ 7. Programación paralela de paso de mensajes 26 Arquitectura e Ingeniería de Computadores Comunicación colectiva La llamada a la función MPI_Bcast puede resultar en recepción de datos, si el rango del proceso es distinto de raiz. envío de datos, si el rango del proceso es igual a raiz. El valor de tipodato y cuenta ha de ser igual para todos. Los procesos afectados serán todos los que se encuentren en el grupo de comunicaciones grupo_com. 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. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 27 Arquitectura e Ingeniería de Computadores Ejemplo de integración con trapecios /* 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); © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 28 Arquitectura e Ingeniería de Computadores Ejemplo de integración con trapecios 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 */ © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 29 Arquitectura e Ingeniería de Computadores Ejemplo de integración con trapecios h = (b-a)/n; local_n = n/p; /* h es el mismo para todos los procesos */ /* 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); © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 30 Ejemplo de integración con trapecios Arquitectura e Ingeniería de Computadores 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); } if (mi_rango == 0) { /* Imprimo resultados */ printf(“Con n = %d trapecios, la integral de f(x) ", n); printf(“desde %f a %f = %f\n", a, b, total); } /* Cerrar MPI */ MPI_Finalize(); } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 31 Arquitectura e Ingeniería de Computadores Ejemplo de integración con trapecios float Trap(float float int float float float int float local_a, local_b, local_n, h) { /* /* /* /* Í Í Í Í */ */ */ */ integral; /* Para almacenar el resultado x; i; f(float x); /* Función a integrar */ */ integral = (f(local_a) + f(local_b))/2.0; x = local_a; for (i = 1; i <= local_n-1; i++) { x = x + h; integral = integral + f(x); } integral = integral*h; return integral; } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 32 Arquitectura e Ingeniería de Computadores Ejemplo de integración con trapecios /* Función que vamos a integrar. Metemos el código que corresponda a la función que deseamos integrar */ float f(float x) { float return_val; /* Calculamos f(x) y lo almacenamos en return_val */ ... return return_val; } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 33 Arquitectura e Ingeniería de Computadores Comunicación colectiva El programa de la integración con trapecios tiene dos fases: Cálculo de las integrales parciales Suma de los resultados La primera fase está equilibradamente distribuida entre los distintos procesos. La fase de suma la realiza exclusivamente el proceso 0. Podríamos redistribuir el trabajo de suma entre los distintos procesos para equilibrar este trabajo. Se podría usar un diagrama en árbol como antes. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 34 Arquitectura e Ingeniería de Computadores Comunicación colectiva 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); Í Î Í Í Í Í Í */ */ */ */ */ */ */ 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. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 /* /* /* /* /* /* /* 7. Programación paralela de paso de mensajes 35 Arquitectura e Ingeniería de Computadores Comunicación colectiva 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. El argumento operacion puede valer MPI_SUM MPI_PROD MPI_MAX MPI_MIN ... 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. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 36 Arquitectura e Ingeniería de Computadores Comunicación colectiva 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); } por MPI_Reduce(&integral, &total, 1, MPI_FLOAT, MPI_SUM, 0, MPI_COMM_WORLD); © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 37 Arquitectura e Ingeniería de Computadores Comunicación colectiva En algunos casos nos interesa que la reducción se efectúe 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); Í Î Í Í Í Í */ */ */ */ */ */ 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. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 /* /* /* /* /* /* 7. Programación paralela de paso de mensajes 38 Arquitectura e Ingeniería de Computadores Comunicación colectiva Las barreras están directamente implementadas en MPI: MPI_Barrier(MPI_Comm Cada proceso del grupo de comunicación grupo_com se quedará bloqueado hasta que todos ellos hayan llamado a esta función. Cuando el último de ellos la llame, todos se desbloquean simultáneamente. Es un mecanismo de sincronización. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 grupo_com); /* Í */ 7. Programación paralela de paso de mensajes 39 Arquitectura e Ingeniería de Computadores Comunicación colectiva 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); Í Í Í Î Í Í Í Í */ */ */ */ */ */ */ */ Cada proceso del grupo de comunicación grupo_com envía los contenidos de buffer_envio al proceso raiz. 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. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 /* /* /* /* /* /* /* /* 7. Programación paralela de paso de mensajes 40 Arquitectura e Ingeniería de Computadores Comunicación colectiva Los argumentos de recepción sólo son significativos en el proceso raiz. El argumento cuenta_recepcion indica el número de items recibidos de cada proceso (no el total). © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 41 Comunicación colectiva Arquitectura e Ingeniería de Computadores MPI_Scatter(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); Í Í Í Î Í Í Í Í */ */ */ */ */ */ */ */ 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. Los argumentos de envío son significativos sólo en el proceso raiz. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 /* /* /* /* /* /* /* /* 7. Programación paralela de paso de mensajes 42 Comunicación colectiva Arquitectura e Ingeniería de Computadores MPI_Allgather(void *buffer_envio, int cuenta_envio, MPI_Datatype tipo_envio, void *buffer_recepcion, int cuenta_recepcion, MPI_Datatype tipo_recepcion, MPI_Comm grupo_com); Í Í Í Î Í Í Í */ */ */ */ */ */ */ Cada proceso del grupo de comunicación grupo_com envía los contenidos de buffer_envio a todos los demás procesos. El efecto es equivalente a llamar a MPI_Gather tantas veces como procesos haya, actuando sucesivamente cada uno de ellos como raiz. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 /* /* /* /* /* /* /* 7. Programación paralela de paso de mensajes 43 Arquitectura e Ingeniería de Computadores Agrupación de datos Enviar mensajes es una operación costosa. Hay que tratar de enviar el mínimo número posible. La solución obvia es agrupar los mensajes. Existen tres mecanismos para agrupar mensajes: Parámetro cuenta. Tipos de datos derivados. Las rutinas MPI_Pack/MPI_Unpack. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 44 Arquitectura e Ingeniería de Computadores Agrupación de datos Recordemos que las funciones MPI_Send, MPI_Receive, MPI_Bcast, MPI_Reduce tienen todas el parámetro cuenta y el parámetro tipodato. Con ellos, el usuario puede agrupar datos que sean del mismo tipo básico en un solo mensaje. Condición imprescindible: los datos han de estar almacenados en memoria contigua. Esto es útil para los vectores o matrices. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 45 Arquitectura e Ingeniería de Computadores Agrupación de datos Ejemplo: enviar la segunda mitad de un vector de 100 flotantes desde el proceso 0 al 1. float vector[100]; MPI_Status status; int p; int mi_rango; ... /* Inicializar vector y enviar */ if (mi_rango == 0) { ... MPI_Send(vector+50, 50, MPI_FLOAT, 1, 0, MPI_COMM_WORLD); } else if (mi_rango == 1) { MPI_Recv(vector+50, 50, MPI_FLOAT, 0, 0, MPI_COMM_WORLD, &status); ... } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 46 Arquitectura e Ingeniería de Computadores Agrupación de datos Si los datos no son de igual tipo, hemos de construir tipos derivados a partir de los primitivos. Un tipo MPI derivado es una sucesión de n pares {(t0, d0), (t1, d1), ..., (tn−1,dn−1)} donde cada ti es un tipo básico y cada di es un desplazamiento en bytes. Un ejemplo podría ser: {(MPI_FLOAT, 0),(MPI_FLOAT, 16),(MPI_INT, 24)} © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 47 Arquitectura e Ingeniería de Computadores Agrupación de datos La función para construir un tipo nuevo es: MPI_Type_struct(int int MPI_Aint MPI_Datatype MPI_Datatype /* /* /* /* /* Í Í Í Í Î */ */ */ */ */ El parámetro cuenta es el número de elementos del tipo derivado y el tamaño de los vectores long_bloque, desplazamiento y lista_de_tipos. El vector lista_de_tipos contiene el tipo de dato MPI para cada entrada. El vector desplazamiento contiene el desplazamiento con respecto al comienzo del mensaje de cada entrada. El vector long_bloque indica cuántos elementos de cada tipo hay en cada entrada. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 cuenta, long_bloque[], desplazamiento[], lista_de_tipos[], *nuevo_tipo); 7. Programación paralela de paso de mensajes 48 Arquitectura e Ingeniería de Computadores Agrupación de datos Ejemplo de construir un nuevo tipo. Queremos construir un mensaje con las variables float a; float b; int n; int long_bloque[3]; MPI_Aint desplazamientos[3]; MPI_Datatype lista_de_tipos[3]; /* Para los cálculos de direcciones */ MPI_Aint start_address; MPI_Aint address; /* Nuevo tipo */ MPI_Datatype nuevo_tipo; © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 49 Arquitectura e Ingeniería de Computadores Agrupación de datos /* 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 */ lista_de_tipos[0] = lista_de_tipos[1] = MPI_FLOAT; lista_de_tipos[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; © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 50 Agrupación de datos Arquitectura e Ingeniería de Computadores /* 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); © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 51 Arquitectura e Ingeniería de Computadores Agrupación de datos Observemos dos funciones que hemos introducido: MPI_Address(void *variable, /* Í */ MPI_Aint *direccion); /* Î */ En principio es equivalente a la sentencia: direccion = &variable; pero nos permite asegurar la portabilidad. MPI_Type_commit(MPI_Datatype *nuevo_tipo); /* ÍÎ */ Mediante este mecanismo, el sistema realiza unos cambios internos en la representación de nuevo_tipo para mejorar el rendimiento. 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. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 52 Arquitectura e Ingeniería de Computadores Agrupación de datos Tenemos otros constructores más sencillos: MPI_Type_contiguous(int cuenta, MPI_Datatype tipo_viejo, MPI_Datatype *tipo_nuevo); El nuevo tipo consiste en cuenta elementos contiguos de un vector de elementos de tipo tipo_viejo. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 /* Í */ /* Í */ /* Î */ 7. Programación paralela de paso de mensajes 53 Arquitectura e Ingeniería de Computadores Agrupación de datos Ejemplo: queremos mandar una fila una matriz de flotantes de tamaño 10×10: float A[10][10]; MPI_Datatype tipo_fila_10; ... MPI_Type_contiguous(10, MPI_FLOAT, &tipo_fila_10); MPI_Type_commit(&tipo_fila_10); if (mi_rango == 0) { MPI_Send(&(A[2][0]), 1, tipo_fila_10, 1, 0, MPI_COMM_WORLD); } else { /* mi_rango == 1 */ MPI_Recv(&(A[2][0]), 1, tipo_fila_10, 0, 0, MPI_COMM_WORLD, &status); } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 54 Arquitectura e Ingeniería de Computadores Agrupación de datos Otro constructor: MPI_Type_vector(int int int MPI_Datatype MPI_Datatype /* /* /* /* /* Í Í Í Í Î */ */ */ */ */ El nuevo tipo consiste en elementos igualmente espaciados de un vector. 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. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 cuenta, long_bloque, espaciado, tipo_elem, *tipo_nuevo); 7. Programación paralela de paso de mensajes 55 Arquitectura e Ingeniería de Computadores Agrupación de datos Ejemplo: queremos mandar una columna de una matriz de flotantes de tamaño 10×10: float A[10][10]; MPI_Datatype tipo_columna_10; ... MPI_Type_vector(10, 1, 10, MPI_FLOAT, &tipo_columna_10); MPI_Type_commit(&tipo_columna_10); if (mi_rango == 0) { MPI_Send(&(A[0][2]), 1, tipo_columna_10, 1, 0, MPI_COMM_WORLD); } else { /* mi_rango == 1 */ MPI_Recv(&(A[0][2]), 1, tipo_columna_10, 0, 0, MPI_COMM_WORLD, &status); } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 56 Arquitectura e Ingeniería de Computadores Agrupación de datos Otro constructor más: MPI_Type_indexed(int int int MPI_Datatype MPI_Datatype /* /* /* /* /* Í Í Í Í Î */ */ */ */ */ El nuevo tipo consiste en cuenta elementos de tipo tipo_elem. El i-ésimo elemento consta de long_bloques[i] entradas y está desplazado desplazamientos[i] elementos de tipo tipo_elem respecto del principio. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 cuenta, long_bloques[], desplazamientos[], tipo_elem, *tipo_nuevo); 7. Programación paralela de paso de mensajes 57 Arquitectura e Ingeniería de Computadores Agrupación de datos Ejemplo: queremos mandar la porción triangular superior de una matriz de n×n elementos: float A[n][n]; /* Matriz completa */ float T[n][n]; /* Triángulo superior */ int desplazamientos[n]; int long_bloques[n]; MPI_Datatype tipo_triang; ... for (i = 0; i < n; i++) { long_bloques[i] = n - i; desplazamientos[i] = (n + 1)*i; } MPI_Type_indexed(n, long_bloques, desplazamientos, MPI_FLOAT, &tipo_triang); MPI_Type_commit(&tipo_triang); /* ... Se envía como antes ... */ © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 58 Arquitectura e Ingeniería de Computadores Agrupación de datos ¿Cómo se realiza la correspondencia entre un tipo de dato enviado y uno recibido? Recordemos que un tipo derivado se define como una sucesión de n pares {(t0, d0), (t1, d1), ..., (tn−1,dn−1)}. La sucesión de tipos {t0, t1,..., tn−1} es la firma del tipo. La regla es que la firma de los tipos que se envía y se recibe sea compatible. Si la firma del tipo enviado es {t0, t1,..., tn−1} y la del recibido {u0, u1,..., um−1} se debe verificar que n≤m ti = ui, para i = 0, …, n − 1 © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 59 Arquitectura e Ingeniería de Computadores Agrupación de datos Por último, podemos empaquetar los datos explícitamente y desempaquetarlos al recibir. Tenemos: MPI_Pack(void /* Í */ *paquete, int cuenta, MPI_Datatype tipo_dato, void *buffer, int tamanyo_buffer, int *posicion, MPI_Comm grupo_com); MPI_Unpack(void V1.4 Í */ Í */ Î */ Í */ ÍÎ */ Í */ /* Í */ *buffer, int tamanyo_buffer, int *posicion, void *desempaquetado, int cuenta, MPI_Datatype tipo_dato, MPI_Comm grupo_com); © J. A. de Frutos Redondo, R. Durán 2005 /* /* /* /* /* /* /* /* /* /* /* /* Í */ ÍÎ */ Î */ Í */ Í */ Í */ 7. Programación paralela de paso de mensajes 60 Arquitectura e Ingeniería de Computadores Agrupación de datos Veamos con un ejemplo su uso: float a; float b; int n; char buffer[100]; int posicion = 0; if (mi_rango == 0) { MPI_Pack(&a, 1, MPI_FLOAT, buffer, 100, &posicion, MPI_COMM_WORLD); MPI_Pack(&b, 1, MPI_FLOAT, buffer, 100, &posicion, MPI_COMM_WORLD); /* /* /* /* /* /* posicion se incrementa hasta la siguiente posición libre del buffer. posicion se incrementa de nuevo. MPI_Pack(n, 1, MPI_INT, buffer, 100, &posicion, MPI_COMM_WORLD); © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 61 */ */ */ */ */ */ Arquitectura e Ingeniería de Computadores Agrupación de datos ... /* Ahora difundimos el contenido del buffer */ MPI_Bcast(buffer, 100, MPI_PACKED, 0, MPI_COMM_WORLD); } else { MPI_Bcast(buffer, 100, MPI_PACKED, 0, MPI_COMM_WORLD); /* Desempaquetamos */ MPI_Unpack(buffer, 100, &posicion, &a, 1, /* posicion se in*/ MPI_FLOAT, MPI_COMM_WORLD); /* menta también de */ /* manera automática */ MPI_Unpack(buffer, 100, &posicion, &b, 1, MPI_FLOAT, MPI_COMM_WORLD); MPI_Unpack(buffer, 100, &position, &n, 1, MPI_INT, MPI_COMM_WORLD); } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 62 Arquitectura e Ingeniería de Computadores La entrada/salida Muchos procesos se están ejecutando simultáneamente, es necesario decidir quién efectúa la escritura en la salida estándar y quién la lectura de la entrada estándar. La solución más típica es que sólo un proceso se ocupe de ello y reciba de los demás lo que debe ser escrito y envíe a los demás lo que ha leído. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 63 Arquitectura e Ingeniería de Computadores La entrada/salida Las funciones normales de C para entrada/salida (printf, scanf) no están pensadas para el paralelismo. No existe un consenso claro acerca de cómo debe funcionar la entrada/salida en sistemas multiproceso. MPI no normaliza nada acerca de la entrada/salida. No obstante las implementaciones de MPI suelen proporcionar algún tipo de soporte. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 64 Arquitectura e Ingeniería de Computadores La entrada/salida Normalmente, todas las implementaciones de MPI: En el caso más sencillo, convertimos las funciones de E/S en operaciones colectivas, utilizando un proceso designado como “proceso de E/S”. Permiten que al menos un proceso pueda acceder a la entrada y salida estándar y a la salida de error. Para salida, el proceso de E/S recogerá los datos de los demás y los imprimirá. Para entrada, el proceso de E/S leerá los datos necesarios y los difundirá a los demás procesos. Así pues, cada comunicador contendrá un proceso designado para la E/S. © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 65 Arquitectura e Ingeniería de Computadores Ejemplo del algoritmo de Gauss-Seidel /* 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. */ © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 66 Arquitectura e Ingeniería de Computadores Ejemplo del algoritmo de Gauss-Seidel # # # # include include include include # # # # # # # # # define define define define define define define define define <stdlib.h> <stdio.h> <math.h> "mpi.h" MATRIZ LAMBDA LADO_I LADO_D ARRIBA ABAJO INTERNO FILA TOL (128) 0.2 30 30 50 50 15 1 0.001 © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 67 Arquitectura e Ingeniería de Computadores Ejemplo del algoritmo de Gauss-Seidel void Resuelve(float myA[][MATRIZ+2], int pid, int nprocs) { int i,j, n1 = 2 + MATRIZ, n2 = 2 + MATRIZ/nprocs, done = 0; float temp, diff, mydiff = 0; MPI_Status status; while (!done) { mydiff = 0; if (pid != 0) MPI_Send(&myA[1][0], n1, MPI_FLOAT, pid - 1, if (pid != nprocs-1) MPI_Send(&myA[n2 - 2][0], n1, MPI_FLOAT, pid MPI_COMM_WORLD); if (pid != 0) MPI_Recv(&myA[0][0], n1, MPI_FLOAT, pid - 1, MPI_COMM_WORLD, &status); if (pid != nprocs-1) MPI_Recv(&myA[n2 - 1][0], n1, MPI_FLOAT, pid MPI_COMM_WORLD, &status); © J. A. de Frutos Redondo, R. Durán 2005 V1.4 FILA, MPI_COMM_WORLD); + 1, FILA, FILA, + 1, FILA, 7. Programación paralela de paso de mensajes 68 Ejemplo del algoritmo de Gauss-Seidel Arquitectura e Ingeniería de Computadores for (i = 1; i < n2 - 1; i++) { for (j = 1; j < n1 - 1; j++) { temp = myA[i][j]; myA[i][j] = LAMBDA*(myA[i][j] + myA[i][j-1] + myA[i-1][j] + myA[i][j+1] + myA[i+1][j]); mydiff += fabs(myA[i][j] - temp); } } MPI_Reduce(&mydiff, &diff, 1, MPI_FLOAT, MPI_SUM, 0, MPI_COMM_WORLD); if (pid == 0) { if (diff/(MATRIZ*MATRIZ) < TOL) { done = 1; } } MPI_Bcast(&done, 1, MPI_INT, 0, MPI_COMM_WORLD); } } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 69 Arquitectura e Ingeniería de Computadores Ejemplo del algoritmo de Gauss-Seidel void Inicializa(float myA[][MATRIZ+2], int pid, int nprocs) { int i,j; int n1 = 2 + MATRIZ, n2 = 2 + MATRIZ/nprocs; for (i = 0; i < n2; i++) { for (j = 0; j < n1; j++) myA[i][j] = INTERNO; } for (i = 0; i < n2; i++) { myA[i][0] = LADO_I; myA[i][n1 - 1] = LADO_D; } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 70 Arquitectura e Ingeniería de Computadores Ejemplo del algoritmo de Gauss-Seidel if (pid == 0) { for (j = 0; j < n1; j++) { myA[0][j] = ARRIBA; } } if (pid == nprocs - 1) { for (j = 0; j < n1; j++) { myA[n2 - 1][j] = ABAJO; } } } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 71 Arquitectura e Ingeniería de Computadores Ejemplo del algoritmo de Gauss-Seidel void PrintData(float myA[][MATRIZ+2], int pid, int nprocs) { int i, j; int n1 = 2 + MATRIZ; int n2 = 2 + MATRIZ/nprocs; FILE *fpc = NULL; char name[256] = ""; sprintf(name, "out%d.dat", pid); if ((fpc = fopen(name, "w")) != NULL) { for (i = 1; i < n2 - 1 ; i++) { for (j = 1; j < n1 - 1; j++) fprintf(fpc, "%.3f ", myA[i][j]); fprintf(fpc, "\n"); } fclose(fpc); } } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 72 Arquitectura e Ingeniería de Computadores Ejemplo del algoritmo de Gauss-Seidel 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(); return 0; } © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 73 Arquitectura e Ingeniería de Computadores Resultado de la ejecución serie Matriz de 32 × 32 puntos © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 74 Arquitectura e Ingeniería de Computadores Resultado usando MPI Matriz de 32 × 32 puntos © J. A. de Frutos Redondo, R. Durán 2005 V1.4 7. Programación paralela de paso de mensajes 75