• No se han encontrado resultados

Proyecto de Investigación I y II

N/A
N/A
Protected

Academic year: 2021

Share "Proyecto de Investigación I y II"

Copied!
165
0
0

Texto completo

(1)

Universidad Autónoma

Metropolitana

Iztapalapa

Proyecto de Investigación I y II

“Migración de datos entre nodos de un

cluster”

Ciencias Básicas e Ingeniería

Licenciatura en Computación

Autores:

Fernando Fernández Vergara

José Bernardo Hipatl Santiago

Asesor: Dra. Graciela Román Alonso

Este proyecto fue desarrollado en el marco del Proyecto CONACyT 342330-A Infraestructura para la

construcción de aplicaciones fuertemente distribuidas.

(2)

Agradecimientos

Agradecemos infinitamente a nuestros padres que nos hayan apoyado

incondicionalmente a lo largo de nuestra carrera, dándonos palabras de

aliento en momentos difíciles y que han hecho grandes sacrificios por que

nosotros cumplamos nuestras metas. Agradecemos también a nuestros

profesores que nos han transmitido su conocimiento adquirido a lo largo no

sólo de su carrera sino de experiencias personales. También damos las

gracias a nuestro asesor la Dra. Graciela Román Alonso por habernos tenido

paciencia.

Agradecemos a los Laboratorios de Docencia y Laboratorio de Súper

cómputo a cargo del Ing. Juan Carlos Rosas Cabrera, por el apoyo

proporcionado al facilitar los recursos necesarios para el desarrollo de gran

parte de este proyecto. Así como al laboratorio de Sistemas Distribuidos del

área de Computación de Sistemas de la UAM-I por facilitar sus recursos en

el desarrollo de este proyecto.

(3)

Tipografía

En este documento el código del lenguaje C y de las rutinas de MPI utilizadas en

código en C, se representan en color azul. El uso de las negritas es utilizado en

comandos y/o opciones de comandos en el sistema operativo, así como en los prototipos

de las rutinas de MPI extraídos de los archivos de inclusión. Se incluyen algunos

mensajes de error generados por el sistema en la línea de comandos, en color rojo, las

letras cursivas sólo son usadas para hacer referencia a términos ya definidos o tratados

en temas anteriores.

(4)

TABLA DE CONTENIDOS

1. INTRODUCCION ...7

1.1. Objetivo ...7

1.2. Justificación ...7

2. CONCEPTOS BÁSICOS ...9

2.1. Clasificación de las arquitecturas de computadoras ...9

2.1.1. SISD (Single Instruction Single Data)...9

2.1.2. SIMD (Single Instruction Multiple Data) ...9

2.1.3. MIMD (Multiple Instruction Multiple Data) ...9

2.2. Clusters...12

2.2.1. Definiciones básicas...12

2.2.2. Funcionamiento de un Cluster ...13

2.2.3. Tipos de Clusters...13

2.2.4. Características importantes de los Clusters...14

2.2.5. Middleware ...14

2.3. Programación con MPI (Message Passing Interface) ...16

2.3.1. Introducción a MPI ...16

2.3.2. Características ...16

2.3.3. Elementos Básicos de la Programación de MPI ...18

2.3.4. Compilación de programas MPI ...27

2.3.5. Ejecución de Programas MPI...29

2.4. Balance de Carga ...31

3. MIGRACIÓN DE DATOS...34

3.1. Definición e Implementación de un TDA lista balanceable ...35

3.1.1. Implementación...36

3.2. Envío/Recepción de un nodo de un TDA lista...38

3.2.1. Envío/Recepción del nodo Campo por Campo (Versión 1 “camxcam.c”)...39

3.2.2. Envío/Recepción nodo empaquetado (Versión 2.1 “empaketado.c”)...41

3.2.3. Envío/Recepción TDA empaquetado (Versión 2.2 “empaketado_b.c”) ...44

3.2.4. Envío/Recepción de un nodo usando uniones (Versión 3.1 “uniones.c”) ...47

3.2.5. Envío/Recepción de un nodo usando uniones (Versión 3.2 “uniones_b.c”) ...49

4. RECONOCIMIENTO DEL TDA LISTA BALANCEABLE...53

4.1 Algoritmo ...54

4.2. Implementación...55

4.2.1. Obtención de los bloques ...55

4.2.2. Verificación de los bloques...55

4.2.3. Registro de la Estructura ...56

4.2.4. Creación del archivo de Salida...56

5. MIGRACIÓN DE DATOS COMPARTIDOS POR HILOS...58

5.1. Uso de Hilos en MPI ...58

5.1.1. hilos_1.c ...59

5.1.2. hilos_2.c ...61

5.2. Sincronización para la lista balanceable ...63

5.2.1. hilos_3.c ...64

(5)

5.3. Rutinas para el balance de carga (versión final) ...75

6. RESULTADOS...77

6.1. Desempeño del programa hilos_3.c 1ª versión...77

6.2. Desempeño del programa hilos_3.c 2ª versión...77

7. CONCLUSIONES Y PERSPECTIVAS ...79

8. BIBLIOGRAFIA ...82

9. ANEXOS ...84

9.1. Anexo A (Codigos Fuentes para la migración de datos) ...84

Listado “protos.h” ...84

Listado “operlis.c” ...85

Listado “ent_sal.c” ...87

Listado “val_azar.c”...88

Listado “camxcamp.c” ...89

Listado “empaketado.c” ...91

Listado “empaketado_b.c” ...94

Listado “uniones.c” ...98

Listado “uniones_b.c” ...100

9.2. Anexo B (Códigos Fuentes para el Reconocimiento del TDA Lista)...103

Listado “scan.c” ...103

9.3. Anexo C (Códigos Fuentes para Migración de datos compartidos con hilos)...112

Listado “hilos_1.c”...112

Listado “hilos_2.c”...115

Listados “Primera versión” ...119

Listado “protos.h” ...120

Listado “def_TDA.h”...122

Listado “balance.c” ...122

Listado “hilos_3.c”...125

Listado “operlis.c” ...129

Listado “ent_sal.c” ...131

Listados “Segunda Versión” ...132

Listado “protos.h” ...132

Listado “def_TDA.h”...132

Listado “balance.c” ...132

Listado “Hilos_3.c”...134

Listado “operlis.c” ...135

Listado “ent_sal.c” ...135

Listado “MPI_lista.c”...135

Listado “scan.c” ...137

9.4. Anexo D (Códigos Fuentes Versión final)...147

Listado “protos.h” ...147

Listado “MPI_lista.orig” ...148

Listado “balance.c” ...149

Listado “scan.c” ...152

(6)
(7)

1. INTRODUCCIÓN

1.1. Objetivo

Realizar un trabajo de investigación en el área de sistemas distribuidos, con el fin de dar una

propuesta de un mecanismo de balance de datos haciendo uso de la herramienta MPI.

1.2. Justificación

En la actualidad los grandes volúmenes de información en formato digital, así como los pesados

cálculos para la investigación científica que llevan a cabo las universidades y empresas en nuestro país,

han obligado a éstas a buscar herramientas que hagan más rápidas y por tanto eficientes sus

investigaciones. Una alternativa es el uso de las supercomputadoras, el cual representa un problema, ya

