cd unidad3 con c

49
COMPUTO DISTRIBUIDO Unidad III. INTRODUCCIÓN A LA PROGRAMACIÓN CON MPI Índice de contenido 0. Preparación............................................................................................................................................. 3 1. Introducción a la programación en MPI................................................................................................... 4 1.1 La historia de MPI............................................................................................................................. 4 1.1.1 ¿Qué es MPI?........................................................................................................................... 5 1.1.2 ¿Qué ofrece MPI?.................................................................................................................... 5 1.2. Funciones básicas........................................................................................................................... 5 1.2.1 Tipos de datos predefinidos...................................................................................................... 6 1.2.2 Estructura básica de un programa paralelo.............................................................................. 6 1.3 Programa 1. Hola mundo paralelo. .................................................................................................. 7 2. Comunicación punto a punto bloqueante................................................................................................. 8 2.1 Comunicación punto a punto............................................................................................................ 8 2.2 Comunicación bloqueante................................................................................................................ 9 2.2.1 Envió estándar........................................................................................................................ 10 2.2.2 Envío Síncrono....................................................................................................................... 10 2.2.3 Envío Buferizado.................................................................................................................... 11 2.2.4 Envió Ready........................................................................................................................... 13 2.2.5 Recepción estándar................................................................................................................ 15 3.- Comunicación no bloqueante............................................................................................................... 20 3.1 Comunicación no bloqueante......................................................................................................... 20 3.2 Envío estándar................................................................................................................................ 22 3.3 Envío síncrono................................................................................................................................ 22 3.4 Envío buferizado............................................................................................................................. 23 3.5 Envío Ready................................................................................................................................... 23 3.6 Recepción no bloqueante............................................................................................................... 24 4.-Comunicación colectiva. ....................................................................................................................... 28 4.1 Difusión de datos (Broadcast)......................................................................................................... 29 4.2. Dispersión de datos (Scatter)........................................................................................................ 30 4.4 Reunión en todos (Allgather).......................................................................................................... 33 4.5 todo a todos (all to all)..................................................................................................................... 35 4.6 Reducción (Reduce y Allreduce).................................................................................................... 37 4.7 Topologías virtuales........................................................................................................................ 38 5.- Tipos de datos derivados...................................................................................................................... 44 5.1 Tipos de datos consecutivos........................................................................................................... 44 5.2 Tipo de dato Vector......................................................................................................................... 45 5.3 Tipo de dato Indexed...................................................................................................................... 45 5.4 Tipo de dato Struct.......................................................................................................................... 46 Abelardo Rodríguez León 1

Upload: diego-isla

Post on 04-Jul-2015

141 views

Category:

Documents


5 download

TRANSCRIPT

Page 1: CD Unidad3 Con C

COMPUTO DISTRIBUIDO

Unidad III. INTRODUCCIÓN A LA PROGRAMACIÓN CON MPI

Índice de contenido0. Preparación............................................................................................................................................. 31. Introducción a la programación en MPI...................................................................................................4

1.1 La historia de MPI............................................................................................................................. 41.1.1 ¿Qué es MPI?........................................................................................................................... 51.1.2 ¿Qué ofrece MPI?.................................................................................................................... 5

1.2. Funciones básicas........................................................................................................................... 51.2.1 Tipos de datos predefinidos......................................................................................................61.2.2 Estructura básica de un programa paralelo..............................................................................6

1.3 Programa 1. Hola mundo paralelo. ..................................................................................................72. Comunicación punto a punto bloqueante.................................................................................................8

2.1 Comunicación punto a punto............................................................................................................82.2 Comunicación bloqueante................................................................................................................9

2.2.1 Envió estándar........................................................................................................................102.2.2 Envío Síncrono....................................................................................................................... 102.2.3 Envío Buferizado.................................................................................................................... 112.2.4 Envió Ready...........................................................................................................................132.2.5 Recepción estándar................................................................................................................15

3.- Comunicación no bloqueante...............................................................................................................203.1 Comunicación no bloqueante.........................................................................................................203.2 Envío estándar................................................................................................................................ 223.3 Envío síncrono................................................................................................................................ 223.4 Envío buferizado............................................................................................................................. 233.5 Envío Ready................................................................................................................................... 233.6 Recepción no bloqueante...............................................................................................................24

4.-Comunicación colectiva. .......................................................................................................................284.1 Difusión de datos (Broadcast).........................................................................................................294.2. Dispersión de datos (Scatter)........................................................................................................304.4 Reunión en todos (Allgather)..........................................................................................................334.5 todo a todos (all to all).....................................................................................................................354.6 Reducción (Reduce y Allreduce)....................................................................................................374.7 Topologías virtuales........................................................................................................................38

5.- Tipos de datos derivados......................................................................................................................445.1 Tipos de datos consecutivos...........................................................................................................445.2 Tipo de dato Vector.........................................................................................................................455.3 Tipo de dato Indexed...................................................................................................................... 455.4 Tipo de dato Struct..........................................................................................................................46

Abelardo Rodríguez León 1

Page 2: CD Unidad3 Con C

COMPUTO DISTRIBUIDO

Abelardo Rodríguez León 2

Page 3: CD Unidad3 Con C

0. Preparación.

Para poder probar los programas se instalará una versión de MPI que hace el paso de mensajes por medio de memoria, es decir sin usar la red de interconección.Esto logra simular un cluster en una sola PC, ya que cada instancia del programa paralelo se hace en un proceso concurrente en el mismo procesador.Para poder hacer esto se debe usar linux en algunas de sus distribuciones.Tambien se debe de escoger alguna de las implementaciones de MPIY por último elegir un ambientes de desarrollo de aplicaciones (en linux existen varios).

En lo personal yo uso la siguiente configuración de SW:Linux: Ubuntu 10.04MPI: Mpich

Para facilitar el manejo en PC del Laboratorio de computo que cuentan solo con Windows, se usará para este curso un virtualizador para simular una sección de Ubuntu dentro de WindowsXP.El virtualizar usado en este caso es Vmware. Dicho virtualizador permite exportar una maquina virtual entre diferentes maquinas.

Instalación previa.

Instalar Ubuntu Instalar los siguientes paquetes:

binutils, g++, automake, autoconf, libmpich-shmem1.0-dev libmpich-shmem1.0gf mpich-shmem-bin

Page 4: CD Unidad3 Con C

1. Introducción a la programación en MPI

1.1 La historia de MPI.

El esfuerzo de estandarización para el paso de mensajes involucró aproximadamente a unas 60 personas de 40 organizaciones, principalmente de Estados Unidos y Europa, entre ellas fabricantes, universidades y laboratorios de gobierno.

El proceso de estandarización comenzó con el taller de Estándares para el Paso de Mensajes en un ambiente de memoria distribuida patrocinado por el Centro para la Investigación de Computación Paralela en abril de 1992 en Williamsburg, Virginia. En dicho taller se discutieron las características básicas esenciales para una Interface Estándar de Paso de Mensajes y un grupo de trabajo se estableció para que diera comienzo con el proceso de estandarización.

De abril a agosto de 1992, Dangorra, Hempel, Hey y Walker trabajaron en un prototipo de lo que sería el estándar de Paso de Mensajes al cual llamaron MPI1; pero no fue sino hasta octubre de 1992 en que este primer borrador fue circulado a una lista de correo, vía correo electrónico, para comentarios y críticas.

El segundo borrador de MPI1 fue presentado en noviembre de 1992 en una sesión de Supercomputación '92 en Minneapolis, aunque MPI contenía muchas características deseables no fue adoptado como estándar de Paso de Mensajes, principalmente porque este no contenía un manejo de grupos de procesos y contextos de comunicación seguros.

Así que en enero de 1993 fueron formadas subcomitivas y un foro de discusión para agilizar el proceso de estandarización y definir metas, producir un borrador de MPI para el otoño de 1995. Para llevar a cabo esta meta el grupo de trabajo de MPI se reunió 2 días cada 6 semanas por los primeros 5 meses de 1993, así lograron desarrollar el corazón de MPI, las rutinas de comunicación punto a punto. Después de mas de 3 reuniones pudieron presentar un borrador de MPI en la conferencia de la Supercomputación ‘93 en noviembre del mismo año.

En enero de 1994 se llevó a cabo un taller europeo sobre MPI en el INRIA, campus Sophia Antipolis en Francia; en abril del mismo año termina el periodo de opinión publica. En julio de 1994 es presentada una errata a la especificación MPI.

Al diseñar MPI se ha buscado hacer uso de las más atractivas características de un número de sistemas existentes de paso de mensajes.

MPI ha sido fuertemente influenciado principalmente por el Centro de Investigaciones Watson de IBM, Intel's NX/2, Express, nCUBES's Vertex, p4 y PARMACS. Otras contribuciones importantes han venido de Zipcode, Chimp, PVM, Chameleon y PICL.

A principios de Marzo de 1995 el Forum de MPI comienza a considerar correcciones y extensiones al estándar de MPI el cual genera a MPI-2, esta extensión contiene nuevos tipos de funciones para comunicaciones con un solo procesador, paralelismo en dispositivos de entrada/salida, programación multithreading, etc. y además contiene correcciones de la versión MPI-1. Para el uso de las rutinas de MPI-2 se hace uso del lenguaje C++.

Page 5: CD Unidad3 Con C

1.1.1 ¿Qué es MPI?.

MPI (por sus siglas en inglés) significa Message Passing Interface (Interface de Paso de Mensajes).

En la mayoría de los libros de consulta sobre MPI, éste viene definido como sigue:

• La primera biblioteca de Paso de Mensajes estándar y portable. • Una colección de rutinas que facilitan la comunicación entre procesadores en programas

paralelos.

MPI es una biblioteca fácil de usar y portable que se utiliza para la comunicación entre procesos.

1.1.2 ¿Qué ofrece MPI?.

MPI proporciona muchas características encaminadas a mejorar el rendimiento de nuestros programas. Podemos mencionar como las más importantes la portabilidad, la estandarización, la ejecución paralela e implementaciones eficientes.

Estandarización

MPI se está popularizado como un estándar en la programación paralela. Los programadores no se deben preocupar por la versión del sistema operativo o la máquina en la cual están corriendo, ya que las llamadas MPI se comportan de igual manera a pesar de la implementación que se utiliza.

Portabilidad

Debido a los esfuerzos encaminados a la estandarización, el MPI tiene desarrolladores en muchas plataformas, que basados en los lineamientos descritos desarrollan las librerías que proporcionarán la tan citada portabilidad.

Pontencialidad

MPI proporciona subrutinas para comunicación asíncrona, comunicación colectiva, topologías virtuales, manejo de mensajes vía buffer, grupos de comunicación, entre otras. Lo cual provee de una potencialidad y flexibilidad muy atractiva para el programador.

1.2. Funciones básicas.

Las funciones de MPI pueden utilizarse en ForTran y C. El formato que se utiliza en lenguaje C es la siguiente:

rc = MPI_Xxxxx(parámetros);

Donde rc es una variable tipo entero que retorna el valor de cero o uno según el éxito/fracaso de la función a su termino. Se tiene una llamada a alguna rutina de MPI, la cual deber ser en mayúsculas, como debe ser también el primer carácter después del guión abajo, todo después debe estar en minúsculas.

Page 6: CD Unidad3 Con C

1.2.1 Tipos de datos predefinidos.

Los datos utilizados para la comunicación entre procesadores deben ser del mismo tipo, es decir, tanto la operación de envío como la de recepción deben utilizar el mismo tipo de dato.

En la tabla 1.1 podemos observar los tipos de datos básicos con los que cuenta MPI y su equivalencia en lenguaje C:

