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.
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.
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.
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.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
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.
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 amemoria 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
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:
•
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.
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.
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
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.
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.
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.
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:
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:
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.
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
-3Tipos 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
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.
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;
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.
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
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.
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
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:
-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();