que el costo de las mismas es muy elevado, costo que muchas instituciones no pueden solventar. Pero,

no es la única opción, ni mucho menos la mejor, por lo menos en el aspecto económico.

Otra alternativa lo son los cluster, que en muchos aspectos y en gran medida son la mejor

opción para la investigación que se desarrolla en las universidades como la nuestra. Pero esta opción,

también tiene sus desventajas, entre las más importantes se encuentra la necesidad de personal

calificado para lograr un buen desempeño en la paralelización de aplicaciones, personal que es

capacitado casi en su totalidad por instituciones educativas como la nuestra.

Pero el buen desempeño de una aplicación paralela no depende sólo de una buena codificación,

además esta en gran medida en función de una buena administración del cluster, para que le mismo

rinda al máximo. El principal problema de un cluster en una institución como la nuestra es que no sólo

es utilizado para el procesamiento de una aplicación paralela, lo cual sería absurdo, ya que el cluster no

sería aprovechado a su máximo, también se da el caso que hay clusters que también son usados para el

desempeño de las actividades docentes. Es aquí cuando surgen nuevos problemas, ya que el buen

desempeño de las aplicaciones paralelas, depende en gran medida de la carga de trabajo de los nodos

del cluster.

Existen herramientas que auxilian en la administración de un cluster, y entre las tareas que

desempeñan están, la detección de nodos con una gran carga de trabajo, para que los procesos sean

lanzados en los nodos con menor carga. Pero, ¿qué pasa si se lanza una aplicación en un nodo que

inicialmente no se encuentra cargado, pero que posteriormente se carga, ya sea por el uso del nodo

como estación de trabajo o por la gran cantidad de datos de nuestra misma aplicación? Pues la

respuesta es obvia, nuestro proceso paralelo se verá afectado por la sobrecarga. Por otra parte nosotros

al codificar una aplicación paralela, si pensamos en prever la situación anterior, nos haremos

complicada nuestra tarea de paralelización más de lo que ya es. Ya que no sólo habría que codificar

rutinas para censar la carga del nodo, sino además la forma de migrar nuestros datos para que el

procesamiento llevado hasta el momento no sea en vano. Por tanto el objetivo de este proyecto es

contribuir al desarrollo de las rutinas encargadas del balance de la carga de datos para nuestras

aplicaciones paralelas.

(8)
(9)

2. CONCEPTOS BÁSICOS

2.1. Clasificación de las arquitecturas de computadoras

[2]

La clasificación de las arquitecturas de computadoras fue propuesta por Michael Flynn y se

basa en basa en el número de instrucciones y de la secuencia de datos que la computadora utiliza para

procesar información. Puede haber secuencias de instrucciones sencillas o múltiples y secuencias de

datos sencillas o múltiples. Esto da lugar a 4 tipos de computadoras, de las cuales solamente dos son

aplicables a las computadoras paralelas.

2.1.1. SISD (Single Instruction Single Data)

Este es el modelo tradicional de computación secuencial donde una unidad de procesamiento

recibe una sola secuencia de instrucciones que operan en una secuencia de datos.

Por ejemplo para procesar la suma de N números

a

1

, a

2

,... a

N, el procesador necesita acceder a

memoria N veces consecutivas (para recibir un número) También son ejecutadas en secuencia N-1

adiciones. Es decir los algoritmos para las computadoras SISD no contienen ningún paralelismo, éstas

están constituidas de un procesador.

2.1.2. SIMD (Single Instruction Multiple Data)

A diferencia de SISD, en este caso se tienen múltiples procesadores que sincronizadamente

ejecutan la misma secuencia de instrucciones, pero en diferentes datos. El tipo de memoria que estos

sistemas utilizan es distribuida.

Aquí hay N secuencias de datos, una por procesador, así que diferentes datos pueden ser

utilizados en cada procesador. Los procesadores operan sincronizadamente y un reloj global se utiliza

para asegurar esta operación. Es decir, en cada paso todos lo procesadores ejecutan la misma

instrucción, cada uno en diferente dato.

2.1.3. MIMD (Multiple Instruction Multiple Data)

Este tipo de computadora es paralela al

igual que las SIMD, la diferencia con estos

sistemas es que MIMD es asíncrono. No tiene un

(10)

característica es la más general y poderosa de esta clasificación.

Se tienen N procesadores, N secuencias de instrucciones y N secuencias de datos. Si el sistema

de multiprocesamiento posee procesadores de aproximadamente igual capacidad, estamos en presencia

de multiprocesamiento simétrico, en el otro caso hablamos de multiprocesamiento asimétrico. Cada

procesador opera bajo el control de una secuencia de instrucciones, ejecutada por su propia unidad de

control, es decir cada procesador es capaz de ejecutar su propio programa con diferentes datos. Esto

significa que los procesadores operan asincrónicamente, o en términos simples, pueden estar haciendo

diferentes cosas en diferentes datos al mismo tiempo.

Los sistemas MIMD se clasifican en:

Sistemas de Memoria Compartida.

Sistemas de Memoria Distribuida.

Sistemas de Memoria Compartida Distribuida

En este tipo de sistemas cada procesador tiene acceso a toda la memoria, es decir hay un espacio

de direccionamiento compartido. Se tienen tiempos de acceso a memoria uniformes ya que todos los

procesadores se encuentran igualmente comunicados con la memoria principal y las lecturas y

escrituras de todos los procesadores tienen exactamente las mismas latencias; Y además el acceso a

memoria es por medio de un conducto común. En esta configuración, debe asegurarse que los

procesadores no tengan acceso simultáneamente a regiones de memoria de una manera en la que pueda

ocurrir algún error.

Desventajas:

El acceso simultáneo a memoria es un problema.

En PC’s y estaciones de trabajo:

Todos los CPU’s comparten el camino a

memoria.

Un CPU que acceda la memoria, bloquea el

acceso de todos los otros CPU’s.

En computadoras vectoriales como Crays, etc.

Todos los CPU’s tienen un camino libre a la

memoria.

No hay interferencia entre CPU’s.

La razón principal por el alto precio de Cray

es la memoria.

Ventaja:

La facilidad de la programación. Es mucho más fácil programar en estos sistemas que en

sistemas de memoria distribuida.

Sistemas de Memoria Distribuida

Estos sistemas tienen su propia memoria local. Los procesadores pueden compartir información

solamente enviando mensajes, es decir, si un procesador requiere los datos contenidos en la memoria

de otro procesador, deberá enviar un mensaje solicitándolos. Esta comunicación se le conoce como

Paso de Mensajes

.

V

entajas:

La escalabilidad. Las computadoras con sistemas de memoria distribuida son fáciles de escalar,

mientras que la demanda de los recursos crece, se puede agregar más memoria y procesadores.

Desventajas:

(11)

El acceso remoto a memoria es lento.

La programación puede ser complicada.

Las computadoras MIMD de memoria distribuida son conocidas como

sistemas de procesamiento

en paralelo masivo

(MPP) donde múltiples procesadores trabajan en diferentes partes de un programa,

usando su propio sistema operativo y memoria. Además se les llama multicomputadoras, máquinas

libremente juntas o

cluster.

Sistemas de Memoria Compartida Distribuida

Es un cluster o una partición de procesadores que tienen acceso a una memoria compartida