Tipos de datos básicos de MPI Equivalencia de datos en lenguaje Csigned char MPI_CHARsigned short intsigned intsigned long intunsigned charunsigned short intunsigned intunsigned long intfloatdoublelong double

MPI_SHORTMPI_INTMPI_LONGMPI_UNSIGNED_CHARMPI_UNSIGNED_SHORTMPI_UNSIGNEDMPI_UNSIGNED_LONGMPI_FLOATMPI_DOUBLEMPI_LONG_DOUBLEMPI_BYTEMPI_PACKED

Tabla 1.1 Tipos de datos predefinidos de MPI.

1.2.2 Estructura básica de un programa paralelo.

En resumen los programas básicos de MPI siguen, al menos, estos pasos generales:

1. Inicializar la comunicación 2. Comunicar para compartir datos entre procesos. 3. Finalizar el ambiente paralelo.

MPI tiene 125 funciones. Sin embargo, un usuario podría comenzar a programar utilizando solamente 6 de ellas.

1. Inicializar la comunicaciónMPI_Init () función que inicializa el ambiente MPI.MPI_Comm_size () retorna el número de procesos.MPI_Comm_rank () retorna el número de cada procesador.

2. Comunicar para compartir datos entre procesosMPI_Send () envía un mensajeMPI_Recv () recibe un mensaje

3. Finalizar el ambiente paralelo.MPI_Finalize() Se finaliza el ambiente paralelo que provee MPI.

Después de esta llamada ninguna rutina (excepto MPI_Init) puede ser usada.

Page 7: CD Unidad3 Con C

Una vez visto las principales funciones que MPI provee, estudiaremos una primera y sencilla implementación: el tradicional "Hola mundo" en su versión paralela.

1.3 Programa 1. Hola mundo paralelo.

/* Programa paralelo que despliega el número de la tarea que se ejecuta */#include <mpi.h> #include <stdio.h>#include <stdlib.h> int main (int argc, char *argv[ ]){ int nprocs ; /* variable que guardará el número de procesadores */

int rango ; /* variable que guardará el rango de cada procesador */ MPI_Init (&argc, &argv); /* Inicializa el ambiente paralelo */ /* Obtener el número de procesadores y el rango de cada uno de ellos */ MPI_Comm_rank (MPI_COMM_WORLD, &rango ); MPI_Comm_size (MPI_COMM_WORLD, &nprocs ); if (nprocs < 2){ printf("El número de procesadores debe ser mayor de uno \n");

exit(1); } else

printf("Hola mundo paralelo desde la tarea %d \n", rango); MPI_Finalize();

}

Impresión en Pantalla:$ mpirun prog holaHola mundo paralelo desde la tarea 1Hola mundo paralelo desde la tarea 2Hola mundo paralelo desde la tarea 3Hola mundo paralelo desde la tarea 0

Page 8: CD Unidad3 Con C

2. Comunicación punto a punto bloqueante.

2.1 Comunicación punto a punto.

Se dice que una comunicación es punto a punto cuando existe un procesador que reciba y otro procesador que envié mensajes, es decir, la transmisión de datos se realiza mediante una comunicación 1-a-1. Podemos mencionar ciertas condiciones que se deben considerar cuando se decide utilizar la comunicación punto a punto:

• Este tipo de comunicación garantiza que cada mensaje dentro de la red de comunicación será recibido en forma secuencial por el procesador receptor.

• Si se envía un mensaje, debe haber un procesador que reciba dicho mensaje. En el caso que alguno de los dos no se complete (send/recv) ocurrirá un error.

• Se debe tener especial cuidado cuando se declaran los tipos de datos. En la operación de envió y en la operación de recepción, la variable encargada de contener el mensaje deberá ser declarada del mismo tipo. Por ejemplo, si un procesador envía un mensaje con un tipo de dato entero (MPI_INTEGER) el procesador receptor esperara un tipo entero.

La comunicación punto a punto requiere de dos funciones que permitan la comunicación dentro de un grupo de procesadores. Las funciones de envío y de recepción. Dentro de la comunicación punto a punto se debe declarar un grupo de procesadores que conformarán un universo (figura 3.1), cada procesador debe ser fácilmente identificable dentro del grupo.

El comunicador MPI_COMM_WORLD identifica a todos los n procesadores que conforman el grupo creado por el usuario. Los procesadores son ordenados y numerados consecutivamente desde el cero (0) hasta n-1, a este número se le conoce como el rango (rank); el cual es usado para identificar al destino o la fuente de un mensaje, figura 2.1.

Fig. 2.1 El universo de procesadores, cada procesador con sunúmero de ordenación correspondiente (rank).

Parámetros de las funciones básicas.

En las funciones de envió y recepción existe parámetros predefinidos por MPI para establecer una comunicación. Sin embargo, la función de envió necesita de un parámetro adicional. Podemos observar en la figura 2.2 que:

Page 9: CD Unidad3 Con C

• El primer argumento es un apuntador al inicio del buffer en donde se almacena de manera consecutiva los datos.

• El segundo argumento nos permite declarar la cantidad de datos que se requieren transferir o recibir.

• En el tercer argumento declara un tipo de dato que corresponde a los datos del buffer. • Dependiendo de la operación que se utilice, este parámetro contendrá el rango del procesador

destino u origen. Este rango depende de los n procesadores y contiene cualquier valor desde 0 hasta n-1.

• Con el parámetro etiqueta se marca el mensaje con un número entero, con él puede distinguirse de los demás, de tal manera que pueda ser fácilmente reconocido por alguna operación receptora. MPI provee la función MPI_ANY_TAG, mediante el cual una operación receptora recibe cualquier mensaje que este en la red.

• El penúltimo argumento identifica los procesadores que están involucrados en la comunicación a esto se le conoce como "comunicador".

• El último elemento es un parámetro adicional que contiene solo la función de recepción, el cual guarda el estado de recepción.

Estado de la recepción.ComunicadorVariable que contiene los datosNum. de datosTipo de datoRango del procesador destino o del origenEtiquetaMPI_COMM_WORLD

(tag)Fig. 2.2 Parámetros de una función de MPI.

Existen dos clasificaciones dentro de la comunicación punto a punto, la comunicación bloqueante y no bloqueante.

2.2 Comunicación bloqueante.

Una comunicación bloqueante se refiere al hecho de que un procesador (emisor) tiene que "esperar" hasta que el procesador receptor complete cierta etapa de su programa para que pueda continuar su ejecución. Un programa con comunicación bloqueante produce una gran cantidad de tiempos "muertos" (idle time en inglés), lo cual repercute en el aprovechamiento de la velocidad y de la utilización al máximo de los procesadores.

En esta sección clasificaremos el modo de comunicación bloqueante de acuerdo a la manera en que los procesadores hacen las operaciones de recepción y de envío.

Los modos de comunicación bloqueante para el envío (send) que proporciona la librería de MPI son:

Envío Función Descripción

Buferizado MPI_Bsend() Permite que el programador establezca un tamaño de buffer, el cual almacena lo que se va a enviar. Esto no garantiza que el procesador correspondiente lo reciba, pero si que llega a un procesador

Síncrono MPI_Ssend() Bloquea la ejecución de su procesador solo hasta que la operación de recepción se vea iniciada.

Estándar MPI_Send() Envía su mensaje y garantiza que todos los datos sean recibidos.

Ready MPI_Rsend() Garantiza que el mensaje ha llegado y ha sido consumido por una

Page 10: CD Unidad3 Con C

función receptora

La función para recibir es el estándar MPI_Recv().

Solo existe un modo de recibir cuando existe una comunicación bloqueante pero esta puede ser utilizada para los ocho tipos de envío. Esta función es una operación básica que acepta los mensajes enviados desde otro procesador.

2.2.1 Envió estándar.

El uso del envió estándar nos asegura que el mensaje enviado será recibido por completo, teniendo de esta forma la terminación de ambas operaciones.

La sintaxis del send estándar es:int MPI_Send (void *buffer, int cont, MPI_Datatype tipodato, int destino, int etiqueta, MPI_Comm com);

donde:

Variable de entrada buffer nombre de la variable asignado al buffer.Variable de entrada cont es el número de elementos del tipo de dato MPI que el buffer contiene.Variable de entrada tipodato es el tipo de dato que se envía.Variable de entrada destino es el número del procesador destino al que llegará el mensaje.Variable de entrada etiqueta marca usada para distinguir los mensajes.Variable de entrada com es el comunicador que nos permite el envío de mensajes entre los

procesadores.

Ejemplo:

MPI_Send(&buffer, cont, tipodato, destino, etiqueta, MPI_COMM_WORLD);

2.2.2 Envío Síncrono.

El envío síncrono se completa una vez que el mensaje ha sido recibido en su inicio, esto no garantiza la recepción completa del mensaje, pero si que el mensaje sea recibido por el procesador correspondiente . Es decir, el programador debe tomar en cuenta que cuando utilice este tipo de envío no debe asumir que se completará exitosamente la operación de recepción.

Una de las ventajas que tenemos al utilizar un envió síncrono es la facilidad de depuración de un programa, ya que es más seguro.

La sintaxis del send síncrono es similar al send estándar:int MPI_Ssend(void *buffer, int cont, MPI_Datatype tipodato, int destino, int etiqueta, MPI_Comm com);

donde:

Variable de entrada buffer nombre de la variable asignado al buffer.

Page 11: CD Unidad3 Con C

Variable de entrada cont es el número de elementos del tipo de dato MPI que el buffer contiene.

Variable de entrada tipodato es el tipo de dato que se envía.Variable de entrada destino es el número del procesador al cual llegará el mensaje.Variable de entrada etiqueta marca usada para distinguir los mensajes.Variable de entrada com es el comunicador que nos permite el envío de mensajes entre los

procesadores.

Ejemplo:

MPI_Ssend (&buffer, cont, tipodato, destino, etiqueta, MPI_COMM_WORLD);

2.2.3 Envío Buferizado.

Como se mencionó, el uso de send buferizado nos garantiza que el envío ha llegado pero no que haya sido recibido, esto nos asegura que la operación send se complete casi inmediatamente. El buffer es utilizado para copiar en él los mensajes que, tiempo después, serán transmitidos o retransmitidos si es necesario (figura 2.3).

Esto puede llevar a dos desventajas inmediatas:

• El uso del buffer corresponde a que se pueden hacer varios envíos y el procedimiento correspondiente puede o no recibirlos, lo cual genera pérdida de tiempo.

• El tamaño del buffer requerido debe ser asignado por el usuario, el cual debe de "adivinar" dicho tamaño; si el tamaño del buffer no es el requerido tanto por el procesador emisor y receptor de mensajes, éste producirá un error de sobre flujo (overflow).

Fig. 2.3 Comportamiento de la función MPI_Bsend.

Explicación de la figura 2.3:

t0: El procesador P0 copia los datos a la memoria que se tiene asignado (buffer) antes de enviar los datos.

t1: El procesador P0 comunica al demonio de MPI (poe), que requiere enviar datos al procesador P1.t2: El procesador P1 comunica al demonio de MPI que espera datos del procesador P0.t3: El demonio del procesador P0 envía los datos a través de la red, hacia el procesador indicado (P1).

Page 12: CD Unidad3 Con C

t4: El demonio de P1 se encarga que los datos recibidos se guarden dentro de la variable.t5: El demonio de P1 emite un valor cero al P0 indicando que la recepción se ha completado, o un uno

para indicar que la recepción no se completo de manera exitosa. t6: De acuerdo al valor retornado por el demonio de P0 la operación de envío, del procesador 1, termina

o envía de nuevo los datos.

La sintaxis para el send buferizado es de la siguiente manera:

int MPI_Bsend(void *buffer, int cont, MPI_Datatype tipodato, int destino, int etiqueta, MPI_Comm com);

donde:

Variable de entrada buffer nombre de la variable asignada a la dirección del buffer.Variable de entrada cont es el número de elementos del tipo de dato MPI que el buffer contiene.Variable de entrada tipodato es el tipo de dato que se envía.Variable de entrada destino es el número del procesador destino al que llegará el mensaje.Variable de entrada etiqueta marca usada para distinguir los mensajes.Variable de entrada com es el comunicador que nos permite el envío de mensajes entre los

procesadores.

Ejemplo:

MPI_Bsend (&buffer, cont, tipodato, destino, etiqueta, MPI_COMM_WORLD);

Para asignar el tamaño del buffer, existe una función dedicada para ello MPI_Buffer_attach () y para libera la memoria se cuenta con la función MPI_Buffer_detach().

La sintaxis para el MPI_Buffer_attach y MPI_Buffer_detach() es la siguiente:

int MPI_Buffer_attach (void *puntero, int espacio);int MPI_Buffer_detach (void puntero, int *espacio);donde para ambos:

Variable de entrada puntero es un puntero a una dirección de memoria.Variable de entrada espacio es el tamaño del espacio en memoria que se asignara (bytes) .

Ejemplos:

MPI_Buffer_attach (&puntero, espacio);MPI_Buffer_detach (puntero, &espacio);

Programa 2: Envío Buferizado.Descripción: En el siguiente programa se muestra la manera de implementar un envío buferizado, definiendo un espacio de memoria para el mensaje de envió.

/* Programa que lanza un mensaje usando como función de envío a MPI_Bsend */#include <stdio.h> #include <string.h> #include <mpi.h> #define max 128

Page 13: CD Unidad3 Con C

void main(int argc, char *argv[]){ int espacio 1024;

int rango, nprocs; int destino, fuente, etiqueta=1; int cuantos; char msgEnt[max]; char cad1[]= "Hola, esto es un"; char cad2[]="envío buferizado."; char *puntero; MPI_Status estado; /* Inicializa el ambiente paralelo */ MPI_Init (&argc, &argv); MPI_Comm_size (MPI_COMM_WORLD, &nprocs); MPI_Comm_rank (MPI_COMM_WORLD, &rango); switch (rango) { case 0:

destino= 1; fuente=1; /* Por medio de un envío sincrono se envía cad1 al procesador 1*/ MPI_Ssend(cad1, strlen(cad1)+1, MPI_CHAR, destino, etiqueta, MPI_COMM_WORLD); MPI_Recv(msgEnt, max, MPI_CHAR, fuente, etiqueta, MPI_COMM_WORLD, &estado); printf("Mensaje recibido: %s\n", msgEnt); break; case 1: destino=0; fuente=0; /* Se reserva un espacio en memoria del tamaño de espacio */ puntero = (char *) malloc (sizeof (char) * espacio);

MPI_Recv (&msgEnt, max, MPI_CHAR, fuente, etiqueta, MPI_COMM_WORLD, &estado); printf("Mensaje recibido %s \n", msgEnt); /* Se asigna el espacio de memoria para las funciones de MPI que lo requieran */ MPI_Buffer_attach ( puntero, espacio); /* Se envía cad2 al procesador 0, esta función copia a cad2 en la memoria asignada*/

MPI_Bsend ( cad2, strlen (cad2)+1, MPI_CHAR, destino, etiqueta, MPI_COMM_WORLD); /* Se libera la memoria que fue asignada a la función de envío */ MPI_Buffer_detach ( puntero, &espacio); break;

} MPI_Finalize( );

}

Impresión en pantalla:

Mensaje recibido: Hola, esto es unMensaje recibido: envío buferizado.

En el programa anterior, la función MPI_Buffer_attach, nos permite asignar un rango de memoria espacio, de tal manera que la operación de envío (MPI_Bsend) no tenga problemas. Para liberar la memoria hacemos uso de la función MPI_Buffer_detach().

2.2.4 Envió Ready.

Page 14: CD Unidad3 Con C

Este tipo de send se debe utilizar siempre y cuando exista una operación de recepción lista para el mensaje enviado y puede completarse sin ningún problema (figura 2.4); pero si el mensaje enviado esta sin rumbo, puede ocurrir un error.

La idea de usar el ready send es la necesidad de evitar que se cree un espacio de memoria "a ciegas" entre el envío y la recepción. El modo ready es un poco difícil de depurar y requiere de mucho cuidado al estar usando este tipo de envío en un programa paralelo. Sin embargo, utilizar el ready send asegura que los datos no se corrompan al ser enviados

Fig. 2.4 El comportamiento de la función MPI_Rsend.

Explicación de la figura 2.4:

t0: El procesador P1 comunica al demonio de MPI (poe), que esta listo para recibir datos del procesador P0.

t1: El procesador P0, comunica al demonio de MPI que requiere enviar datos al procesador P1.t2: El demonio del procesador P0 envía los datos a través de la red, hacia el procesador

indicado (P1).t3: El demonio de P1, se encarga que los datos recibidos se guarden dentro de una variable.t4: El demonio de P1 emite un valor cero al P0 indicando que la recepción se ha completado, o

un uno para indicar que la recepción no se completo de manera exitosa. t5: De acuerdo al valor retornado, el procesador 0 puede o no continuar su ejecución.

La sintaxis se muestra a continuación:

int MPI_Rsend(void *buffer, int cont, MPI_Datatype tipodato, int destino, int etiqueta, MPI_Comm com);

donde:

Variable de entrada buffer nombre de la variable asignado al buffer.Variable de entrada cont es el número de elementos del tipo de dato MPI que el buffer contiene.Variable de entrada tipodato es el tipo de dato que se envía.Variable de entrada destino es el número del procesador destino al que llegará el mensaje.Variable de entrada etiqueta marca usada para distinguir los mensajes.Variable de entrada com es el comunicador que nos permite el envío de mensajes entre los

procesadores.

Page 15: CD Unidad3 Con C

Ejemplo:

MPI_Rsend (&buffer, cont, tipodato, destino, etiqueta, MPI_COMM_WORLD);

2.2.5 Recepción estándar.

Esta operación básica de recepción es usada para aceptar envíos desde otros procesadores, las cuales pueden ser bloqueantes o no bloqueantes. Cuando el procesador receptor recibe un mensaje, éste debe ser igual o menor al tamaño del buffer declarado para el mensaje, si en dado caso el mensaje enviado es demasiado grande, puede ocurrir un error de sobre flujo; en caso contrario éste solamente tomará su lugar correspondiente dentro del buffer.

El formato para la recepción es de la siguiente manera:

int MPI_Recv (void *buffer, int cont,MPI_Datatype tipodato, int fuente, int etiqueta, MPI_Comm com, MPI_Status *estado);

donde:

Variable de salida buffer nombre de la variable asignado al buffer de recepción.Variable de entrada cont es el número de elementos a recibir.Variable de entrada tipodato es el tipo de dato que se va a recibir mensaje enviado.Variable de entrada fuente es el número de rango del procesador emisor. Este se puede ser de

manera general utilizando MPI_ANY_SOURCE.Variable de entrada etiqueta es la etiqueta que tiene el mensaje que espera. Esto puede hacer de

manera general utilizando MPI_ANY_TAG.Variable de entrada com es el comunicador para la comunicación entre los procesadores.Variable de salida estado es el estado de la comunicación.

Ejemplo:

MPI_Recv (&buffer, cont, tipodato, fuente, etiqueta, MPI_COMM_WORLD, &estado);

La variable estado es un argumento de salida que provee información acerca de los mensajes que han sido recibidos. Esta es una estructura de tipo MPI_Status predefinido por MPI, el cual contiene: estado.MPI_Source que corresponden a la fuente y los elementos estado.MPI_Tag que guardan que tipo de mensaje (etiqueta) ha recibido, otras entradas en estado son usadas para determinar el número de elementos que se han recibido; este valor puede ser extraído de esta estructura usando la función MPI_Get_count.

int MPI_Get_count ( MPI_Status *estado, MPI_Dataype tipodato, int *cont);

donde:

Variable de entrada estado es el estado de la comunicación que se quiere evaluar.Variable de entrada tipodato es el tipo de dato de cada elemento del mensaje.Variable de salida cont variable que guarda el número de elementos.

Ejemplo:

Page 16: CD Unidad3 Con C

MPI_Get_count (&estado, tipodato, &cont);

Programa 3. Un programa bloqueante.Se probará un programa para ejemplificar las funciones de recepción y envío: MPI_Send, MPI_Ssend, MPI_Recv.

Descripción: Nuestro universo esta compuesto por 4 procesadores, es necesario comunicar a todos los procesadores secuencialmente, de tal manera que el procesador con rango=0 le envíe un mensaje al procesador de rango=1, el 1 al 2 y así sucesivamente (figura 2.5).

Fig. 2.5 Esquema de explicación.

Código para MPI_Send y MPI_Recv:

/* Programa donde se envía una cadena de caracteres simulando una topología de anillo, utilizando la función de envío estándar */#include <stdio.h> #include <mpi.h> #include <stdio.h > #define max 5 /* constante definida para el arreglo que contendrá al mensaje recibido*/ void main(int argc, char *argv[]){ int rango; /* variable que guardará el número de cada procesador */ int nprocs; /* variable que guardará el número total de procesador */ int etiqueta =50; /* etiqueta es cualquier número entero */ int destino; int fuente; char cad1[]="cero", cad2[]="uno", cad3[]="dos", cad4[]="tres", msgEnt[max]; /* arreglo que contendrá el mensaje que se recibe */ MPI_Status estado; /*Inicializamos ambiente paralelo*/ MPI_Init(&argc, &argv); /* Obtener el número de procesadores y asignar el rango correspondiente */ MPI_Comm_size (MPI_COMM_WORLD,&nprocs);

Page 17: CD Unidad3 Con C

MPI_Comm_rank(MPI_COMM_WORLD,&rango); /*verificando si el número de procesadores asignados es el indicado */ if(nprocs != 4){ printf("El número de procesadores asignados no es el requerido \n"); exit(1); }else switch(rango){

case 0: destino=1; fuente=3; /* Se envía el mensaje contenido en cad1 al procesador con rango 1*/ MPI_Send(cad1,strlen(cad1)+1,MPI_CHAR,destino,etiqueta, MPI_COMM_WORLD); /* función de recepción del mensaje desde el procesador 3*/ MPI_Recv (msgEnt,max,MPI_CHAR,fuente,etiqueta, MPI_COMM_WORLD,&estado); printf("Soy %d recibi el siguiente mensaje: %s\n", rango, msgEnt);

break; case 1:

destino=2; fuente=0; /* La operación de recepción es ejecutada antes de la función de envío, espera un mensaje del procesador 0*/ MPI_Recv (msgEnt,max,MPI_CHAR,fuente,etiqueta, MPI_COMM_WORLD,&estado); printf("Soy %d, recibí el siguiente mensaje: %s \n",rango,msgEnt); /* Envía cad2 al destino 2, es decir, al procesador 2*/ MPI_Send (cad2,strlen(cad2)+1,MPI_CHAR,destino,etiqueta, MPI_COMM_WORLD);

break; case 2:

destino=3; fuente=1; /* recepción del mensaje del procesador 1 */ MPI_Recv (msgEnt,max,MPI_CHAR,fuente,etiqueta, MPI_COMM_WORLD,&estado); printf("Soy %d, recibí el siguiente mensaje: %s \n",rango,msgEnt); /* envío cad3 al procesador 3*/ MPI_Send(cad3,strlen(cad3)+1,MPI_CHAR,destino,etiqueta, MPI_COMM_WORLD);

break; case 3:

destino=0; fuente=2; /* recepción del mensaje del procesador 2*/ MPI_Recv(msgEnt,max,MPI_CHAR,fuente,etiqueta, MPI_COMM_WORLD,&estado); printf("Soy %d, recibí el siguiente mensaje:%s\n",rango,msgEnt); /* envío cad4 al procesador 0 y cierro el circulo*/ MPI_Send(cad4,strlen(cad4)+1,MPI_CHAR,destino,etiqueta, MPI_COMM_WORLD);

break; } /* Se finaliza el ambiente paralelo */ MPI_Finalize(); }