común pero sin un canal compartido. Esto es, físicamente cada procesador posee su memoria local y se

interconecta con otros procesadores por medio de un dispositivo de alta velocidad, y todos ven las

memorias de cada uno como un espacio de direcciones globales.

El acceso a la memoria de diferentes clusters se realiza bajo el esquema de Acceso a Memoria

No Uniforme (NUMA), la cual toma menos tiempo en acceder a la memoria local de un procesador que

acceder a memoria remota de otro procesador.

Ventajas:

Presenta escalabilidad como en los sistemas de memoria distribuida.

Es fácil de programar como en los sistemas de memoria compartida.

(12)

2.2. Clusters

[5]

El término cluster es usado generalmente para referirnos a un grupo de componentes

distribuidos que colaboran en conjunto para presentarse al usuario como un solo sistema.

Un Cluster es un conjunto o conglomerado de computadoras interconectadas entre sí de alguna

manera, que trabajan en conjunto, distribuyéndose las tareas entre ellas, logrando que el usuario lo vea

como una sola, simulando una sola computadora muy potente. Son construidos utilizando componentes

de hardware comunes, lo que los hace un poco más accesibles y con el software que en su mayoría se

apega a la licencia GNU. El principal papel que juegan es el de proporcionar soluciones a la

investigación científica, que requieren mucho tiempo de procesamiento y de cálculos pesados, aunque

también son usados en las ingenierías y en las aplicaciones

comerciales.

2.2.1. Definiciones básicas

Rendimiento:

Es la efectividad del desempeño de una computadora, sobre una aplicación o un

benchmark en particular.

Flops:

Es una medida de la velocidad del procesamiento numérico del procesador. Son

operaciones de punto flotante por segundo.

Alto Rendimiento (HPC):

Gran demanda de procesamiento de datos en procesadores, memoria

y otros recursos de hardware, donde la comunicación entre ellos es muy rápida.

Latencia:

Tiempo de transferencia de mensajes de una interfaz a otra.

Ancho de Banda:

Capacidad de transferencia que tiene un canal de comunicaciones en una

unidad de tiempo.

(13)

Switches:

Es un conjunto de puertos de entrada, un conjunto de puertos de salida y una red

cruzada interna (crossbar) que conecta cada entrada a cada salida, el buffering interno, y el control

lógico para efectuar la conexión de entrada y salida en cada punto de tiempo (malla)

Hub:

es un conjunto de puertos de entrada y salida multiplexado (un alambre)

Ethernet:

Protocolo de comunicación basado en el estándar IEEE.

VIA:

Protocolo de comunicación con características de baja latencia.

2.2.2. Funcionamiento de un Cluster

En su parte central, la tecnología de Clusters consta de dos partes. La primera componente,

consta de un sistema operativo confeccionado especialmente para esta tarea, un conjunto de

compiladores y aplicaciones especiales, que permiten que los programas que se ejecutan sobre esta

plataforma tomen las ventajas de esta tecnología de Clusters.

La segunda componente es la interconexión de hardware entre las máquinas (nodos) del Cluster.

Se han desarrollado interfaces de interconexión especiales muy eficientes, pero comúnmente las

interconexiones se realizan mediante una red Ethernet dedicada de alta velocidad. Es mediante esta

interfaz que los nodos del Cluster intercambian entre sí asignación de tareas, actualizaciones de estado

y datos del programa. Existe otra interfaz de red que conecta al Cluster con el mundo exterior. Un nodo

esencialmente se compone de:

Procesador.

Memoria.

Dispositivos de almacenamiento.

Tarjeta de Red.

2.2.3. Tipos de Clusters

[5]

Beowulf

Concepto de los primeros clusters

(componentes COTS, filosofía “Hágalo usted mismo”). En

1994, se integró el primer cluster de PC’s en el Centro de

Vuelos Espaciales Goddard de la NASA, para resolver

problemas computacionales que aparecen en las ciencias de

la Tierra y el Espacio. Los pioneros de este proyecto fueron

Thomas Sterling, Donald Becker y otros científicos de la

NASA. El cluster de PC’s desarrollado tuvo una eficiencia

de 70 megaflops (millones de operaciones de punto flotante

por segundo). Los investigadores de la NASA le dieron el

nombre de Beowulf a este cluster, en honor del héroe de las

leyendas medievales, quien derrotó al monstruo gigante

Grendel. Nombre de un héroe de la mitología danesa relatado en el libro La Era de las Fábulas, del

autor norteamericano Thomas Bulfinch (1796-1867).

Clusters de Alto Rendimiento (HPC)

Dedicados a tareas que requieran de muchos recursos

(CPU, memoria, comunicaciones). La tecnología de Clusters de Alto Rendimiento para Linux más

conocida es la tecnología Beowulf. Esta tecnología puede proporcionar potencial de cómputo del tipo

de una supercomputadora utilizando computadoras personales sencillas. Al conectar estas entre sí

mediante una red Ethernet de alta velocidad, las computadoras personales se combinan para lograr la

(14)

asegurados por medio del Sist. Operativo. La alta disponibilidad en

tareas de cómputo en general tiene que estar proporcionada por el

mismo programa. Los servidores de un Cluster de Alta

Disponibilidad normalmente su función es la de esperar listos para

entrar inmediatamente en funcionamiento en el caso de que falle

algún otro servidor.

Clusters Homogéneos

Son aquellos en los cuales la

tecnología es la misma (PC’s, SCSI, etc.).

Clusters Heterogéneos

Aquellos en los que la tecnología

puede ser diferente o hasta pueden usarse diferentes plataformas

Clusters Dedicados

Son maquinas que se usan

específicamente como un cluster. En la fotografía se muestra un

cluster dedicado con 20 nodos, cada nodo cuenta con dos

procesadores Intel Pentium III a 1 GHz y 1 GB en RAM, una

interfaz de red para la administración del sistema Ethernet 10/100

Mb y otra para las aplicaciones paralelas Giganet a 1Gb. Este se

encuentra en el “Laboratorio de Súper cómputo y visualización paralela” de la UAM-Iztapalapa.

Clusters No Dedicados

Trabajan como un cluster solo una parte del tiempo.

2.2.4. Características importantes de los Clusters

Uso de Hardware convencional.

PC’s

Servidores.

Memoria Distribuida.

Cada procesador tiene acceso sólo a la memoria local, se necesitan usar bibliotecas del tipo

(DSM-Dynamic Shared Memory).

Uso de software abierto.

En el 95% de Clusters al momento, se usa Linux como sistema operativo.

Solo el 0.01% de Clusters hace uso de Windows.

2.2.5. Middleware

Es aquel modulo que interactúa como conductor entre sistemas permitiendo a cualquier usuario

de sistemas de información comunicarse con varias fuentes de información que se encuentran

interconectadas a través de una red.

Interfases más usadas:

MPI (Message Passing Interface) Interfaz mas usada para paso de mensajes, existen varias

implementaciones: MPICH, LAM-MPI, VAMPIR. Entre las ventajas que tiene esta interfaz se

encuentran: distintos modos de comunicación, la sincronización de procesos, el traslape de

procesos de computo con procesos de comunicación, entre otras.

PVM (Parallel Virtual Machine) Desarrollada para NOW, totalmente libre, capaz de trabajar en