Tenemos entonces la ejecución en forma gráfica de nuestro programa en la figura 2.7:

Page 18: CD Unidad3 Con C

Fig. 2.7 Ejecución de las funciones de envío y recepcióndel programa 3.

Se observa en la figura 2.7, los tiempos de ejecución de las operaciones de envío y recepción de los diferentes procesadores.

t0: En el tiempo cero (t0) el procesador P0 envía el contenido de cad1 al procesador P1.t1: El procesador P1, ejecuta la operación de recepción y los demás procesadores ejecutan la

operación de recepción.t2: P0 ejecuta ahora la operación de recepción después de completar su envío.t3 Después de que P1 completa su operación de recepción. Envía el contenido de cad2 a P2.t4: Una vez que P2 completa su operación de recepción, envía el contenido de cad3 a P3.t5: Una vez que P3 completa su operación de recepción, envía a cad4 a P0.

Este es el resultado real que se obtiene al ejecutar el programa:

Soy 1, recibí el siguiente mensaje: ceroSoy 2, recibí el siguiente mensaje: unoSoy 3, recibí el siguiente mensaje: dosSoy 0, recibí el siguiente mensaje: tres

Programa 4. Código para MPI_Ssend:

Ahora convertiremos parte del código del programa 3, para que este opere con la función de envió síncrono MPI_Ssend.

Considerando:

switch(rango){ case 0:

destino=1; fuente=3; /* Se envía el mensaje contenido en cad1 al procesador con rango 1*/ MPI_Send(cad1,strlen(cad1)+1,MPI_CHAR,destino,etiqueta, MPI_COMM_WORLD); /* función de recepción del mensaje desde el procesador 3*/ MPI_Recv (msgEnt,max,MPI_CHAR,fuente,etiqueta, MPI_COMM_WORLD,&estado); printf("Soy %d recibi el siguiente mensaje: %s\n", rango, msgEnt);

break; case 1:

destino=2; fuente=0; /* La operación de recepción es ejecutada antes de la función de

Page 19: CD Unidad3 Con C

envío, espera un mensaje del procesador 0*/ MPI_Recv (msgEnt,max,MPI_CHAR,fuente,etiqueta, MPI_COMM_WORLD,&estado); printf("Soy %d, recibí el siguiente mensaje: %s \n",rango,msgEnt); /* Envía cad2 al destino 2, es decir, al procesador 2*/ MPI_Ssend (cad2,strlen(cad2)+1,MPI_CHAR,destino,etiqueta, MPI_COMM_WORLD);

break; case 2:

destino=3; fuente=1; /* recepción del mensaje del procesador 1 */ MPI_Recv (msgEnt,max,MPI_CHAR,fuente,etiqueta, MPI_COMM_WORLD,&estado); printf("Soy %d, recibí el siguiente mensaje: %s \n",rango,msgEnt); /* envío cad3 al procesador 3*/ MPI_Ssend(cad3,strlen(cad3)+1,MPI_CHAR,destino,etiqueta, MPI_COMM_WORLD);

break; case 3:

destino=0; fuente=2; /* recepción del mensaje del procesador 2*/ MPI_Recv(msgEnt,max,MPI_CHAR,fuente,etiqueta, MPI_COMM_WORLD,&estado); printf("Soy %d, recibí el siguiente mensaje:%s\n",rango,msgEnt); /* envío cad4 al procesador 0 y cierro el circulo*/ MPI_Ssend(cad4,strlen(cad4)+1,MPI_CHAR,destino,etiqueta, MPI_COMM_WORLD);

break; } /* Se finaliza el ambiente paralelo */ MPI_Finalize(); }

En el caso que muestra arriba, las funciones de envío bloquean la ejecución de los procesadores hasta verificar que hayan sido recibidos. El funcionamiento se puede comparar con el de la figura 2.7.

Este es el resultado real que se obtiene al ejecutar el programa:

Soy 1, recibí el siguiente mensaje: ceroSoy 2, recibí el siguiente mensaje: unoSoy 3, recibí el siguiente mensaje: dosSoy 0, recibí el siguiente mensaje: tres

Page 20: CD Unidad3 Con C

3.- Comunicación no bloqueante.

3.1 Comunicación no bloqueante.

Dentro de la comunicación punto a punto, nos encontramos el modo no bloqueante (Non blocking comunication); esta idea surgió por la necesidad de terminar con la mayor desventaja de la comunicación bloqueante, que como habíamos visto anteriormente se trata de los tiempos muertos. La principal característica de la comunicación no bloqueante es que no interrumpe la ejecución del procesador, al realizar operaciones de envío/recepción.

Las funciones básicas de envío no bloqueante en MPI son:

• Envío buferizado: MPI_Ibsend. • Envío síncrono: MPI_Issend. • Envío estándar: MPI_Isend. • Envío ready: MPI_Irsend. • La función de recepción es MPI_Irecv .

La sintaxis de las funciones no bloqueantes varia de las bloqueantes en un argumento y la letra "I" que se adiciona después del guión bajo y significa Inmediato (Immediate en Inglés).

Es conveniente señalar que existe una diferencia substancial entre el uso de las funciones de checado del estado del buffer, ya sea de recepción o envío. Con el uso de la función wait, el procesador receptor que espera un envío, interrumpe su ejecución hasta la llegada de datos a su buffer. Usualmente el wait es usado donde tenemos cálculos que esperan datos de otras tareas, o cuando el buffer necesita ser reutilizado (figura 3.1).

La función wait tiene la siguiente sintaxis:

MPI_Wait (MPI_Request *petición, MPI_Status *estado);

donde:

Variable de entrada/salida petición solicitud de espera de datos del procesador cuestionado.Variable de salida estado estado del buffer.

Ejemplo:

MPI_Wait (&petición, &estado);

Page 21: CD Unidad3 Con C

Fig. 3.1 Comportamiento de MPI_Wait.

Explicación de la figura 3.1:

En el tiempo t0, el procesador P0 ejecuta la operación de evaluación para la recepción de datos.

La función MPI_Wait desbloquea la ejecución del programa en un tiempo tn cuando se retorna que existen datos dentro del buffer del procesador P0.

Cuando el uso de la función wait se ve limitado, es necesario introducir una nueva función: el MPI_Test. La versatilidad del MPI_Test estriba en la característica de poder leer el estado del buffer constantemente sin bloquear la ejecución del programa. La función MPI_Test usualmente se ocupa en situaciones donde es necesario saber si la comunicación se ha completado, sin requerir los datos.

Fig. 3.2 Comportamiento de MPI_Test.

Explicación de la figura 3.2:

En un tiempo tn, el procesador P0 ejecuta la operación de evaluación (MPI_Test) para la recepción de datos sin bloquear su ejecución y continua preguntando hasta un tiempo tn+1. En este tiempo el buffer ya contiene los datos que son requeridos.

La sintaxis de la función MPI_Test requiere de una bandera para poder saber si en el buffer encuentra lo que busca.

MPI_Test (MPI_Request *petición, int *bandera, MPI_Status &estado);

donde:

Page 22: CD Unidad3 Con C

Variable de entrada/salida petición solicitud de espera de datos del procesador cuestionado.Variable de salida bandera retorna verdadero, si los datos requeridos se encuentran dentro

del buffer. Variable de salida estado estado del buffer.

Ejemplo:

MPI_Test (&petición, &bandera, &estado);

MPI_Test retorna un valor verdadero en bandera cuando la comunicación identificada por peticion se ha completado; la variable estado contiene información acerca de las operaciones que se han completado.

Una vez estudiado las funciones que nos permiten inspeccionar el éxito de una operación de envío y/o recepción, estudiaremos la sintaxis de las funciones no bloqueantes de envío y recepción.

Es conveniente mencionar que los parámetros son equivalentes a las operaciones bloqueantes de envío o recepción que se conoce, excepto por el aumento del argumento &peticion, que se encarga de guardar información acerca de la completación de una operación, esto es, guardará datos acerca de su envío o recepción. Una variable de este tipo de obtiene declarándola como MPI_Request.

3.2 Envío estándar

int MPI_Isend (void *buffer, int cont, MPI_Datatype tipodato, int destino, int etiqueta,MPI_Comm com,MPI_Request *peticion);

donde:

Variable de entrada buffer es la dirección inicial del dato.Variable de entrada cont es el número de elementos del tipo de dato a enviar.Variable de entrada tipodato es el tipo de dato que se envía.Variable de entrada destino es el número del procesador destino al que llegará el mensaje.Variable de entrada etiqueta marca usada para poder distinguir los mensajes.Variable de entrada com es el comunicador que nos permite el envío de mensajes entre los

procesadores.Variable de salida peticion contiene información sobre los datos que son enviados.

Ejemplo:

MPI_Isend (&buffer, cont, tipodato, destino, etiqueta, MPI_COMM_WORLD, &peticion);

3.3 Envío síncrono

int MPI_Issend (void *buffer, int cont, MPI_Datatype tipodato, int destino, int etiqueta,MPI_Comm com,MPI_Request *peticion);

donde:

Page 23: CD Unidad3 Con C

Variable de entrada buffer es la dirección de inicio del dato dentro del buffer.Variable de entrada cont es el número de elementos del tipo de dato MPI que el buffer contiene.Variable de entrada tipodato es el tipo de dato que se envía.Variable de entrada destino es el número del procesador destino al que llegará el mensaje.Variable de entrada etiqueta marca usada para poder distinguir entre mensajes.Variable de entrada com es el comunicador que nos permite el envío de mensajes entre los

procesadores.Variable de salida peticion contiene información sobre los datos que son enviados.

Ejemplo:

MPI_Issend (&buffer, cont, tipodato, destino, etiqueta, MPI_COM_WORLD, &peticion);

3.4 Envío buferizado

int MPI_Ibsend (void *buffer, int cont, MPI_Datatype tipodato, int destino, int etiqueta,MPI_Comm com,MPI_Request *peticion);

donde:

Variable de entrada buffer es la dirección del dato. Variable de entrada cont es el número de elementos del tipo de dato a enviar.Variable de entrada tipodato es el tipo de dato que se envía.Variable de entrada destino es el número del procesador destino al que llegará el mensaje.Variable de entrada etiqueta marca usada para poder distinguir los mensajes.Variable de entrada com es el comunicador que nos permite el envío de mensajes entre los

procesadores.Variable de salida peticion contiene información sobre los datos que son enviados.

Ejemplo:

MPI_Ibsend (&buffer,cont,tipodatp,destino,etiqueta, MPI_COM_WORLD, &peticion);

3.5 Envío Ready

int MPI_Irsend (void *buffer, int count, MPI_Datatype tipodato, int destino, int etiqueta,MPI_Comm com,MPI_Request *peticion);

donde:

Variable de entrada buffer es la dirección de inicio del dato.Variable de entrada cont es el número de elementos del tipo de dato MPI que el buffer contiene.Variable de entrada tipodato es el tipo de dato que se envía.Variable de entrada destino es el número del procesador destino al que llegará el mensaje.Variable de entrada etiqueta marca usada para poder distinguir los mensajes.Variable de entrada com es el comunicador que nos permite el envío de mensajes entre los