redes homogéneas y heterogéneas, realiza un manejo transparente del ruteo de los mensajes,

conversión da datos, calendarización de tareas a través de una red de arquitecturas

incompatibles.

DSM (Memoria Compartida Distribuida): Es un modelo que hace que la memoria distribuida de

todos los nodos aparezca como memoria compartida desde el punto de vista de programador.

(15)

2.3. Programación Paralela

[S2001]

Para paralelizar una aplicación es necesario contar con un lenguaje o una biblioteca que

brinde las bibliotecas necesarias para esto. Dependiendo de la herramienta con que se cuente, se

particionará el código en piezas para que se ejecute en paralelo en varios procesadores. Es aquí donde

entra el termino

granularidad

.

Granularidad

es el tamaño de las piezas en que se divide una aplicación. Dichas piezas pueden

ser una sentencia de código, una función o un proceso en si que se ejecutara en paralelo.

G

ranularidad

es categorizada en paralelismo de grano fino y paralelismo de grano grueso.

De

grano fino

es cuando el código se divide en una gran cantidad de piezas pequeñas. Es a nivel

de sentencia donde un ciclo se divide en varios subciclos que se ejecutaran en paralelo. Se le conoce

además como

paralelismo de datos

.

De

grano grueso

es a nivel de subrutinas o segmentos de código, donde las piezas son pocas y

de cómputo más intensivo que las de grano fino. Se le conoce como paralelismo de tareas.

En el paralelismo de

grano grueso

en el nivel más alto se presenta cuando en la aplicación se

detectan tareas independientes y estas se ejecutan en procesos independientes en, más de un

procesador. En los modelos de memoria distribuida (paso de mensajes) sólo se implementa paralelismo

de

grano grueso

.

Un programador puede desarrollar aplicaciones paralelas con código fuente en C, C++ y

FORTRAN, mediante el uso de paradigmas o modelos provistos por los Sistemas Operativos como lo

es en los sistemas tipo Unix. Con la clonación de procesos o creación de hilos, o usando directivas a

nivel sentencia, las cuales, también son provistas por los Sistemas Operativos, pero cuando se cuenta

con un paralelismo real, es decir, a nivel de Hardware. Además del paso de mensajes, el cual es usado

para sistemas de memoria distribuida en donde cada procesador tiene su propia memoria. Este modelo

es apropiado para la comunicación y sincronización entre los procesos que se ejecutan de manera

independiente (con su propio espacio de direcciones) en diferentes computadoras.

La programación usando el modelo de paso de mensajes consiste en enlazar y hacer llamadas

dentro del programa, a unas bibliotecas que manejarán el intercambio de datos entre los procesadores.

Existen principalmente dos bibliotecas para la programación bajo este esquema:

MPI (Message Passing Interface).

PVM (Parallel Virtual Machine).

El esquema de implementación de paralelismo mediante el paso de mensajes es a nivel de

grano

grueso

dada la generación de procesos o tareas independientes que se ejecutan en varios procesadores.

En MPI y PVM, uno de los esquemas que se emplean en la comunicación y generación de los procesos

es el modelo Maestro-Esclavo. Un proceso maestro divide u distribuye el trabajo en subtareas, que las

asigna a cada nodo conocido como “esclavo”. Terminando cada “esclavo” su parte, envían los

resultados al “maestro” para que este recopile la información y la presente. Otro esquema utilizado en

este modelo es el de “todos esclavos” que consiste en que todos los procesos no son generados por un

“maestro” y se ejecutan independientemente sin notificar a un proceso maestro sus resultados.

(16)

2.3. Programación con MPI (Message Passing Interface)

2.3.1. Introducción a MPI

[3]

MPI es un estándar para el paradigma de

programación de paso de mensajes. En este paradigma el

programador se imagina varios procesadores, cada uno con

su propio espacio de memoria, y escribe un programa para

correr en cada procesador. Pero esto no es programación

en paralelo, no mientras que dichos procesos no

intercambien información, es decir, no tengan una

coparticipación. Por lo que debe de haber una

coparticipación entre los procesadores para el intercambio

de información, y la forma en que ocurra este intercambio

es mediante los

mensajes

. Así el punto principal del

paradigma de

paso de mensajes

es el que los procesos se

comuniquen mediante el envió y recepción de mensajes. De esta forma en el paso de mensajes no hay

una concepción sobre

memoria compartida

o procesos que

acceden la memoria de otros procesadores

.

Por lo que no hay preocuparse por la

consistencia de los datos

, es decir, que no se escriba un dato

mientras se esta leyendo por otro proceso, el cual puede llevar a resultados no deseados.

El hecho de que para cada procesador se tenga un proceso corriendo, el cual tiene interacción

con otros procesos corriendo en procesadores distintos, no quiere decir que el programador tenga que

escribir una serie de programas secuénciales que sean capaces de comunicarse entre sí, sino que el

programador sea capaz de crear código, que dependiendo sobre que y/o cuantos procesadores se

encuentre corriendo, el proceso aplique el mismo funcionamiento a cada parte de los datos que le

corresponda, para que posteriormente los resultados individuales de cada procesador sean unificados y

evaluados en un solo proceso para llegar al fin deseado. Así que lo que hace un típico programa

paralelo es “dividir el problema en problemas chiquitos y después reunir los resultados parciales y

obtener un resultado final”. Es así como con MPI se crea paralelismo de

grano grueso

ya que esta

herramienta provee funciones que determinan que fragmento de código serán ejecutados por cada

procesador. MPI no provee

paralelismo de datos

, sino

paralelismo de tareas

.

2.3.2. Características

[3]

MPI es un estándar para la comunicación inter-proceso en un esquema de multiprocesador

de memoria-distribuida. El estándar ha sido desarrollado por un comité de proveedores, laboratorios del

gobierno y universidades. La implementación del estándar es usualmente dejada para los diseñadores

de sistemas en el cual MPI corre, pero una implementación de dominio público está disponible en

http://www-unix.mcs.anl.gov/mpi/

. MPI es un conjunto de rutinas de librería para C/C++ y

FORTRAN. Además MPI es un conjunto de rutinas de biblioteca para C/C++ y FORTRAN.

Cuando un programa bajo MPI comienza, el programa se descompone en un número de

procesos especificados por el usuario. Cada proceso corre y se comunica con otras instancias del

programa, posiblemente corriendo en el mismo procesador o en diferentes. La comunicación básica

consiste en enviar y recibir datos de un proceso a otro. Esta comunicación toma lugar en una red de

trabajo muy rápida, que conecta a los procesadores en un sistema de memoria-distribuida.

(17)

Un paquete de datos enviados

con MPI requiere bastantes piezas de

información: el proceso de envío, el

proceso de recepción, la dirección de

comienzo en memoria a ser

enviados, el número de datos que

han sido enviados, un identificador

de mensajes, y el grupo de todos los

procesos que puede recibir el

mensaje. Todos estos objetos están

disponibles para ser enviados por el

programador.

En uno de los programas más

simples en MPI, un proceso maestro

despacha trabajo a los procesos

esclavos. Esos procesos reciben los datos, realizan tareas en ellos, y envían los resultados al proceso

maestro, el que combinará los resultados. Los otros procesos corren continuamente desde el comienzo