procesadores.

Page 24: CD Unidad3 Con C

Variable de salida peticion contiene información sobre los datos que son enviados.

Ejemplo:

MPI_Irsend (&buffer, cont, tipodato, destino, etiqueta,MPI_COMM_WORLD, &peticion);

3.6 Recepción no bloqueante.

El formato para la recepción es de la siguiente manera:

int MPI_Irecv (void *buffer, int cont, MPI_Datatype tipodato, int fuente, int etiqueta, MPI_Comm com, MPI_Request *peticion);

donde:

Variable de salida buffer es la dirección inicial del dato.Variable de entrada cont es el número de elementos ha recibir.Variable de entrada tipodato es el tipo de dato receptor del mensaje enviado.Variable de entrada fuente es el número del procesador receptor. Este se puede hacer de manera

general utilizando MPI_ANY_SOURCE. Variable de entrada etiqueta marca usada para poder distinguir los mensajes. Esto puede hacer de

manera general utilizando MPI_ANY_TAG.Variable de entrada com es el comunicador que nos permite el envío de mensajes entre los

procesadores.Variable de entrada peticion contiene información sobre los datos que son enviados.

Ejemplo:

MPI_Irecv (&buffer, cont, tipodato, fuente, etiqueta, MPI_COMM_WORLD, &peticion);

Para ilustrar la manera en como trabajan las funciones, se presenta a continuación dos programas. El primero nos muestra la funcionalidad de MPI_Wait usando envío estándar y la función de recepción, por supuesto no bloqueante.

El segundo programa muestra el uso de MPI_Test usando la función de envío estándar no bloqueante. En el caso de la función de recepción, se usará una recepción no bloqueante y una bloqueante, de tal manera que se pueda comprender la diferencia entre ellas.

Programa 5. Uso de la función MPI_Wait./* programa de prueba para la función MPI_Wait para comunicación no bloqueante */#include <mpi.h> #include <stdio.h> #define max 10 void main (int argc, char *argv[]){ int rango, nprocs, destino, fuente; int etiqueta=111; /*se etiqueta al número con 111*/ char cad1 [5] ="envío"; /* mensaje de salida */ char cad2 [9] = "recepción"; int msg[max]; /* mensaje de entrada */

Page 25: CD Unidad3 Con C

MPI_Status estado; MPI_Request envío; MPI_Request recepcion; /* Se inicializa el ambiente paralelo */ MPI_Init (&argc,&argv); MPI_Comm_size (MPI_COMM_WORLD,&nprocs); MPI_Comm_rank (MPI_COMM_WORLD,&rango); if(rango==0){

destino=1; fuente=1; /* Se envía a cad1 por medio de una función no bloqueante de envío */ MPI_Isend (cad1, strlen(cad1), MPI_CHAR, destino, etiqueta, MPI_COMM_WORLD, &envío); printf("Yo procesador %d, estoy tratando de enviar un mensaje\n", rango); /* Bloquea la ejecución del programa hasta que envío contenga valores */ MPI_Wait (&envío, &estado); printf("El envío desde el procesador %d ha tenido éxito \n", rango); printf("a continuación recibiré un mensaje desde el procesador %d\n", fuente); MPI_Recv (msg, max, MPI_CHAR, fuente, etiqueta, MPI_COMM_WORLD, &estado); printf("El procesador %d recibió: %s\n", rango, msg);

} else {

destino=0; fuente=0; /* Recibe el contenido de cad1 enviado desde el procesador 0 (fuente) */ MPI_Irecv (msg, max, MPI_CHAR, fuente, etiqueta, MPI_COMM_WORLD, &recepcion); printf("Yo procesador %d, estoy tratando de tomar un mensaje\n", rango); /* Espera hasta que la recepción contenga valores correspondiente a lo enviado */ MPI_Wait (&recepcion, &estado); printf("La recepción se llevó a cabo con éxito\n"); /* Envía el contenido de cad2 al procesador 0 (destino) y continua la ejecución*/ MPI_Isend(cad2, strlen(cad1), MPI_CHAR, destino, etiqueta, MPI_COMM_WORLD, &envío); printf("El procesador %d recibió: %s\n", rango, msg);

} MPI_Finalize( ); }

Impresión en pantalla:

Yo procesador 0, estoy tratando de enviar un mensaje Yo procesador 1, estoy tratando de tomar un mensajeEl envío desde el procesador 0 ha tenido éxito a continuación recibiré un mensaje desde el procesador 1 La recepción se llevó a cabo con éxitoEl procesador 1 recibió: envíoEl procesador 0 recibió: recepción

Programa 6. Uso de la función MPI_Test.

Descripción: El siguiente programa será utilizado para mostrar el comportamiento de las funciones no bloqueantes al enviar un mensaje con MPI_Isend y la manera en que el procesador correspondiente recibe datos con el uso de MPI_Test.

Page 26: CD Unidad3 Con C

/* programa de prueba para la función MPI_Test para comunicación no bloqueante*/#include <mpi.h> #include <stdio.h> #define max 6 void main (int argc, char *argv[]){ int rango, i; int nprocs; int destino, fuente; int etiqueta=111; int bandera=0; char cad1[3]="si"; char msg[max]; MPI_Status estado; MPI_Request envío; MPI_Request recepcion; MPI_Init(&argc,&argv); /* Inicialización del ambiente paralelo*/ /*función que obtiene el no. de procesadores*/ MPI_Comm_size (MPI_COMM_WORLD,&nprocs); /*se obtiene el no. de rango de c/procesador*/ MPI_Comm_rank (MPI_COMM_WORLD,&rango); if(rango==0){ destino=1; fuente=1;

/* función de envío no bloqueante estándar */ MPI_Isend(cad1,strlen(cad1),MPI_CHAR, destino, etiqueta, MPI_COMM_WORLD, &envío); printf("He enviado mi mensaje, soy el procesador %d\n", rango); do{ printf("Antes del test envío: %d\n",bandera);

/*pregunta por el estado del buffer */ MPI_Test(&envío, &bandera, &estado); printf("Después del test envío: %d", bandera);

} while (!bandera); } else { destino=0; fuente=0;

/* función de recepción no_bloqueante */ MPI_Irecv (msg, max, MPI_CHAR, fuente, etiqueta, MPI_COMM_WORLD, &recepcion); /* pregunta hasta que la el buffer de recepción contenga valores, esto mediante la evaluacion de

bandera */ do{ MPI_Test (&recepcion, &bandera, &estado); }while(!bandera); printf("Recibí: %s\n", msg);

} MPI_Finalize(); /* finaliza el ambiente paralelo*/ }

Impresión en pantalla:

He enviado mi mensaje, soy el procesador 0Recibí: siAntes del test de envío: 0

Page 27: CD Unidad3 Con C

Después del test de envío: 1

Page 28: CD Unidad3 Con C

4.-Comunicación colectiva. Dentro de la comunicación colectiva, lo primero que se debe tener en mente es que no empleará más ninguna operación send o recv como ocurrió en la comunicación punto a punto. Lo que distingue una comunicación colectiva de una comunicación punto a punto, es la distribución de un dato a muchos procesadores que pertenecen al comunicador indicado.

Algunas de las características de la comunicación colectiva son :

• Una comunicación colectiva puede o no sincronizar las operaciones incluidas. • Un mensaje es un arreglo de un particular tipo de dato. • Las funciones usadas para una comunicación colectiva, contienen 2 parámetros para declarar el

tipo de dato que se distribuirá en los procesadores, dichos parámetros deben estar declarados con el mismo tipo de dato.

Diferencias:

• No existe el concepto de etiqueta. • El envío del mensaje debe llenar con las especificaciones del buffer de la recepción.

Las funciones que MPI provee para la distribución de datos:

• Difusión de datos (Broadcast). • Dispersión de datos (Scatter). • Reunión de datos (Gather). • Reunión en todos (Allgather). • Todo a todos (all to all). • Reducción (Reduce y Allreduce).

Antes de definir cada una de las operaciones de comunicación colectiva, es necesario explicar que función es la indicada para sincronizar una comunicación colectiva. MPI_Barrier provee un mecanismo para sincronizar todos los procesadores que constituyen al comunicador com. Cada procesador hace una pausa mientras todos los procesadores del comunicador son llamados por MPI_Barrier.

La sintaxis de la función MPI_Barrier es:

int MPI_Barrier (MPI_Comm com);

donde:

Variable de entrada com conforma el grupo de procesadores.

Ejemplo:

MPI_Barrier (MPI_COMM_WORLD);

Page 29: CD Unidad3 Con C

4.1 Difusión de datos (Broadcast).

Para una comunicación por difusión, se requiere de un solo procesador (root o raíz), que envía el mismo dato a todos los procesadores que pertenecen al comunicador (com) (figura 4.1 ). En MPI la función para broadcast es MPI_Bcast ():

Fig. 4.1 Comunicación colectiva por medio del uso de la función broadcast.

La función broadcast tiene la siguiente sintaxis:

int MPI_Bcast (void *buffer, int cont, MPI_Datatype tipodato, int raiz, MPI_Comm com);

donde:

Variable de entrada buffer es la dirección donde inicia el dato. Variable de entrada cont es el número de elementos que contiene buffer.Variable de entrada tipodato son los tipos de datos de los elementos del buffer.Variable de entrada raiz es el rango del procesador que envía.Variable de entrada com es el comunicador que contiene a los procesadores.

Ejemplo:

MPI_Bcast (&buffer, cont, tipodato, raiz,MPI_COMM_WORLD);

Programa 7. Uso de la función MPI_Bcast.Descripción: Al obtener 3 números (flotante, flotante y entero), estos se difundirán a todos los procesadores por medio del uso de la función Bcast.

#include <mpi.h> #include <stdio.h> void Bcast ( int mi_rango, int mi_nprocs){ float a,b; int n, raiz = 0; if (mi_rango == 0){ printf("Introduce 3 números (flotante flotante entero\n"); scanf("%f %f %d", &a, &b, &n); } /* Difusión a todos los procesadores del primer valor que fue introducido en la variable a */ MPI_Bcast ( &a, 1, MPI_FLOAT, raiz, MPI_COMM_WORLD); printf ("rango:% i, recibí:% f \n ", mi rango, a ); /* Difusión a todos los procesadores del segundo valor que fue introducido en la variable b */ MPI_Bcast ( &b, 1, MPI_FLOAT, raiz, MPI_COMM_WORLD); printf ("rango:% i, recibí:% f \n", mi_ rango, b); /* Difusión a todos los procesadores del tercer valor que fue introducido en la variable n */

Page 30: CD Unidad3 Con C

MPI_Bcast (&n, 1, MPI_INT, raiz, MPI_COMM_WORLD); printf ("rango:% i, recibí:% i \n", mi_rango, n ); } main ( int argc, char *argv[ ]){ int rango, nprocs; /* Se da inicio al ambiente paralelo */ MPI_Init ( &argc, &argv); MPI_Comm_rank ( MPI_COMM_WORLD,&rango); MPI_Comm_size (MPI_COMM_WORLD, &nprocs); /* Se manda a llamar el procedimiento Bcast */ Bcast (rango, nprocs); MPI_Finalize(); }

Impresión en pantalla:

Introduce 3 números (flotante flotante entero):3.0 4.4 5rango: 1 , recibí: 3.00000rango: 2 , recibí: 4.40000rango: 3 , recibí: 5rango: 1 , recibí: 4.40000rango: 2 , recibí: 5rango: 3 , recibí: 3.00000rango: 1 , recibí: 5rango: 2 , recibí: 3.00000rango: 3 , recibí: 4.40000

Fig. 4.2 Broadcast.

En la salida del programa, se encontrara difuso como la función Bcast distribuye los números a, b y n a los demás procesadores. En la figura 4.2 se muestra como el valor difundido es tomado por los demás procesadores.

4.2. Dispersión de datos (Scatter).

El procesador raíz distribuye el contenido de un arreglo hacia todos los procesadores. Cada elemento del arreglo es dividido a n procesadores (figura 4.3). Donde el número de procesadores debe ser igual a n números de elementos dentro del arreglo.

Page 31: CD Unidad3 Con C

Fig. 4.3 Comunicación colectiva por medio deluso de la función Scatter.

La función scatter tiene la siguiente sintaxis:

int MPI_Scatter (void *buffer_env, int envio_cont, MPI_Datatype tipodato_envío, void *buffer_rcv, int recepcion_cont, MPI_Datatype tipodato_rcv, int raiz, MPI_Comm com);

donde:

Variable de entrada Buffer_env es la dirección donde inicia el dato. Variable de entrada envío_cont es el número de elementos que se enviará a cada procesador.

Este número debe ser dado en bytes.Variable de entrada tipodato_envío es el tipo de dato de cada elementos dentro del buffer.Variable de salida buffer_rcv es la dirección del buffer que se recibe.Variable de entrada recepcion_cont es el número de elementos en el buffer de recepción.Variable de entrada tipodato_rcv es el tipo de dato dentro del buffer de recepción.Variable de entrada raiz es el rango del procesador que envía.Variable de entrada com es el comunicador que contiene a los procesadores.

Ejemplo:

MPI_Scatter (buffer_env, envio_cont, tipodato_envío, </B&AMP;buffer_rcv, recepcion_cont, tipodato_rcv, raiz,MPI_COMM_WORLD);

Programa 8. Uso de la función MPI_Scatter.Descripción: El siguiente programa contiene un arreglo de 8 elementos. Mediante el uso de la función MPI_Scatter cada elemento será difundido a cada procesador.

#include <mpi.h> #include <stdio.h> void Scatter (int mi_rango){ int arreglo[8]={3,5,7,9,11,13,15,17},recepcion=0; int cont_rcv=1; int raiz=0; /* Función que distribuye un elemento del arreglo a un procesador del comunicador */ MPI_Scatter (&arreglo, sizeof (arreglo[0])/sizeof(int), MPI_INT, &recepcion, cont_rcv, MPI_INT, raiz,

MPI_COMM_WORLD); printf("rango:%i, mensaje:%d \n", mi_rango, recepcion); } main (int argc, char *argv[ ]){ int rango, nprocs; /* Se da inicio al ambiente paralelo*/ MPI_Init (&argc, &argv);

Page 32: CD Unidad3 Con C

MPI_Comm_rank (MPI_COMM_WORLD,&rango); MPI_Comm_size (MPI_COMM_WORLD, &nprocs); /* Se llama a l procedimiento Scatter*/ Scatter (rango); MPI_Finalize(); }

Impresión en pantalla:

rango: 0, mensaje: 3rango: 1, mensaje: 5rango: 2, mensaje: 7rango: 3, mensaje: 9rango: 4, mensaje: 11rango: 5, mensaje: 13rango: 6, mensaje: 15rango: 7, mensaje: 17

4.3.- Reunión de datos (Gather).Al usar la rutina gather, todos los procesadores del comunicador envían datos al procesador cero, es decir, que los datos que contengan cada procesador serán enviados a un solo procesador. Podemos decir entonces, que la función gather equivale al contrario de la función scatter (figura 4.4).

Fig. 4.4 Comunicación colectiva por medio deluso de la función Gather.

La función gather tiene la siguiente sintaxis:int MPI_Gather (void *buffer_env, int envio_cont, MPI_Datatype tipodato_envío, void *buffer_rcv,

int recepcion_cont, MPI_Datatype tipodato_rcv, int raiz, MPI_Comm com);donde:

Variable de entrada buffer_env es la dirección donde inicia el dato.Variable de entrada envío_cont es el número de elementos que se enviarán a cada procesador. Variable de entrada tipodato_envío es el tipo de dato de cada elemento dentro del buffer a enviar.Variable de salida buffer_rcv es la dirección del arreglo en donde se reciben los datos. Este

debe ser del mismo tamaño que el arreglo que se forma. Variable de entrada recepcion_cont es el número de elementos en el buffer de recepción.Variable de entrada tipodato_rcv es el tipo de dato dentro del buffer de recepción.Variable de entrada raíz es el rango del procesador que envía.Variable de entrada com es el comunicador que contiene a los procesadores.

Ejemplo:

Page 33: CD Unidad3 Con C

MPI_Gather (&buffer_env, envio_cont, tipodato_envio, &buffer_rcv, recepcion_cont, tipodato_rcv, raiz, MPI_COMM_WORLD);

Programa 9. Uso de la función MPI_Gather.

Descripción: Suponiendo que los datos están distribuidos en los procesadores por medio del programa 8. Se decide reunirlo todos en al procesador cero; tenemos entonces:

#include <mpi.h> #include <stdio.h> void Gather (int mi_rango, int mi_nprocs){ int arreglo[8]={3,5,7,9,11,13,15,17}; int arreglo1[8]; int raiz=0,i; MPI_Gather (&arreglo[mi_rango*2],2,MPI_INT,&arreglo1,2,MPI_INT,0, MPI_COMM_WORLD); if (mi_rango == 0) { printf ("rango: %i\n", mi_rango); for (i=0; i < mi_nprocs*2; i++) printf ("% d\n", arreglo1[i]); }} main (int argc, char *argv[ ]){ ;int rango, nprocs; MPI_Init (&argc, &argv); MPI_Comm_rank (MPI_COMM_WORLD,&rango); MPI_Comm_size (MPI_COMM_WORLD, &nprocs); Gather(rango, nprocs); MPI_Finalize(); }

Impresión en pantalla:

rango: 0357911131517

4.4 Reunión en todos (Allgather).

Esta rutina no tiene un procesador raíz específico. Todos los procesadores envían sus datos, para que los demás procesadores reciban un arreglo con los datos que fueron enviados, es decir, el elemento que contiene cada procesador será enviado a cada arreglo de los n procesadores(figura 4.5).

Page 34: CD Unidad3 Con C

Fig. 4.5 Comunicación colectiva por medio del uso de la función Allgather.

La función allgather tiene la siguiente sintaxis:

int MPI_Allgather (void *buffer_env, int envio_cont, MPI_Datatype tipodato_envio, void *buffer_rcv, int

recepcion_cont, MPI_Datatype tipodato_rcv, MPI_Comm com);

donde:

Variable de entrada buffer_env es la dirección donde inicia el dato.Variable de entrada envío_cont es el número de elementos que se enviará a cada procesador.Variable de entrada tipodato_envío es el tipo de dato de cada elementos dentro del buffer a enviar.Variable de salida buffer_rcv es la dirección del arreglo que contendrá a los elementos. El

arreglo debe ser del tamaño del arreglo que se forme.Variable de entrada recepcion_cont es el número de elementos en el buffer de recepción.Variable de entrada tipodato_rcv es el tipo de dato dentro del buffer de recepción. Variable de entrada com es el comunicador que contiene a los procesadores.

Ejemplo:

MPI_Allgather (buffer_env, envio_cont, tipodato_envío, & buffer_rcv, recepcion_cont, tipodato_rcv, MPI_COMM_WORLD);

Programa 10. Uso de la función MPI_Allgather.

Descripción: Se distribuyen los datos de cada procesador a todos los procesadores usando la función Scatter del programa 8, para después, por medio de la función MPI_Allgther se puedan reunir en un arreglo y poder redistribuirlos a todos los procesadores.

#include <mpi.h> #include <stdio.h> void Allgather (int mi_rango, int mi_nprocs){ int arreglo[3]={3,5,7},recepcion; int arreglo1[sizeof(arreglo)/sizeof(int)]; int raiz=0,i; /* Se distribuye un elemento a cada procesador */ MPI_Scatter (&arreglo, sizeof (arreglo[0])/sizeof(int),MPI_INT,&recepcion,1, MPI_INT, raiz,

MPI_COMM_WORLD); /* Se reúnen los elementos de cada procesador en el arreglo1 y este se distribuye a todos los procesadores */ MPI_Allgather (&recepcion,1,MPI_INT,&arreglo1,1,MPI_INT, MPI_COMM_WORLD); for (i=0; i < sizeof(arreglo1)/sizeof(int); i++)

Page 35: CD Unidad3 Con C

printf ("rango: %i y tengo % i\t", arreglo1[i]); } int main (int argc, char *argv[]){

int rango, nprocs; /* Se da inicio al ambiente paralelo*/ MPI_Init (&argc, &argv); MPI_Comm_rank (MPI_COMM_WORLD,&rango); MPI_Comm_size (MPI_COMM_WORLD, &nprocs); /* Se llama al procedimiento Allgather */ Allgather(rango,nprocs); MPI_Finalize();

}

Impresión en pantalla:

rango: 0, y tengo 3rango: 1, y tengo 3rango: 0, y tengo 5rango: 1, y tengo 5rango: 2, y tengo 3rango: 0, y tengo 7rango: 1, y tengo 7rango: 2, y tengo 5rango: 2, y tengo 7

Aunque es un poco confusa la salida en pantalla, se puede observar que cada procesador cuenta con los elementos que antes estaban distribuidos en los procesadores. Por ejemplo, si ordenamos los procesadores por su rango

Rango 0: 3 5 7Rango 1: 3 5 7Rango 2: 3 5 7

observamos que ahora cada procesador contiene los elementos del arreglo1.

4.5 todo a todos (all to all).

MPI_Alltoall es una rutina que provee MPI para que todos los datos distintos de un procesador sean distribuidos a todos los procesadores que conforman al comunicador (figura 4.6).

Fig. 4.6 Usando la función MPI_Alltoall.

La función Alltoall tiene la siguiente sintaxis:

Page 36: CD Unidad3 Con C

int MPI_Alltoall (void *buffer_env, int envio_cont, MPI_Datatype tipodato_envio, void *buffer_rcv, int recepcion_cont, MPI_Datatype tipodato_rcv,MPI_Comm com);

donde:

Variable de entrada buffer_env es la dirección donde inicia el dato .Variable de entrada envío_cont es el número de elementos que se enviará a cada procesador.Variable de entrada tipodato_envío es el tipo de dato de cada elementos dentro del buffer a

enviar.Variable de salida buffer_rcv es la dirección del arreglo que contendrá a los elementos. Este

arreglo deberá tener el mismo tamaño que el arreglo que se forme.

Variable de entrada recepcion_cont es el números de elementos en el buffer de recepción. Variable de entrada tipodato_rcv es el tipo de dato dentro del buffer de recepción. Variable de entrada com es el comunicador que contiene a los procesadores.

Ejemplo:

MPI_Alltoall (buffer_env, envio_cont, tipodato_envío, & buffer_rcv, recepcion_cont, tipodato_rcv, MPI_COMM_WORLD);

Programa 11. Uso de la función MPI_Alltoall.

Descripción: Por medio de las funciones Scatter y Allgather, se obtendrá una distribución de datos requerida en los procesadores para poder ejemplificar a la función Alltoall. La cual reunirá los elementos consecutivamente de cada arreglo para formar un nuevo arreglo que se distribuirá a los procesadores.