del programa. Las características de MPI son:

§

Generales

- Los comunicadores combinan procesamiento en contexto y en grupo para seguridad de los

mensajes.

- Seguridad en las aplicaciones con hebrado.

§

Manejo de ambiente. MPI incluye definiciones para:

- Temporizadores y sincronizadores.

- Inicializar y finalizar.

- Control de Errores.

- Interacción con el ambiente de ejecución.

§

Comunicación punto a punto.

- Heterogeneidad para el buffer y tipos de datos derivados.

- Varios modos de Comunicación.

§

Comunicaciones colectivas:

- Capacidad de manipulación de operaciones colectivas con operaciones propias o definidas

por el usuario.

- Gran número de rutinas para el movimiento de datos.

- Los subgrupos pueden definirse directamente o por la topología.

§

Topologías por procesos:

- Soporte incluido para las topologías virtuales para procesos.

§

Caracterización de la interfase:

(18)

2.3.3. Elementos Básicos de la Programación de MPI

Iniciación y terminación de MPI

[4]

Lo primero que necesita hacer el programador para paralelizar sus programas con MPI es

iniciar MPI. Por lo tanto la rutina a ser llamada por cualquier programa hecho con MPI es la rutina de

iniciación, antes de cualquier otra rutina de MPI. Para la codificación en C, dicha rutina acepta los

argumentos del

main

(

argc

y

argv

) para que estos puedan ser pasados a cada uno de los procesos, lo

cual es una ventaja sobre la codificación en FORTRAN, ya que podemos crear ejecutables que reciba

argumentos. La rutina de iniciación es la siguiente:

int MPI_Init(int *argc, char ***argv);

Un programa con MPI debe de llamar a la rutina:

int MPI_Finalize(void);

Cuando todas las comunicaciones son completadas. Esta rutina limpia todas las estructuras de

datos de MPI, entre otras cosas. Esta rutina no cancela el estado las comunicaciones, es responsabilidad

del programador completar y/o terminar con todas las comunicaciones. Esta rutina sólo puede ser

llamada una vez, después de la llamada de dicha rutina ninguna otra rutina de MPI puede ser llamada,

incluso MPI_Init, solo otro proceso nuevo lo podrá hacer.

Terminación anormal de procesos de MPI

En ocasiones hay que prever una posible situación que pueda bloquear la terminación de

nuestros procesos que se encuentran corriendo en paralelo. Un ejemplo de este tipo de situaciones que

podrían presentarse, es el hecho de que uno de nuestros procesos se quede esperando la recepción de un

mensaje, cuando todos los demás ya han terminado, esta situación impedirá a nuestra aplicación

paralela terminar. Claro que esta es una situación que no se debe de presentar si el programa esta bien

hecho, pero en ocasiones es posible que se presente dicha situación. Otra situación que puede provocar

una terminación anormal de nuestros procesos es que en algún nodo se presente una situación que

pueda afectar el desempeño de la aplicación en general, por lo que sería conveniente terminar nuestros

procesos. Para esto se tiene la siguiente rutina que trata de terminar la aplicación paralela.

int MPI_Abort(MPI_Comm

comm

, int

codigoerror

);

Esta aplicación trata de terminar todos los procesos contenidos en

comm

, para que el programa

paralelo termine.

Información del Ambiente

Cuando el programador ejecuta sus programas con MPI, hay información que podría a llegar a

ser relevante para nuestros propósitos, como lo es el número de procesos corriendo, numero

(identificador) de proceso que se trata y/o en donde se encuentra corriendo. MPI provee rutinas que

ayudan en la obtención de dichos datos.

El valor para el número de procesos se obtiene con la rutina:

int MPI_Comm_size(MPI_Comm

comm

, int *

numproc

);

comm

Es el comunicador, utilizado en la comunicación de los proceso, este parámetro es usado pero no

modificado por la rutina.

numproc

Es el número de proceso en el grupo del comunicador, debe ser un apuntador ya que la rutina

modifica dicho parámetro.

El identificador del proceso, es el número o rango del proceso, es un número entre cero y el

total de procesos menos uno (n-1). La rutina para la obtención del identificador es:

(19)

int MPI_Comm_rank(MPI_Comm

comm

, int *

identificador

);

comm

Es el comunicador, utilizado en la comunicación de los proceso, este parámetro es usado

pero no modificado por la rutina.

identificador

Es el rango del proceso en el grupo del comunicador, debe ser un apuntador ya que la

rutina modifica dicho parámetro.

El nombre de la estación donde se encuentra corriendo lo proporciona la rutina:

int MPI_Get_processor_name(char *

nombre

, int *

longitud

);

nombre

Es el apuntador de la cadena donde se guardara el nombre del procesador, este parámetro es

modificado por la rutina.

longitud

Es la longitud de la cadena

nombre

, este parámetro es modificado por la rutina.

Es importante hacer notar que al pasarle un apuntador a una cadena, hay que reservar el espacio

necesario que pudiese ocupar el nombre de la estación de trabajo. Para esto MPI define la macro

MPI_MAX_PROCESSOR_NAME la cual es igual 256. Dicha macro esta definida en “mpi.h”.

Comunicadores y Etiquetas

Entre las rutinas de iniciación que lleva a cavo MPI_Init esta la definición de algo llamado

MPI_COMM_WORLD para cada proceso que llama dicha macro. MPI_COMM_WORLD es un

comunicador

. Todas las comunicaciones en MPI realizan llamadas a rutinas que requieren de un

argumento

comunicador

, y los procesos en MPI sólo pueden comunicarse si comparten el mismo

comunicador

, ya que éste especifica un dominio de comunicación.

Cada comunicador contiene un

grupo

que es una lista de procesos. Un proceso puede tener

varios comunicadores y por consiguiente pertenecer a varios grupos. MPI_COMM_WORLD es el

comunicador de todos los procesos en MPI.

El procesamiento por grupos permite a MPI cubrir una serie de debilidades presentes en algunas

bibliotecas de paso de mensajes e incrementa sus capacidades de portabilidad, eficiencia y seguridad.

El procesamiento por grupos agrega elementos como: división de procesos, transmisión de mensajes

sin conflictos, extensibilidad para los usuarios (para la creación de bibliotecas) y seguridad.

MPI_GROUP_EMPTY sirve para denotar aquel grupo que no tiene miembros., La constante

MPI_GROUP_NULL es el valor usado para referirse a un grupo no valido.

Para crear un nuevo comunicador se debe partir de un comunicador ya existente. La rutina que

implementa la creación de un comunicador es:

int MPI_Comm_dup(MPI_Comm

comm

, MPI_Comm *

newcomm

);

comm

Comunicador a partir del cual se generara uno nuevo, la rutina sólo hace uso de este

argumento.

newcomm

Nuevo comunicador, la rutina modifica dicho parámetro.

Esta rutina le permite crear un nuevo comunicador (

newcomm

) compuesto de los mismos

procesos que hay en

comm

pero con un nuevo contexto para asegurar que las comunicaciones

efectuadas para distintos propósitos no sean confundidas.

Finalmente cuando se han efectuado todas las comunicaciones asociadas al nuevo comunicador,

este deberá ser destruido la rutina para la liberación del comunicador es como sigue, esta no implica

mayor dificultad por lo que no requiere mayor explicación.

(20)

un número entero. Con esto MPI implementa la recepción de mensajes selectiva por parte de los

procesos receptores.

Medición de tiempo

Medir el tiempo que dura un programa es importante para establecer su eficiencia y para fines

de depuración, sobre todo cuando éste es paralelo. Para esto MPI ofrece las funciones MPI_Wtime y

MPI_Wtick. Ambas proveen alta resolución y poco costo.

double MPI_Wtime(void);

MPI_Wtime devuelve un punto flotante que representa el número de segundos transcurridos a

partir de cierto tiempo pasado, el cual se garantiza que no cambia durante la vida del proceso. Es

responsabilidad del usuario hacer la conversión de segundos a otras unidades de tiempo: horas,

minutos, etc.

double MPI_Wtick(void);

MPI_Wtick permite saber cuantos segundos hay entre tic's sucesivos del reloj. Por ejemplo, si el

reloj esta implementado en hardware como un contador que se incrementa cada milisegundo, entonces

MPI_Wtick debe devolver 10

-3

Tipos de datos en MPI

Para el envió y recepción de mensajes es muy importante indicarle exactamente a las rutinas

encargadas de ello, los tipos de datos que serán enviados, por lo que MPI establece una equivalencia

entre los datos definidos en C/C++ y los tipos de datos de MPI. La siguiente tabla muestra las macros

definidas para los tipos de datos de MPI y su equivalente en C, así como el valor de la macro.

Tipo de Dato en C Macro

Valor de la Macro

char

MPI_CHAR

1

unsigned char

MPI_UNSIGNED_CHAR

2

MPI_BYTE

3

short

MPI_SHORT

4

unsigned short

MPI_UNSIGNED_SHORT

5

int

MPI_INT

6

unsigned

MPI_UNSIGNED

7

long

MPI_LONG

8

unsigned long

MPI_UNSIGNED_LONG

9

float

MPI_FLOAT

10

double

MPI_DOUBLE

11

long double

MPI_LONG_DOUBLE

12

long long int

MPI_LONG_LONG_INT

13

MPI_PACKED

14

MPI_LB

15

MPI_UB

16

MPI_FLOAT_INT

17

MPI_DOUBLE_INT

18

long int

MPI_LONG_INT

19

short int

MPI_SHORT_INT

20

(21)

Tipo de Dato en C Macro

Valor de la Macro

MPI_LONG_DOUBLE_INT

22

Algunas de las macros definidas por MPI hacen referencia a datos que no son tipos de datos en

C. En algunos casos simplemente se tratan de estructuras con la siguiente forma:

struct

{

double var;

int loc;

}

Como lo son las macros MPI_FLOAT_INT, MPI_DOUBLE_INT y

MPI_LONG_DOUBLE_INT. Estos casos en particular al ser declarados en código C puro generaría el

siguiente error en la compilación

too many types in declaration”(demasiados tipos en la declaración)

.

Algunos otros son tipos simples como MPI_BYTE, el cual no requiere mayor explicación. Otros son

más complejos como el MPI_PACKED, el cual será tratado más adelante.

Comunicación punto a punto

Una comunicación

punto-a-punto

siempre involucra

solamente dos procesos. Un proceso que envía el mensaje y otro

que lo recibe. En el envío el proceso

fuente

hace una llamada a

una rutina especificándole el proceso

destino

dentro del rango del

comunicador. El proceso receptor sólo hace la llamada a la rutina

de MPI para la recepción del mensaje, dentro del rango del

comunicador, no es necesario especificar la fuente de quien se

espera el mensaje.

Existen cuatro modos de comunicación provistas por MPI:

estándar

,

sincrono

,

buffered

y

lectura

. Los cuatro tipos se refieren

a cuatro tipos de envío, no es significante el modo de recepción

de los mensajes en los modos de comunicación. El envío de un

mensaje se completa cuando el

buffer

, el cual es el canal de

comunicación, puede ser reutilizado. Los modos

estándar

,

sincrono

y

buffered

difieren en el envío, sólo en un aspecto. Como se completa el envío dependiendo

de cómo se recibe el mensaje. La siguiente tabla muestra los cuatro tipos de envío, así como la rutina

encargada de ella y una descripción.

Modo de

Comunicación

Rutina de

MPI

Descripción

Estándar

MPI_Send

Se completa cuando el mensaje puede ser enviado, no

implica que el mensaje pueda o no ser recibido.

Sincrono

MPI_Ssend

Sólo se completa cuando la recepción se ha completado

Buffered

MPI_Bsend

Siempre se completa (a menos que ocurra un error), sin

considerar si la recepción se ha completado. El mensaje se

copia a un sistema de

buffer

para después transmitir si es

necesario.

(22)

Un mensaje es un arreglo de elementos de un tipo de dato particular de MPI. Para todos los

mensajes en MPI deben especificarse el tipo en el envío y la recepción.

Los aspectos que MPI provee para el envío de mensajes y ganar portabilidad son los siguientes:

Especificación de los datos a enviar con tres campos: dirección de inicio, tipo de dato y

contador.

Los tipos de datos son construidos recursivamente por las rutinas de MPI

La especificación de tipos de datos permite comunicaciones heterogéneas.

Se elimina la longitud de un mensaje a favor de un contador.

Cualquiera de las rutinas de envío de mensajes sigue el patrón de la rutina MPI_Send, el cual es

el siguiente:

MPI_Send(void *

buf

, int

count

, MPI_Datatype

tipo

, int

dest

, int

etiqueta

, MPI_Comm

comunicador

);

donde:

buf

Es la dirección del buffer de envío.

count

Es un entero que indica el número de elementos a recibir

tipo

Tipo de datos de MPI de los elementos en el buffer de envío.

destino

Identificador del proceso destino.

etiqueta

Etiqueta de un mensaje.

comunicador

Comunicador al cual pertenece el proceso que envía y recibe mensaje.

La recepción de un mensaje se realiza mediante la rutina:

MPI_Recv(void *

buf

, int

count

, MPI_Datatype

tipo

, int

fuente

, int

etiqueta

, MPI_Comm

comunicador

, MPI_Status

estado

);

donde:

buf

Es la dirección del buffer de envío.

count

Es un entero que indica el número de elementos a recibir

tipo

Tipo de datos de MPI de los elementos en el buffer de envío.

fuente

Identificador del proceso fuente o MPI_ANY_SOURCE (reciba de cualquier fuente)

etiqueta

Etiqueta de un mensaje o MPI_ANY_TAG (cualquier etiqueta).

comunicador

Comunicador al cual pertenece el proceso que envía y recibe mensaje.

estado

Estructura de tipo MPI_Status con información variada.

La estructura MPI_Status es usada para la información del mensaje recibido, esto en caso de

que se hallan utilizado algunas de las macros MPI_ANY_TAG o MPI_ANY_SOURCE, que

posteriormente puedan necesitarse, e incluso tiene un campo que indica el error que ocurrió en caso de

que la recepción no se logre completar. La estructura es la siguiente:

typedef struct { int count; int MPI_SOURCE; int MPI_TAG; int MPI_ERROR; int private_count; }MPI_Status;

(23)

Comunicaciones colectivas