#include <mpi.h> #include <stdio.h> void todo (int mi_rango, int mi_nprocs){ int arreglo[3]={3,5,7}, recepcion, recepcion1; int arreglo1[sizeof(arreglo)/sizeof(int)]; int arreglo2[sizeof(arreglo)/sizeof(int)]; int raiz=0,i, n; /* Se distribuye un elemento a cada procesador */ MPI_Scatter (&arreglo, sizeof (arreglo[0])/sizeof(int),MPI_INT, &recepcion, 1, MPI_INT,

raiz,MPI_COMM_WORLD); /*Se reúnen los elementos de cada procesador en el arreglo1 y este se distribuye a todos los procesadores */ MPI_Allgather (&recepcion,1,MPI_INT,&arreglo1,1,MPI_INT, MPI_COMM_WORLD); /* Rutina que reúne los datos de los procesadores en un nuevo arreglo y distribuye el nuevo arreglo a todo los procesadores */ MPI_Alltoall (&arreglo1, 1, MPI_INT, &arreglo2, 1, MPI_INT, MPI_COMM_WORLD); /* Se imprime en pantalla */ for(n=0; n < mi_nprocs; n++) if (mi_rango==n) for (i=0; i < sizeof(arreglo2)/sizeof(int); i++) printf ("rango: %i y tengo % i\t", arreglo2[i]); } main (int argc, char *argv []){

Page 37: CD Unidad3 Con C

int rango, nprocs; /* Se da inicio al ambiente paralelo*/ MPI_Init (&argc, &argv); MPI_Comm_rank (MPI_COMM_WORLD,&rango); MPI_Comm_size (MPI_COMM_WORLD, &nprocs); /* Se llama al procedimiento Allgather */ todo(rango,nprocs); MPI_Finalize();

}

Impresión en pantalla:

rango 0 y tengo 3rango 1 y tengo 5rango 2 y tengo 7rango 0 y tengo 3rango 1 y tengo 5rango 2 y tengo 7rango 0 y tengo 3rango 1 y tengo 5rango 2 y tengo 7

4.6 Reducción (Reduce y Allreduce).

La rutina de reducción nos ayuda a realizar operaciones de cálculo (tabla 2 ) con los datos distribuidos que existen dentro de los procesadores disponibles en el comunicador.

Por ejemplo, si cualquier procesador toma un entero, la reducción global puede ser usada para encontrar el total de la suma o producto, el máximo valor o el rango del procesador con el máximo valor.

Operaciones Descripción

MPI_MAX Retorna el elemento mayor.

MPI_MIN Retorna el elemento menor.

MPI_SUM Suma

MPI_PROD Producto

MPI_LAND And lógico

MPI_BAND And

MPI_LOR Or lógico

MPI_LXOR Or exclusivo lógico

MPI_BXOR Or exclusivo

MPI_MAXLOC Retorna el elemento mayor y la localidad.

MPI_MINLOC Retorna el elemento menor y la localidad.

Tabla 2 Operadores predefinidos que puede tomar la función MPI_Reduce.

Page 38: CD Unidad3 Con C

Podemos con las operaciones predefinidas por MPI, obtener:

La suma de 2 enteros, El producto de 2 números reales, El máximo de 2 enteros, El producto de 2 matrices cuadradas... etc.

La función MPI_Allreduce es una variante de MPI_Reduce, la diferencia es que el resultado que retorna se difunde a todos los procesadores que constituyen al comunicador. Por lo tanto no contiene el parámetro de raiz.

La función reduce y Allreduce tienen la siguiente sintaxis:

int MPI_Reduce (void *buffer_env, void *buffer_rcv, int cont, MPI_Datatype tipodato, MPI_Op operador, int raiz, MPI_Comm com);

int MPI_Allreduce (void *buffer_env, void *buffer_rcv, int cont,MPI_Datatype tipodato, MPI_Op operador, MPI_Comm com);

donde:Variable de entrada buffer_env es la dirección donde inicia el dato Variable de salida buffer_rcv es la dirección de inicio del buffer de recepción.Variable de entrada cont es el número de elementos en el buffer de envío.Variable de entrada tipodato es el tipo de dato de cada elementos dentro del buffer a enviar.Variable de entrada operador es la operación de reducción.Variable de entrada raiz es el procesador que contendrá el resultado de la operación.Variable de entrada com es el comunicador que contiene a los procesadores.

Ejemplo:

MPI_Reduce (&buffer_env, &buffer_rcv, cont, tipodato, operador, raiz, MPI_COMM_WORLD); MPI_Allreduce (&buffer_env, &buffer_rcv, cont, tipodato, operador, MPI_COMM_WORLD);

Programa 12. Uso de la función MPI_Reduce.

Descripción: En este programa, la función MPI_Reduce sumara un número flotante tantas veces como el número de procesadores que contenga el comunicador.

#include <mpi.h> #include <stdio.h> void Reduce (int mi_rango){ float total=0.0, num=3.2; /* Función que suma según el número de procesadores que contenga el comunicador */ MPI_Reduce(&num, &total,1,MPI_FLOAT, MPI_SUM, 0,MPI_COMM_WORLD); if (mi_rango == 0){ printf("El total de la suma es:%f\n ", total); } } main (int argc, char *argv [ ]){ int rango, nprocs; /* Se da inicio al ambiente paralelo */ MPI_Init (&argc, &argv); MPI_Comm_rank (MPI_COMM_WORLD,&rango);

Page 39: CD Unidad3 Con C

MPI_Comm_size (MPI_COMM_WORLD, &nprocs); /* Se llama al procedimiento Reduce */ Reduce (rango); MPI_Finalize(); }

Impresión en pantalla:$ mpirun -np 40 prog12El total de la suma es: 128.000000

4.7 Topologías virtuales.

Cuando varios procesadores de una computadora paralela son conectados entre si lo hacen por medio de una topología o una red de interconexión. MPI provee herramientas para conectar los procesadores de un grupo por medio de topologías.

Las topologías son construidas sobre los procesadores que estén contenidos en el comunicador. Los tipos de topología principales que son construidos sobre el grupo comunicador son:

• Malla cartesiana (grid o cartesian). • De grafos (Graph).

Cada elemento de la descomposición esta nombrado por una par ordenado indicando la posición de los elementos en cada dirección de las coordenadas.

MPI provee una colección de rutinas para definir, examinar y manipular topologías cartesianas, en este trabajo se verán las funciones básicas:

• MPI_Dims_create( ) • MPI_Cart_create ( ) • MPI_Cart_coords ( )

• MPI_Dims_create( )

int MPI_Dims_create (int num_nodos, int num_dims, int *dim);

donde:

Variable de entrada num_nodos es el número de nodos que tendrá la malla.Variable de entrada num_dims número de dimensiones que tendrá la malla. Variable de salida dim arreglo que contendrá las especificaciones de largo y ancho de la

malla, esta no debe exceder de num_dims dimensiones ni de num_nodos.

Ejemplo:

MPI_Dims_create (num_nodos, num_dims, &dim);

Page 40: CD Unidad3 Con C

La malla que se crea es una malla cartesiana de num_dims dimensiones (sí num_dims=2 por lo tanto la malla será bidimensional, num_dims=3 entonces la malla será tridimensional) y un número de nodos num_nodos .

• MPI_Cart_create ().

int MPI_Cart_create (MPI_COMM com, int ndim, int *dims, int *periodos, int orden, MPI_Comm com_cart);

donde:

Variable de entrada com es la entrada del comunicador.Variable de entrada ndim indica el número de dimensión de la malla cartesiana.Variable de entrada dims es un arreglo de tamaño ndim, específica el número de

procesadores en cada dimensión.Variable de entrada periodos es un arreglo lógico de tamaño de ndims, específica si la malla es

periódica o no en cada dimensión.Variable de entrada orden retorna verdadero o falso. verdadero puede ser reordenado; falso

entonces el rango en com_cart es el mismo de com_old (este argumento por lo general es ignorado).

Variable de salida com_cart es el comunicador con la nueva topología cartesiana.

Ejemplo:

MPI_Cart_create (MPI_COMM_WORLD, ndim, dims, periodos, order, com_cart);

Esta función crea un nuevo comunicador que contiene información de una topología cartesiana, la cual esta definida por ndim, dims, periodos y orden. Es decir, se crea un comunicador con la nueva organización que los procesadores han tomado en la malla.

• MPI_Cart_coords()

int MPI_Cart_coords (MPI_Comm com, int rango, int maxdim, int *coords);

donde:

Variable de entrada com es un comunicador con topología cartesiana.Variable de entrada rango es el rango de un procesador dentro del grupo comm.Variable de entrada maxdim indica las dimensiones de la malla.Variable de entrada coords son las coordenadas de un procesador en la malla.

Ejemplo:

MPI_Cart_coords (comm, rango, maxdim, coords);

Esta función nos ofrece las coordenadas de un procesador n, dentro del nuevo comunicador cartesiano com que se creo con la función MPI_Cart_create. Las coordenadas se guardan en un arreglo coords donde se localizan cada uno los procesadores.

Programa 13. Creación de una topología bidimensional y tridimensional.

Page 41: CD Unidad3 Con C

Descripción: Este programa muestra la manera de formar una topología malla bidimensional y una malla tridimensional.

Malla bidimensional:/*Creación de una estructura n dimensiones */ #include <stdio.h> #include <mpi.h> #include <sys/utsname.h> void main (int argc, char *argv[]){ int rango, nprocs; struct utsname maquina; MPI_Comm nuevo_comm; /* El número de dimensiones de la estructura */ #define DIMENSIONES 2 /* Aquí se colocan las dimensiones de la malla */ #define X_SIZE 4 #define Y_SIZE 3 int dims[DIMENSIONES]={Y_SIZE}, /* Dos dimensiones */ Periodos[DIMENSIONES]={1}, /* verdadero */ Reorder=1, /* Si el rango puede ser reordenado */ Num_Nodos = X_SIZE * Y_SIZE ; int Coordenadas[DIMENSIONES]; /* Se inicializa el ambiente paralelo */ MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rango); MPI_Comm_size(MPI_COMM_WORLD, &nprocs); /* Crea una topología cartesiana. */ MPI_Dims_create (Num_Nodos, DIMENSIONES, dims); /* Crea un nuevo comunicador para la malla cartesiana*/ MPI_Cart_create (MPI_COMM_WORLD, DIMENSIONES, dims, Periodos, Reorder, &nuevo_comm); /* Para obtener las coordenadas de cada nodo */ MPI_Cart_coords(nuevo_comm, rango, DIMENSIONES, Coordenadas); /* Imprime las dimensiones */ if (rango == 0) printf("Se creó una malla de %i x %i \n\n", X_SIZE, Y_SIZE); if (uname(&maquina) < 0) { perror("Error al obtener el nombre de un procesador...\n"); exit(1); } /* Se imprime las coordenadas */ printf("Rango %i: %s, en %i,%i\n", rango, maquina.nodename, Coordenadas[0], Coordenadas[1]); MPI_Finalize(); }

Impresión en pantalla:

$mpirun -np 12 prog13Se creó una malla de 4 x 3Rango 4: spn21, en 1,0Rango 8: cw, en 2,0

Page 42: CD Unidad3 Con C

Rango 1: spn18, en 0,1Rango 3: spn20, en 0,3Rango 5: spn22, en 1,1Rango 9: spn17, en 2,1Rango 2: spn19, en 0,2Rango 6: spn23, en 1,2Rango 10: spn18, en 2,2Rango 11: spn19, en 2,3Rango 7: spn24, en 1,3Rango 0: spn17, en 0,0