Además de las comunicaciones punto a punto MPI provee rutinas para las comunicaciones

colectivas, por lo que permite la comunicación entre varios tipos de procesos. Las comunicaciones

colectivas permiten la transferencia de datos entre procesos que tienen el mismo canal de

comunicación, es decir, comunicador. En el caso de dichas comunicaciones se usan etiquetas para los

mensajes, se hace uso del

comunicador

.

[S2001]

Las comunicaciones colectivas pueden ser clasificadas en tres clases:

Sincronización

: barreras para sincronizar.

Movimiento (transferencia) de datos

: operaciones para difundir, recolectar y esparcir datos.

Cálculos colectivos

: operaciones para reducción global, tales como suma, multiplicación,

máximo o mínimo o cualquier función definida por el usuario.

Sincronización

Una operación de barrera sincroniza a todos los procesos que compartan el mismo canal de

comunicación, es decir, que pertenezcan al mismo grupo. No hay intercambio de datos.

La rutina encargada de dicha operación es:

int MPI_Barrier(MPI_Comm

comunicador

);

Movimiento (transferencia) de datos

Para la transferencia de datos, en primer lugar se tiene a la comunicación

uno-a-muchos

en este

el proceso raíz difunde el mismo mensaje a múltiples procesos con una simple operación.

(24)

inbuf

Dirección del buffer de recepción, o buffer de envío si se trata del proceso raíz

numelem

Numero de elementos en el buffer de envío.

tipo

Tipo de dato de los elementos en el buffer.

raiz

Rango del proceso raíz, es decir, el encargado de difundir los datos.

comunicador

Canal de comunicación.

Otras rutinas para las comunicaciones colectivas son:

int MPI_Gather(void

*inbuf

, int

numelem

, MPI_Datatype

intipo

, void

*outbuf

, int

outelem

,

MPI_Datatype

outtipo

, int

raiz

, MPI_Comm

comunicador

);

La rutina anterior hace que cada proceso, incluyendo el proceso raíz, envié el contenido de su

buffer de envío (

inbuf

) al proceso raíz. El proceso raíz recibe los mensajes y los almacena en orden

(según el rango del proceso que realiza el envío) en el buffer de recepción (

outbuf

).

int MPI_Scatter(void

*inbuf

, int

numelem

, MPI_Datatype

intipo

, void

*outbuf

, int

outelem

,

MPI_Datatype

outtipo

, int

raiz

, MPI_Comm

comunicador

);

En esta rutina el proceso raíz envié un trozo de información a cada proceso incluyéndolo a él. El

proceso raíz enviará u trozo del contenido del buffer de envío (

inbuf

). Comenzando desde la dirección

inicial de dicho buffer se desplazara una cantidad (

numelem

) para realizar el siguiente envío.

donde:

inbuf

Dirección del buffer envío.

numelem

Numero de elementos a enviar a cada proceso.

intipo

Tipo de dato de los elementos en el buffer de entrada.

outbuf

Dirección del buffer de recepción.

outelem

Número de elementos a recibir cada uno

outtipo

Tipo de elementos en el buffer de recepción.

raiz

Rango del proceso raíz, es decir, el encargado de difundir los datos.

comunicador

Canal de comunicación.

Cálculos colectivos

Básicamente las operaciones colectivas son operaciones de reducción. Una operación de

reducción toma datos de diferentes procesos y los reduce a un simple dato. El orden de evaluación

canónico de una reducción esta determinado por el rango de los procesos. La operación de reducción

puede ser una operación predefinida o una definida por el usuario, Las operaciones definidas en MPI

se muestran en la siguiente tabla:

Nombre de

operación en

MPI

Operación

MPI_MAX

Máximo

MPI_MIN

Mínimo

MPI_PROD

Producto

MPI_SUM

Suma

MPI_LAND

Y lógico

MPI_LOR

O lógico

MPI_LXOR

XOR lógico

MPI_BAND

Y bit a bit

MPI_BOR

OR bit a bit

(25)

Nombre de

operación en

MPI

Operación

MPI_BXOR

XOR bit a bit

MPI_MAXLOC

Máximo y su posición

MPI_MINLOC

Mínimo y su posición

Las rutinas que realizan las operaciones de reducción se explican a continuación. La diferencia

entre estas dos rutinas es que en la primera sólo el proceso raíz tiene el resultado de la operación en su

buffer de recepción, la segunda todos los procesos cuentan con el resultado en su buffer de recepción.

int MPI_Reduce(void

*inbuf

, void

*outbuf

, int

numelem

, MPI_Datatype

tipo

, MPI_Op

operacion

, int

root

, MPI_Comm

comunicador

);

int MPI_Allreduce(void

*inbuf

, void

*outbuf

, int

numelem

, MPI_Datatype

tipo

, MPI_Op

operacion

, int

root

, MPI_Comm

comunicador

);

donde:

inbuf

Dirección del buffer de envío.

outbuf

Dirección del buffer de recepción.

numelem

Numero de elementos en el buffer de envío.

tipo

Tipo de datos de los elementos del buffer de envío.

operacion

Operación a realizar sobre los elementos del buffer de envío.

root

Rango del proceso raíz.

comunicador

Canal de comunicación.

Comunicaciones sin bloqueo

El rendimiento de muchas operaciones se puede mejorar si se logran solapar las comunicaciones

y el cálculo. La forma de solapar las comunicaciones es la creación de hilos de ejecución, con esto se

puede crear una hilo que se encargue de las comunicaciones, mientras que el proceso siga realizando su

trabajo. En este caso no habría problema al utilizar comunicaciones con bloqueo ya que el hilo

encargado de las comunicaciones podría permanecer bloqueado, esperando la terminación de las

mismas.

La comunicación sin bloqueo es una alternativa, que usualmente da un buen resultado. La forma

en que operan las comunicaciones es como sigue: para enviar y/o recibir un mensaje la rutina

encargada de ello hace la solicitud y regresa antes de que el canal de comunicación se encuentre libre

y/o ocupado, es así, como la rutina regresa antes de que el buffer de envío sea reutilizable y/o

utilizable, por lo que hay que realizar una llamada a una rutina de verificación, para ver si el envío y/o

recepción se han completado.

Hasta el momento se han hablado de comunicaciones con bloqueo, esto quiere decir que la

rutina llamada para la transmisión regresa si se han completado las operaciones, por lo que el proceso

es detenido en cuanto las comunicaciones no terminen. Pero, MPI implementa estas mismas

comunicaciones, sin bloqueo, las rutinas siempre regresan directamente y permiten que el proceso

continué realizando su trabajo y después de un lapso de tiempo realice un

test

para verificar la

terminación de la operación de envío y/o recepción.

(26)

MPI_Isend

MPI_Issend

MPI_Ibsend

MPI_Irsend

La sintaxis de estas rutinas tienen la misma sintaxis que la de MPI_Isend que es como sigue:

int MPI_Isend(void *

buf

, int

count

, MPI_Datatype

tipo

, int

dest

, int

etiqueta

, MPI_Comm

comunicador

,

MPI_Request

*request

);

donde:

buf

Es la dirección del buffer de envío.

count

Es un entero que indica el número de elementos a recibir

tipo

Tipo de datos de MPI de los elementos en el buffer de envío.