spn17spn18spn19spn20spn21spn22spn23spn24cwspn17spn18spn19Malla Tridimensional:/*Creación de una estructura n dimensiones */ #include <stdio.h> #include <mpi.h> #include <sys/utsname.h> void main (int argc, char *argv[]){ int rango, nprocs; struct utsname maquina; MPI_Comm nuevo_comm; /* El número de dimensiones de la estructura */ #define DIMENSIONES 3 /* Aquí se colocan las dimensiones de la malla */ #define X_SIZE 2 #define Y_SIZE 2 #define Z_SIZE 2 int dims[DIMENSIONES]={X_SIZE,Y_SIZE}, /* Dos dimensiones */ Periodos[DIMENSIONES]={1}, /* verdadero */ Reorder=1, /* Si el rango puede ser reordenado */ Num_Nodos = X_SIZE * Y_SIZE * Z_SIZE; int Coordenadas[DIMENSIONES]; /* Se inicializa el ambiente paralelo */ MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rango); MPI_Comm_size(MPI_COMM_WORLD, &nprocs); /* Crea una topología cartesiana. */ MPI_Dims_create (Num_Nodos, DIMENSIONES, dims); /* Crea un nuevo comunicador para la malla cartesiana*/ MPI_Cart_create (MPI_COMM_WORLD, DIMENSIONES, dims, Periodos, Reorder, &nuevo_comm); /* Para obtener las coordenadas de cada nodo */ MPI_Cart_coords(nuevo_comm, rango, DIMENSIONES, Coordenadas); /* Imprime las dimensiones */ if (rango == 0) printf("Se creó una malla de %i x %i x %i\n\n", X_SIZE, Y_SIZE , Z_SIZE); if (uname(&maquina) < 0) { perror("Error al obtener el nombre de un procesador...\n"); exit(1); } /* Se imprime las coordenadas */

Page 43: CD Unidad3 Con C

printf("Rango %i: %s, en %i,%i,%i\n", rango, maquina.nodename, Coordenadas[0], Coordenadas[1], Coordenadas[2]); MPI_Finalize(); }

Impresión en pantalla:

$ mpirun -np 8 prog14Se creó una malla de 2 x 2 x 2Rango 1: spn18, en 0,0,1Rango 2: spn19, en 0,1,0Rango 4: spn21, en 1,0,0Rango 5: spn22, en 1,0,1Rango 3: spn20, en 0,1,1Rango 6: spn23, en 1,1,0Rango 7: spn24, en 1,1,1Rango 0: spn17, en 0,0,0

Este arreglo puede ser visto como un 2-Cubo.

Page 44: CD Unidad3 Con C

5.- Tipos de datos derivadosLos tipos de datos básicos que se presentaron en el punto 1.2.1 son fácilmente transferibles de una plataforma a otra, ya que estos ocupan de manera consecutiva una región de memoria.

MPI ofrece un conjunto de funciones para realizar operaciones sobre un tipo de dato definido por los usuarios, a través de combinaciones con los tipos predefinidos. Los tipos de datos derivados permiten especificar datos no contiguos de una manera transparente y se maneja como si el dato se encontrará de manera consecutiva en memoria.

MPI provee muchos métodos de construcción de tipos de datos derivado:

• Contiguos. • Vector. • Indexed (ordenamiento). • Struct (estructura).

El primer método (contiguos) construye un tipo de dato nuevo cuyos elementos son copiados para formar un arreglo de elementos consecutivos. El segundo (vector), construye un tipo de dato, cuando los elementos se encuentran espaciados dentro de un arreglo y el tercero ( indexed) construye un tipo de dato cuyos elementos tienen entradas arbitrarias a un arreglo, es decir se requiere formar un nuevo arreglo de los elementos que se encuentran en diferentes posiciones. El Struct nos permite crear un tipo de dato nuevo con diferentes elementos y datos de una estructura de datos.

En cualquier caso, los nuevos tipos de datos han de ser confirmados con MPI_Type_commit(). Si un tipo no esta confirmado no puede ser utilizado.int MPI_Type_commit (MPI_Datatype *tipodato);

5.1 Tipos de datos consecutivos.

int MPI_Type_contigous (int cont, MPI_Datatype datoinicial, MPI_Datatype *datonuevo);

donde:

Variable de entrada cont es el número de elementos a replicar.Variable de entrada datoinicial tipo de dato inicial.Variable de salida datonuevo tipo de dato nuevo.

Ejemplo:

MPI_Type_contigous (cont, MPI_INTERGER, &datonuevo);

Un tipo de dato contiguo construye un dato que consiste en la replicación de cierto tipo de dato dentro de una localidad continua de memoria. El nuevo tipo que se forma (datonuevo) es el resultado de la concatenación de n copias (cout) del viejo tipo de datos (datoinicial).Un ejemplo es el siguiente [5].

Page 45: CD Unidad3 Con C

Fig. 5.1 Funcionamiento de la función MPI_Type_contiguos.

5.2 Tipo de dato Vector.

Esta función crea un tipo de dato nuevo a partir de un desplazamiento constante dentro de un arreglo.

int MPI_Type_vector (int cont, int num_bloque, int despl, MPI_Datatype datoinicial, MPI_Datatype *datonuevo);

donde:

Variable de entrada cont es el número de elementos que forman el arreglo.Variable de entrada num_bloque número de elementos a copiar al nuevo tipo de dato.Variable de entrada despl es un arreglo de desplazamientos.Variable de entrada datoinicial tipo de dato inicial.Variable de salida datonuevo dirección del tipo de dato nuevo.

Ejemplo:

MPI_Type_vector (cont, num_bloque, despl, datoinicial, &datonuevo);

Crea un tipo derivado que consiste de cont elementos. Cada elemento comprende de num_bloque entradas de tipo datoinicial. despl es el número de elementos de tipo datoinicial entre elementos consecutivos de datonuevo (figura 5.2).

Fig. 5.2 Tipo de dato vector.

5.3 Tipo de dato Indexed.

Esta función crea un tipo de dato nuevo a partir de elementos seleccionados de un arreglo.

int MPI_Type_indexed (int cont, int *num_arreglo, int *despl, MPI_Datatype datoinicial,

Page 46: CD Unidad3 Con C

MPI_Datatype *datonuevo);

donde:

Variable de entrada cont número de elementos del arreglo.Variable de entrada num_arreglo número de elementos a copiar al nuevo tipo de dato.Variable de entrada despl es un arreglo de desplazamientos.Variable de entrada datoinicial tipo de dato inicial.Variable de salida datonuevo tipo de dato nuevo.

Ejemplo:

MPI_Type_indexed (cont, &num_arreglo, despl, datoinicial, &datonuevo);

MPI_Type_indexed crea un tipo derivado que consiste en cont elementos. Los n-ésimos elementos (n=0,1,... cont-1), que constituyen los num_arreglo[n] entradas de tipo datoinicial, y los desplazamientos despl [n] son unidades de tipo datoinicial que se darán desde el principio para el datonuevo.

Fig. 5.3 Tipo de indexed.

5.4 Tipo de dato Struct.

La función struct permite crear un tipo de dato nuevo a partir de elementos seleccionados de una estructura.

int MPI_Type_struct (int cont, int *num_arreglo, MPI_Aint *despl, MPI_Datatype *datoinicial,MPI_Datatype *datonuevo);

donde:

Variable de entrada cont Número de elementos de la estructura.Variable de entrada num_arreglo Número de elementos a copiar al nuevo tipo de dato.Variable de entrada despl es un arreglo de desplazamiento (bytes)Variable de entrada datoinicial tipo de dato inicial.Variable de salida datonuevo es el nuevo tipo de dato.

Ejemplo:

MPI_Type_struct (count, &num_arreglo, &despl, &datoinicial, &datonuevo);

Page 47: CD Unidad3 Con C

La función MPI_Type_struct permite crear nuevos tipos de datos de una estructura, si es necesario transmitir datos de tipos diferentes.

Ejemplo:

struct piezas {int cantidad; char serie[max];double bodega;int salidas;

}empresa [max];MPI_Datatype datonuevo;

Fig. 6.4 Tipo de dato estructura (struct).

Programa 14. Un tipo de dato derivado.

Descripción: Este programa crea una matriz de 8 x 8; se necesita sumar solo los elementos que conforman la parte inferior triangular de la matriz, por lo que se recurre a una función que nos permita crear un tipo de dato con diferentes desplazamientos dentro de la matriz: MPI_Type_indexed [8].

/* Programa que permite formar un nuevo MPI_Datatype usando la función MPI_Type_indexed

Objetivo: Enviar la matriz triangular inferior. */ #include <mpi.h> #include <stdio.h> #define max 8 double a[max][max]; int despl[max], elcont[max]; MPI_Datatype NVO_TIPO; void main (int argc, char *argv[]){ double suma; int rango,nprocs,destino,fuente; int n, m, bandera; MPI_Status estado; MPI_Request envio; MPI_Request receptor; MPI_Init (&argc, &argv); MPI_Comm_size (MPI_COMM_WORLD, &nprocs); MPI_Comm_rank (MPI_COMM_WORLD, &rango); if ( nprocs != 2) { printf("\n Error\n");

Page 48: CD Unidad3 Con C

exit(0); } /* Se crean los arreglos de desplazamientos y los números de datos que debe ser incluidos */ for(n=0; n<max; n++){ despl[n] = n * max; /* desplazamientos */ elcont[n]= n + 1; /* Elementos que contendrá el nuevo tipo de dato */ } /* Se crea el nuevo tipo de dato con los desplazamientos y los elementos útiles se genera el nuevo tipo de dato conteniendo el numero de desplazamientos y los elementos que serán necesarios tomar de la matriz */ MPI_Type_indexed(max, elcont, despl, MPI_DOUBLE, &NVO_TIPO); MPI_Type_commit (&NVO_TIPO); /* Se confirma el nuevo tipo de dato*/ switch (rango){ case 0: destino=1; fuente=1; for (n=0; n<max; n++) for (m=0; m<max; m++) a[n][m]= m + n; /*Se inicializa la matriz */ /* Se envía el tipo derivado al procesador uno para su suma*/ MPI_Isend (a, 1, NVO_TIPO, destino, 1 , MPI_COMM_WORLD, &envio); printf ("la matriz transpuesta inferior ha sido enviada \n"); MPI_Irecv (&suma,1,MPI_DOUBLE, fuente, 1, MPI_COMM_WORLD, &receptor); /*Espera respuesta */ MPI_Wait (&receptor, &estado); printf ( "La suma es: %f \n", suma); break; case 1: destino=0; fuente=0; for (n=0; n<max; n++) for (m=0; m<max; m++) /*Se inicializa la matriz para el procesador con rango=1*/ a[n][m]=0; /* Se reciben los datos que contiene el nuevo tipo de dato */ MPI_Irecv (a,1, NVO_TIPO , destino, 1, MPI_COMM_WORLD, &receptor); /* Espera respuesta */ do{ MPI_Test (&receptor, &bandera, &estado); }while (!bandera); suma = 0; for ( n=0; n < max; n++) for ( m=0; m < max; m++) suma += a[n][m]; /* Envía el resultado de la suma de todos los nodos al procesador 0 */ MPI_Send (&suma,1,MPI_DOUBLE, fuente,1, MPI_COMM_WORLD); break; } MPI_Finalize( ); }

Impresión en pantalla:

$ mpirun -np 2 prog14La matriz transpuesta inferior ha sido enviada.La suma es: 252.000000

Utilizar la función MPI_Type_indexed permite crear un nuevo tipo de dato (matriz) con los datos requeridos y que no se encuentran en orden consecutivo dentro de la memoria. El tipo de dato nuevo

Page 49: CD Unidad3 Con C

NVO_TIPO contendrá un arreglo con los desplazamientos y el número de elementos que se tomarán de la matriz.

0123456701234567819.......217.......325.......433.......541.......649.......757.......De esta manera solo se sumaran al final los elementos que corresponden a NVO_TIPO.