destino

Identificador del proceso destino.

etiqueta

Etiqueta de un mensaje.

comunicador

Comunicador al cual pertenece el proceso que envía y recibe mensaje.

request

Estructura de tipo MPI_Request con información que ayuda a determinar si las

comunicaciones se han completado.

La rutina de recepción tiene la siguiente sinopsis:

MPI_Recv(void *

buf

, int

count

, MPI_Datatype

tipo

, int

fuente

, int

etiqueta

, MPI_Comm

comunicador

, MPI_Request

*request

);

donde:

buf

Es la dirección del buffer de envío.

count

Es un entero que indica el número de elementos a recibir

tipo

Tipo de datos de MPI de los elementos en el buffer de envío.

fuente

Identificador del proceso fuente o MPI_ANY_SOURCE (reciba de cualquier fuente)

etiqueta

Etiqueta de un mensaje o MPI_ANY_TAG (cualquier etiqueta).

comunicador

Comunicador al cual pertenece el proceso que envía y recibe mensaje.

request

Estructura de tipo MPI_Request con información que ayuda a determinar si las

comunicaciones se han completado.

Como ya se menciono anteriormente las rutinas de envío y recepción sin bloqueo no completan

las comunicaciones, sólo hacen la solicitud de envío o recepción según sea el caso. Las funciones para

completar el envío o la recocción son MPI_Wait y MPI_Test, la primera permite esperar por la

finalización de una operación y la otra verifica si se ha completado dicha operación. La sinopsis de

dichas rutinas es:

int MPI_Wait(MPI_Request

*request

, MPI_Status

*estado

);

int MPI_Test(MPI_Recuest

*request

, int

*bandera

, MPI_Status

*estado

);

donde:

request

Tipo de dato de MPI_Recuest que determina la operación que se trata, envío o recepción.

bandera

Bandera que especifica si la operación ha sido contemplada.

estado

Tipo de dato MPI_Status en donde se gurda toda la información sobre la operación

completada.

En MPI existen otras rutinas que ayudan a la comunicación sin bloqueo, por ejemplo, en

ocasiones no importa que un proceso se espere a que termine de enviar un mensaje, sino que hay

muchos que esperan un mensaje que posiblemente no llegará, esto se presenta cuando no se sabe en

realidad como se presentaran las comunicaciones entre los proceso, como lo es en este proyecto. Para

(27)

esto existen dos rutinas que ayudan a que un proceso no se bloquee esperando un mensaje, lo que se

hace es verificar si se ha recibido un mensaje de una fuente especifica y con una etiqueta determinada.

Las rutinas que realizan dicha verificación son:

int MPI_Probe(int

fuente

, int

etiqueta

, MPI_Comm

comunicador

, MPI_Status *

estado

) ;

int MPI_Iprobe(int

fuente

, int

etiqueta

, MPI_Comm

comunicador

, int *bandera,

MPÌ_Status

*estado

) ;

donde:

fuente

Identificador rango del proceso enviador.

etiqueta

Etiqueta del mensaje.

comunicador

Canal de comunicación.

bandera

Entero que determina si hay un mensaje con la respectiva etiqueta y fuente.

estado

Tipo de dato de MPI_Status en donde se guarda información del mensaje recibido.

La primera es una verificación con bloqueo, es decir, que esta se bloquea hasta que se presente

un mensaje con las características especificadas. La segunda es sin bloqueo, esta rutina verifica si hay

un mensaje que se ajuste a los parámetros y regresa, si se presenta esta situación modifica el parámetro

bandera=true

. Una vez que se sabe que llegó el mensaje, se puede recibir usando MPI_Recv.

Con estas rutinas no sólo se puede evitar el bloqueo de un proceso además se pueden establecer

comunicaciones sin que se tenga que establecer previamente una comunicación para establecer la

longitud del mensaje a enviar, Esto se debe a que una vez que MPI_Probe o MPI_Iprobe hayan sido

exitosas, se puede revisar la estructura

estado

con MPI_Get_count para obtener la información

respectiva y usarla en MPI_Recv. La sinopsis esta rutina es:

int MPI_Get_count(MPI_Status

estado

, MPI_Datatype

tipo

, int

*contador

);

donde:

estado

Tipo de dato MPI_Status donde se guarda información de la comunicación establecida.

tipo

Tipo de dato de los elementos recibidos.

contador

Número de elementos recibidos.

2.3.4. Compilación de programas MPI

[1]

Para compilar los programas MPI, usualmente se utiliza el comando

mpicc

(o

mpif77

en el

caso de FORTRAN) cuyo funcionamiento y parámetros son similares a la del compilador

gcc

, no existe

un manual del comando

mpicc

la siguiente sinopsis y opciones del compilador son tomadas del manual

del

gcc

, pero fueron probadas exitosamente con programas MPI.

Sinopsis de mpicc

mpicc

[opciones] <fuente | Fuentes> [opciones]

Opciones:

Algo muy importante que hay que tomar en cuenta es que las opciones van separadas una de

otra, por ejemplo, si se tienen las opciones

v

y

o

no es posible ponerlas de la siguiente forma:

$: mpicc -vo hola holamundo.c

Esto es incorrecto y el compilador marcará un error. La forma correcta de poner estas opciones

es:

(28)

-o

file

Coloca la salida del proceso en un archivo con el nombre especificado especificando en

file

. Si

no se utiliza esta opción la salida por default dependerá de la etapa en la que se encuentre, es decir, si

se está en el preprocesado, compilado o ensamblado la salida será el nombre del fuente con la extensión

correspondiente a la etapa de que se trate; si se trata del ejecutable la salida será

a.out

.

-v

Imprime en la salida estándar del error la ejecución de la instrucción al recorrer cada una de las

etapas de la compilación. También imprime el número de versión del compilador y del preprocesador

-D

macro=valor

Esta es utilizada para la creación de macros que no fueron declarados en el código fuente, por

ejemplo, el archivo “prueba.c” con el siguiente código.

#include <stdio.h> #include “mpi.h”

int id, proc, longitud; char nombre[50];

int main (int argc, char *argv[]) {

MPI_Init (&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &id); MPI_Comm_size(MPI_COMM_WORLD, &proc); MPI_Get_processor_name(nombre, &longitud);

printf("hola Mundo , estoy en el procesador %s \n", nombre); printf(“Macro definida : %d\n”, MCR);

MPI_Finalize(); }

Hay que observar que la macro

MCR

no es declarada en el código fuente, para que nuestra

compilación no tenga errores hay que realizarla de con la siguiente instrucción:

$: mpicc -o salida prueba.c -DMCR=1

Y no tendremos ningún problema en obtener nuestro ejecutable.

-include

file

Esta opción debe ser utilizada para indicarle un archivo de cabecera que no halla sido declarado

en el código fuente. Por ejemplo, si tenemos el archivo holamundo.c con el siguiente código:

#include <stdio.h>

int id, proc, longitud; char nombre[50];

int main (int argc, char *argv[]) {

MPI_Init (&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &id); MPI_Comm_size(MPI_COMM_WORLD, &proc); MPI_Get_processor_name(nombre, &longitud);

printf("hola Mundo , estoy en el procesador %s \n", nombre); MPI_Finalize();

Referencias

Documento similar