Simulador DFS Distribuido de Eventos Discretos

Anuncio
Universidad Autónoma Metropolitana
Unidad Iztapalapa
División de Ciencias Básicas e Ingenierı́a
“Simulador DFS Distribuido de
Eventos Discretos”
Proyecto de Ingenierı́a Electrónica
Grado: Licenciatura
David Cortés Poza
99321198
Alejandro Martı́nez Ramı́rez
97319967
Asesor: Dr. Ricardo Marcelı́n Jiménez
3 de marzo de 2004
1
Índice
I
MARCO TEÓRICO
4
1. Prefacio
4
2. La simulación de eventos discretos
6
3. Métodos de sincronización en la simulación
6
4. Algoritmo DFS
7
5. Funcionamiento del simulador utilizado
9
II
PROGRAMACIÓN DISTRIBUIDA
6. Herramientas de la simulación distribuida
6.1. MPI (Message-Passing Interface) . . . . .
6.1.1. INSTALACIÓN DE MPICH . . . .
6.1.2. Instalación de LAM / MPI . . . . .
6.2. Configuración final . . . . . . . . . . . . .
6.2.1. MPICH . . . . . . . . . . . . . . .
6.2.2. MPI/LAM . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
12
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
12
12
13
13
14
15
15
7. Compilación y ejecución de programas.
16
8. Comunicación básica con MPI
8.1. Funciones de MPI . . . . . . .
8.2. Envı́o y recepción de mensajes
8.3. Otras funciones de MPI . . .
8.4. Ejemplo de Broadcast . . . .
16
16
18
18
19
III
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
PROCEDIMIENTO Y RESULTADOS
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
21
9. El algoritmo DFS distribuido
21
10.Adaptación del código existente
21
2
11.Resultados Finales
25
12.Observaciones y Conclusiones
33
13.Trabajo por hacer
34
IV
36
APÉNDICES
14.Código fuente del simulador DFS distribuido
14.1. Clase: DFS / Archivo dfs.cc . . . . . . . . . .
14.2. Clase: Simulation . . . . . . . . . . . . . . . .
14.2.1. Archivo simulation.h . . . . . . . . . .
14.2.2. Archivo simulation.cc . . . . . . . . . .
14.3. Clase: Model . . . . . . . . . . . . . . . . . .
14.3.1. Archivo model.h . . . . . . . . . . . .
14.3.2. Archivo model.cc . . . . . . . . . . . .
14.4. Clase: Event . . . . . . . . . . . . . . . . . . .
14.4.1. Archivo event.h . . . . . . . . . . . . .
14.4.2. Archivo event.cc . . . . . . . . . . . .
14.5. Clase: Process . . . . . . . . . . . . . . . . . .
14.5.1. Archivo process.h . . . . . . . . . . . .
14.5.2. Archivo process.cc . . . . . . . . . . .
14.6. Clase: Simulator . . . . . . . . . . . . . . . . .
14.6.1. Archivo simulator.h . . . . . . . . . . .
14.6.2. Archivo simulator.cc . . . . . . . . . .
14.7. Clase: Graph . . . . . . . . . . . . . . . . . .
14.7.1. Archivo graph.h . . . . . . . . . . . . .
14.7.2. Archivo graph.cc . . . . . . . . . . . .
14.8. Clase: MPIComm . . . . . . . . . . . . . . . .
14.8.1. Archivo mpicomm.h . . . . . . . . . .
14.8.2. Archivo mpicomm.cc . . . . . . . . . .
14.9. Clase: LinkList / Archivo linklist.cc . . . . . .
14.10.Clase: AVLTree / Archivo: avltree.cc . . . . .
15.Bibliografı́a
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
36
36
39
39
40
42
42
43
45
45
45
47
47
48
50
50
51
52
52
53
55
55
56
60
70
85
3
Parte I
MARCO TEÓRICO
1.
Prefacio
La simulación por medio de las computadoras es la disciplina de diseño
de un modelo de un sistema fı́sico real o teórico, ejecutando el modelo en una
computadora y analizando la ejecución y los datos de salida. La simulación
engloba el principio de “aprender haciendo”; se aprende acerca de un sistema
construyendo un modelo de algún tipo y luego operando este modelo. El
uso de la simulación es una actividad que es tan natural como un niño en
un juego de rol. Los niños entienden el mundo que los rodea por medio
de juguetes y disfraces que les permiten simular su interacción con otras
personas, animales y objetos. Como adultos, se pierden algunos de estos
comportamientos infantiles pero se recapturan en cierto modo por medio de
la simulación por medio de las computadoras. Para entender la realidad y
toda su complejidad, se deben construir objetos artificiales y asumir roles con
ellos. Este tipo de simulación sirve para interactuar en ambientes sintéticos
y mundos virtuales. El concepto de simulación está dividido en tres partes:
diseño del modelo, ejecución del modelo y el análisis del modelo.
Para simular un sistema fı́sico, primero se necesita crear un modelo (que
en general es matemático) que represente ese o esos objetos fı́sicos. Estos
modelos pueden ser de tipo aseverativo, funcional, restringido, espacial o
multimodelo. Un multimodelo es un modelo que contiene múltiples modelos
integrados y dependiendo del número de modelos, se proporciona un nivel de
precisión para la simulación del sistema fı́sico. La tarea siguiente, una vez que
el modelo ha sido desarrollado, es la ejecución del modelo en una computadora; esto es la creación de un programa de computadora que conforme avance
el tiempo, el estado de las variables y de los eventos del modelo matemático
vaya siendo actualizado. Existen varias maneras de “avanzar en el tiempo”.
Se puede, por ejemplo, avanzar gracias a una agenda de eventos o se pueden
emplear pequeños incrementos de tiempo (time slicing). Se puede ejecutar el
programa de una manera masiva en una una computadora en paralelo. Esto
se llama simulación paralela y distribuida. Para muchos modelos de gran escala, esta es la única manera de obtener resultados en un perı́odo de tiempo
4
razonable.
La simulación de un sistema puede ser llevada a cabo a diferentes niveles
de fidelidad, ya que una persona puede pensar en un modelo matemático
basado en uno fı́sico, pero otra podrı́a hacerlo en un modelo más abstracto y
los resultados de estos modelos serán bastante diferentes entre sı́ en cuanto
a detalle. El tipo de resultado que se necesita indica el tipo de modelo que
se debe emplear.
Es posible preguntarse cuando la simulación debe de ser usada para estudiar sistemas dinámicos. Existen varios métodos para modelar sistemas
que no involucran la simulación pero que involucran la solución de sistemas
que tienen formas similares (como los sistemas de ecuaciones lineales). La
simulación es a menudo esencial para los siguientes casos:
1) El modelo es muy complejo, con muchas variables y componentes
que interactúan entre sı́.
2) Las relaciones entre las variables fundamentales no es lineal
3) El modelo contiene variables aleatorias
4) El resultado de la simulación es en 3D
Un aspecto importante de la técnica de simulación es que se construye un
modelo como réplica de un sistema real y al ejecutar la simulación, el modelo
tiende a seguir la forma del método de solución más que a representar de
manera precisa al sistema fı́sico, por lo que se debe de escoger un modelo que
se ajuste al sistema y a las condiciones de funcionamiento de éste.
Para realizar el estudio de un sistema, es difı́cil realizarlo sobre el mismo,
ya que puede no estar aún construido o puede ser demasiado complicado
debido a su tamaño o a su disposición, el proyecto Construcción de un Simulador Distribuido de Eventos Discretos, nos ofrece la facilidad de realizar un
estudio del comportamiento de un sistema de éste tipo sin necesidad de un
sistema real. Otra ventaja de este proyecto, es que al realizar la versión distribuida, podemos ejecutar la simulación de sistemas cada vez más grandes y
en perı́odos de tiempo pequeños, sin la necesidad de tener una computadora
muy poderosa.
Para lograr la comprensión de este proyecto, hay algunos puntos que a
nuestra consideración deben de ser tratados, los cuales serán descritos con la
mayor claridad en los siguientes capı́tulos.
5
2.
La simulación de eventos discretos
Una simulación de este tipo, utiliza un modelo de estados discretos, y genera nuevos eventos gracias a las condiciones iniciales y a algunas propiedades
estadı́sticas. Este tipo de simulación es muy utilizada para sistemas de cómputo
y de comunicaciones, ya que pueden ser representados como un conjunto de
estados discretos. Un punto importante a remarcar es que el término discreto
no se aplica a los valores de tiempo utilizados en la simulación, ya que los
valores de tiempo pueden ser discretos o continuos.
En la simulación de eventos discretos, es necesario realizar un modelo del
sistema real, llamado Red de procesos fı́sicos que consiste en vértices y aristas.
Los vértices representan unidades autónomas de procesamiento mientras que
las aristas son las posibles interacciones que existen entre ellas. Al contar ya
con esta información, los datos más importantes que se deben de generar son
el reloj y la lista de eventos. El reloj es una variable que registra el tiempo
hasta el que se ha simulado, mientras que la lista de eventos nos informa de
los mensajes que se han intercambiado entre las distintas unidades autónomas
de procesamiento y el tiempo en el que se han dado.
Al efectuar simulaciones de grandes sistemas, es necesario tener un mayor
poder de procesamiento ya que una sola computadora puede tomar mucho
tiempo para producir resultados. Cuando se tiene una red de computadoras, es posible realizar un método alternativo de simulación, la Simulación
Distribuida de Eventos Discretos (DDES). Ésto nos permite acelerar la simulación ya que los procesos son divididos entre las computadoras de la red.
Es posible observar que cuando se realiza una simulación de este tipo, al
no ser ejecutados los procesos a la misma velocidad, habrá diferencias en el
tiempo (las cuales se pueden corregir) y que pueden generar la posibilidad
de estancamiento o interbloqueo, al haber problemas con la sincronización
del tiempo. Los dos métodos más utilizados para corregir estas diferencias
y permitir la sincronı́a de la simulación son : los métodos optimistas y los
métodos conservadores.
3.
Métodos de sincronización en la simulación
Los métodos optimistas nos permiten realizar una ejecución continua de
la simulación y se considera que todos los procesos se ejecutan a la misma
6
velocidad (consideración que no siempre corresponde con la realidad). De
este modo, los mensajes están sincronizados y en el momento en el que son
mandados, es el mismo momento en el que se espera sean recibidos. Cuando
esto no sucede y se recibe un mensaje cronológicamente anterior a los que ya
han sido realizados, se efectúa un mecanismo de restauración (rollback) que
regresa la simulación hacia el estado correspondiente al mensaje tardı́o, para
que éste pueda ser procesado.
Los métodos conservadores permiten que la simulación avance en sincronı́a, ya que para avanzar en la variable de tiempo, es necesario tener la
certeza de que ningún mensaje será enviado de manera tardı́a, de este modo,
todos los relojes avanzan a la misma velocidad y no se tendrán problemas de
estancamiento o interbloqueo.
La programación de un simulador con estas caracterı́sticas es realizable
gracias a una herramienta de programación tal como MPI (Message-Passing
Interface), que es una interfaz de paso de mensajes.
4.
Algoritmo DFS
Para la realización de este proyecto, se tomó un algoritmo DFS (Depth
First Search) previamente programado y a continuación se presentan los conceptos más importantes del funcionamiento de este algoritmo.
En los sistemas distribuidos, el explorar y recorrer una gráfica que representa a una red, resulta ser una operación muy común. El algoritmo DFS
ayuda a construir un árbol generador, que incluya a todos los nodos de la red
con el mı́nimo número de aristas. De esta manera, cuando se envı́an mensajes
entre los diferentes nodos, podemos aseguranos de que cada nodo recibe cada
mensaje exactamente una vez.
En este proyecto, se utilizó un algoritmo DFS distribuido, que supone
que cada nodo del grafo lo ejecutará las mismas instrucciones. Cuando se
da inicio al algoritmo, se elige a alguno de los nodos para arrancar en él el
algoritmo (s), y esto es lo único que diferenciará la ejecución en los diferentes
nodos. Al nodo inicial (s) se le envı́a una “ficha” o mensaje de activación
(mensaje DESCUBRE) y este a su vez selecciona a otro de los nodos paa
otorgarle el control de la ejecución. Sólo a aquel que tenga la “ficha” en su
poder, se le considera activo, por lo que en cualquier instante del algoritmo,
existe un solo proceso en estas condiciones.
7
Cada proceso que recibe un mensaje de activación, marca al emisor como
su padre y, a su vez, selecciona un camino por el cual expedirá la “ficha”, para
iniciar la ejecución en otro nodo. Ası́ mismo, cada vez que un proceso recibe
el mensaje de DESCUBRE envı́a a sus vecinos (que no sean su padre)
un mensaje de AVISO indicándoles que él ya no puede ser descubierto,
por que ya tiene un padre. Si algún proceso recibe repetido un mensaje de
activación desde algún vecino que ignore sus condiciones locales, el receptor
deberá responder con un mensaje RECHAZO que indique su estado y la
imposibilidad de ser definido como hijo de otro nodo.
Finalmente, cuando un nodo ya no puede transmitir la “ficha”, ya sea
porque no tiene vecinos o porque todos sus vecinos ya han sido descubiertos,
enviará de vuelta a su padre un mensaje (REGRESA) para devolverle el
control de las operaciones. El receptor del mensaje REGRESA designará al
emisor como su hijo.
A continuación se presenta el pseudocódigo de este algoritmo:
<1> al recibir DESCUBRE desde el enlace j efectúa
<2>
si visitado(i) entonces
<3>
envı́a RECHAZO a j
<4>
sin visitar(i) <- sin visitar(i) - {j}
<5>
otro
<6>
visitado(i) <- verdadero
<7>
padre(i) <- j
<8>
envia AVISO a todos los vecinos que no sean padre(i)
<9>
sin visitar(i) <- sin visitar(i) - {j}
<10>
si existe k en sin visitar(i) entonces
<11>
envia DESCUBRE a k
<12>
sin visitar(i) <- sin visitar(i) - {k}
<13>
otro si padre(i) == i entonces
<14>
termina
<15>
otro
<16>
envia REGRESA a padre(i)
<17> al recibir REGRESA o RECHAZO desde el enlace j efectua
<18>
ve a <10>
<19> al recibir AVISO desde el enlace j efectua
<20>
visitado(j) <- VERDADERO
<21>
sin visitar(i) <- sin visitar(i) - {j}
8
5.
Funcionamiento del simulador utilizado
El simulador que se utilizó para este proyecto, utiliza tres tipos de mensaje: AVISO, DESCUBRE y REGRESA. El mensaje de AVISO es el
primero que se envı́a desde un nodo a sus nodos vecinos y sirve para verificar si estos han sido o no descubiertos. Si se tiene uno o más vecinos sin
descubrir, se elige alguno de ellos de manera aleatoria.
Una vez seleccionado el nodo a descubrir, se le envı́a el mensaje Descubre,
y de este modo se va recorriendo la gráfica hasta llegar al punto en que el
nodo no tenga vecinos que no hayan sido previamente descubiertos, entonces
comienza a enviar el mensaje Regresa al nodo que lo descubrió. Y esto se
realiza hasta llegar al nodo de inicio. A continuación se estudiará un ejemplo
para ver de manera más clara la ejecución del algoritmo DFS.
2
4
2
3
1
1
3
1
2
5
Figura 1: Trayectoria de los mensajes DESCUBRE
Como se puede observar en la Figura 1, podemos ver que el nodo de inicio
es el 1, y se analizará iteración por iteración:
1. Al tener dos vecinos escoge al azar al nodo 3 y le envı́a un mensaje
DESCUBRE. Éste, al recibir el mensaje DESCUBRE envı́a un mensaje
de AVISO a todos sus vecinos que no sean su padre (al 4 y al 5).
2. El nodo 3 al tener dos vecinos, le manda el mensaje DESCUBRE al
nodo 5. El nodo 5 al ser descubierto, envia un AVISO al nodo 4.
9
2
4
2
3
1
1
3
1
2
5
Figura 2: Trayectoria de los mensajes DESCUBRE y REGRESA
3. El nodo 5 sólo tiene un vecino que no ha sido descubierto (el nodo 4) y
le manda el mensaje DESCUBRE. El nodo 4, cuando recibe el mensaje,
envia un AVISO al nodo 3.
4. El nodo 4 no tiene ningún vecino sin descubrir, ası́ que envı́a el mensaje
REGRESA al nodo que lo descubrió.
5. El nodo 5 envı́a el mensaje REGRESA al nodo 3.
6. El nodo 3 envı́a el mensaje REGRESA al nodo 1.
7. El nodo 1 aún tiene un vecino sin descubrir (el nodo 2) y le manda el
mensaje DESCUBRE.(Figura 2)
8. El nodo 2 no tiene vecinos a quienes descubrir, entonces manda un
mensaje REGRESA al nodo 1.
El simulador utilizado, necesita como datos de entrada la gráfica de adyacencias en un archivo de texto. El número de lı́nea nos indica el número
de nodo y los datos vienen por parejas en un formato a,b; donde la posición
a nos indica el número de nodo vecino, y b nos indica el peso. La gráfica de
adyacencias para el ejemplo anterior, es la siguiente:
2,2 3,1
1,2
10
1,1 4,3 5,2
3,3 5,1
3,2
4,1
El simulador al ejecutarlo, da la siguiente salida.
1@0
2@1
3@1
3@1
4@2
5@2
5@2
4@3
4@3
3@4
5@4
3@5
1@6
2@7
1@8
DESCUBRE from 1
AVISO from 1
AVISO from 1
DESCUBRE from 1
AVISO from 3
AVISO from 3
DESCUBRE from 3
AVISO from 5
DESCUBRE from 5
AVISO from 4
REGRESA from 4
REGRESA from 5
REGRESA from 3
DESCUBRE from 1
REGRESA from 2
11
Parte II
PROGRAMACIÓN
DISTRIBUIDA
6.
6.1.
Herramientas de la simulación distribuida
MPI (Message-Passing Interface)
MPI es la especificación estándar para las librerı́as de paso de mensajes.
Existen varias implementaciones diferentes para las librerı́as de MPI; las más
utilizadas son: MPICH, LAM/MPI, MP-MPICH, WMPI, MacMPI.
MPICH es una implementación portable de la especificación estándar
MPI-1 que sirve para una gran variedad de entornos computacionales paralelos y distribuidos. MPICH contiene, junto con la librerı́a MPI, un entorno
de programación para trabajar con programas en MPI. El entorno de programación incluye un mecanismo de inicio portable, varias librerı́as para estudiar
el desempeño de programas en MPI, y una interfase X para todas las herramientas. Funciona en un amplio conjunto de plataformas y sistemas operativos, incluyendo Unix, Windows NT y Windows 2000/XP Professional. Esta
implementación
se
puede
obtener
de
la
dirección:
http://www-unix.mcs.anl.gov/mpi/mpich/download.html
LAM (Local Area Multicomputer) es un entorno de programación y de
desarrollo de sistemas para una red de computadoras heterogéneas. Con
LAM, un cluster dedicado o la infraestructura de una red de computadoras puede actuar como una computadora paralela para resolver un problema.
Funciona en redes de estaciones de trabajo Unix/Posix. Esta implementación
se puede obtener de la dirección: http://www.lam-mpi.org/7.0/download.php
MP-MPICH funciona en sistemas Unix, Windows NT y Windows 2000
/ XP Professional. Esta implementación se puede obtener de la dirección:
http://www.lfbs.rwth-aachen.de/mp-mpich/download form.html
WMPI funciona en plataforma Windows 95, 98, ME, NT y 2000. Esta implementación se puede obtener de la siguiente dirección de internet:
http://www.criticalsoftware.com/hpc/
12
MacMPI es una implementación parcial de MPI para computadoras
Macintosh. Esta puede ser conseguida en la siguiente dirección:
http://home.t-online.de/home/froehling/MacMPI/MacMPICW11.hqx
Todas estas diferentes implementaciones tienen diferencias en los sistemas
sobre los cuales pueden funcionar, pero utilizan la misma librerı́a estándar
MPI. Para el simulador que se quiere realizar, decidimos que puede ser desarrollado utilizando las herramientas LAM/MPI o MPICH.
6.1.1.
INSTALACIÓN DE MPICH
Para instalar MPICH es necesario conseguir el código fuente (mpich.tar.gz)
y compilarlo; este se puede bajar de la siguiente dirección:
http://www-unix.mcs.anl.gov/mpi/mpich/
El archivo mpich.tar.gz es un paquete de archivos comprimidos, ası́ que el
primer paso será descomprimirlo. Hay que colocar el archivo en el directorio
donde se vaya a instalar MPICH y ejecutar:
...]$ tar -zxvf mpich.tar.gz
Esto descomprimirá los archivos, creando una nueva carpeta mpich-1.2.5.
A continuación, es necesario compilar el programa:
...]$
...]$
...]$
...]$
cd mpich-1.2.5
./configure
make
make install
Después de estos pasos, MPICH estará listo para utilizarse. Al instalarlo
en varias computadoras, se recomienda instalarlo en todas en la misma ruta
o utilizar NFS, para evitar instalarlo más de una vez.
6.1.2.
Instalación de LAM / MPI
Hay dos opciones para instalar LAM / MPI:
1) Si se tiene un sistema RedHat 7.0 o mayor, o alguna otra distribución
de Linux de versión equivalente con soporte para RPM’s, se puede conseguir
una versión de LAM / MPI ya compilada y empaquetada en un RPM en la
página
13
http://www.lam-mpi.org
Una vez teniendo el archivo, sólo será necesario ejecutar la siguiente instrucción:
...]$ rpm -i lam-6.5.9-tcp.1.i386.rpm
Con esto, LAM / MPI quedará instalado, si se cuenta con los paquetes
necesarios para que LAM / MPI funcione.
Para desinstalar el RPM de LAM / MPI sólo es necesario ejecutar lo
siguiente:
...]$ rpm -e lam-6.5.9-tcp.1.i386
2) De otra manera, se puede conseguir el código fuente de LAM / MPI y
compilarlo personalmente. De la misma manera, este paquete se consigue en
la dirección
http://www.lam-mpi.org .
El archivo se llama lam-6.5.9.tar.gz y deberá ser colocado en el directorio
donde se desee instalar. Una vez ahı́, es necesario descomprimir y compilar:
...]$ tar -zxvf lam-6.5.9.tar.gz
...]$ cd lam-6.5.9
...]$ ./configure --prefix=/path/to/install/in
[ si se desea instalar en el directorio actual, se puede eliminar la opción
--prefix ]
...]$ make
...]$ make install
6.2.
Configuración final
¿Cómo indicar en que máquinas se va a trabajar?
Primero se necesita tener instalado el servidor y cliente RSH (que viene en
todas las distribuciones modernas de LINUX y UNIX) en todas las máquinas
en donde se va a trabajar. Una vez instalados, se necesita crear un archivo
.rhosts en el home del usuario. El archivo .rhosts debe tener el nombre de
cada máquina que va a utilizarse para ejecutar programas en paralelo seguido
del nombre del usuario, separados por un tabulador.
Ejemplo:
14
quetzal1.uam.mx
quetzal2.uam.mx
quetzal3.uam.mx
quetzal4.uam.mx
quetzal5.uam.mx
.
.
.
daco
daco
daco
daco
daco
No importa si en este archivo se tienen más máquinas que aquellas con las
que se pretende trabajar; el servicio rsh simplemente permite que un usuario
se pueda conectar de una máquina a otra sin tener que introducir contraseña
si fue previamente autorizado (en el archivo .rhosts). En algunos casos, es
necesaria la creación o la modificación del archivo /etc/hosts.equiv y debe
de ser editado como el ejemplo anterior.
6.2.1.
MPICH
Una vez instalado MPICH, es necesario indicar los nombres de las máquinas
que van a participar en la ejecución de nuestros programas. Examinando el
archivo:
...]$ vi /.../mpich-1.2.5/util/machines/machines.LINUX
Veremos que por default, al concluir la instalación se toma en cuenta que
la máquina local es la única que participará en la ejecución, varias veces. Es
necesario introducir ahı́ cada una de las máquinas participantes. Si se desea
que en alguna máquina se ejecute el proceso más de una vez, se vuelve a
escribir su nombre en otra (u otras) lı́neas.
El nombre correcto de cada máquina puede ser obtenido ejecutando en
ella el comando hostname.
6.2.2.
MPI/LAM
Para indicar en que máquinas se va a trabajar con MPI / LAM, es necesario definir un archivo de texto con el nombre de cada máquina que va a
trabajar en una lı́nea diferente y luego ejecutar la siguiente instrucción:
...]$ lamboot -v hostfile
15
Donde hostfile es el nombre del archivo con los nombres de las máquinas.
Los nombres correctos de las máquinas pueden conseguirse ejecutando el
comando hostname en cada una de ellas.
La instrucción lamboot deja preparadas a todas las máquinas indicadas
en el archivo hostfile para empezar a trabajar.
Cuando se terminen de usar las máquinas con LAM / MPI, es recomendable ejecutar el comando lamhalt.
7.
Compilación y ejecución de programas.
Las dos implementaciones de MPI que estamos tratando coinciden en la
manera de compilar y ejecutar un programa.
Para compilar un programa en C y C++ se utilizan las mismas instrucciones que con el compilador gcc de Linux.
Ejemplo en C:
...]$ mpicc -o <nombre del ejecutable><nombre del fuente .c>
Ejemplo en C++:
...]$ mpiCC -o <nombre del ejecutable><nombre del fuente .cc>
Una vez compilado el programa, se puede ejecutar en una o más máquinas
de la red. Para esto es necesario ejecutar la siguiente instrucción:
...]$ mpirun -np <número de máquinas><nombre del ejecutable>
8.
Comunicación básica con MPI
A continuación se explicarán algunas de las funciones de MPI para realizar
la comunicación entre procesadores y posteriormente, se presentan algunos
ejemplos de su utilización.
8.1.
Funciones de MPI
void Init(int& argc, char**& argv):
16
Esta función se encarga de crear un “comunicador” entre todos los
procesos que se iniciaron en las diferentes máquinas. Los argumentos
que recibe son argc y argv, que vienen de la lı́nea de comando a la hora
de arrancar el programa.
int Comm::Get rank() const:
Esta función regresa el número o indentificador (entero) del proceso
que hizo el llamado de esta función. El “rank” cero se le asigna al nodo
coordinador (desde donde se arrancó el programa) y numera en orden
ascendente según el orden de iniciación de cada proceso en las demás
máquinas (que depende a su ves del archivo de hosts incluidos en la
red).
int Comm::Get size() const:
Esta función regresa el número de procesos que se iniciaron en total en
las diferentes máquinas.
void MPI::Comm::Bcast(void* buffer,
MPI::Datatype& datatype, int root):
int
count,
const
Esta función hace la difusión (broadcast) de un mensaje a todos los
demás procesos de la red.
El primer argumento es el buffer que se va a enviar.
El segundo argumento es el número de elementos dentro del buffer que
se van a mandar.
El tercero define el tipo de dato que se enviará.
El último argumento define de que nodo parte el mensaje. Si el número
coincide con el identificador del proceso, quiere decir que este nodo
será el. Si el número no coincide con el identificador del proceso entonces
indica el que envı́e el mensaje; si el número no coincide, quiere decir
que se recibirá un mensaje del nodo indicado por ese número.
void MPI::Comm::Barrier() const = 0:
Esta instrucción simplemente detiene todos los procesos hasta que todos lleguen al mismo punto de ejecución.
17
8.2.
Envı́o y recepción de mensajes
Ejemplo de envı́o y recepción de un mensaje:
#include <iostream>
#include <mpi.h>
using namespace std;
int main(int argc, char *argv[]) {
int num;
int rank, size, tag, dest, from=0, tag=1;
char cad[30];
MPI::Status status;
MPI::Init(argc, argv);
rank = MPI::COMM WORLD.Get rank();
size = MPI::COMM WORLD.Get size();
cout << “Process # ” << rank << “ saluda” << endl;
if (rank == 0) {
cout << “Enter the msg: ”;
cin >> cad;
for (dest=1;dest<size;dest++)
MPI::COMM WORLD.Send(cad,5, MPI::CHAR,dest, tag);
}
if (rank!=0) {
MPI::COMM WORLD.Recv(cad,5, MPI::CHAR,from,tag);
cout << “Process ” << rank << “ received ” << cad << endl;
}
MPI::COMM WORLD.Barrier();
cout << “Process ” << rank << “ exiting” << endl;
MPI::Finalize();
return 0;
}
8.3.
Otras funciones de MPI
void Comm::Send(const void* buf, int count, const Datatype&
datatype, int dest, int tag) const
18
Esta instrucción envı́a un mensaje almacenado en el buffer definido por
const void* buf, del tamaño que es indicado por int count y de tipo especificado por const Datatype& datatype al proceso cuyo identificador
sea igual que int dest. El quinto argumento es una etiqueta que tiene
este mensaje, que tiene que coincidir en el envı́o y la recepción para
que se pueda dar la transmisión.
void Comm::Recv(void* buf, int count, const Datatype& datatype,
int source, int tag) const
Esta función es la contraparte de la función Send. Sus argumentos todos
son iguales, a excepción del cuarto, que en vez de ser el nodo al que
debe enviarse el mensaje es el nodo de donde proviene el mensaje.
void Finalize()
Esta función cierra el “comunicador” que se abrió con la función Init.
8.4.
Ejemplo de Broadcast
Broadcast de un mensaje :
#include <iostream>
#include <mpi.h>
using namespace std;
int main(int argc, char *argv[]) {
int num;
int rank, size, tag, next, from;
char cad[30];
MPI::Status status;
MPI::Init(argc, argv);
rank = MPI::COMM WORLD.Get rank();
size = MPI::COMM WORLD.Get size();
cout << “Process # ” << rank << “ saluda ” << endl;
if (rank == 0) {
cout << “Enter the msg: ”;
cin >> cad;
MPI::COMM WORLD.Bcast(cad,5,MPI::CHAR,0);
}
19
if (rank!=0) {
MPI::COMM WORLD.Bcast(cad,5,MPI::CHAR,0);
cout << “Process ” << rank << “ received ” << cad << endl;
}
MPI::COMM WORLD.Barrier();
cout << “Process ” << rank << “ exiting” << endl;
MPI::Finalize();
return 0;
}
20
Parte III
PROCEDIMIENTO Y
RESULTADOS
9.
El algoritmo DFS distribuido
Para desarrollar el algoritmo DFS distribuido, es necesario dividir la gráfica, de manera que cada procesador que participe simulará un número de nodos, que es fracción del tamaño total de la gráfica. El algoritmo DFS empieza
mandando un mensaje DESCUBRE a uno de los nodos de la gráfica; este
nodo inicial tendrá que estar simulado forzosamente en el procesador maestro, para que él comience su simulación mientras los otros esperan. Cuando
un mensaje tenga que salir a un nodo que está en otra de las secciones de
la gráfica arrancará la simulación en el procesador encargado de simular esa
parte del grafo.
Para hacer la versión distribuida del algoritmo DFS, hay que tomar en
cuenta que no existe la posibilidad de que los diferentes procesadores se desincronicen. Esto es por que un nodo define a sus vecinos como sus hijos y espera
a que ellos a su vez definan a sus hijos y regresen un mensaje cuando hayan
terminado. Por esta razón nunca sucederá que un nodo reciba un mensaje
con una etiqueta de tiempo anterior al tiempo actual del procesador en el
que se esta simulando. Entonces, se utilizará el método optimista para desarrollar la versión distribuida de este algoritmo. A demás, dado que no existe
la posibilidad de que un nodo reciba mensajes “del pasado” no será necesario
hacer “rollback” para corregir los errores ocasionados por esto.
10.
Adaptación del código existente
En el simulador que se tenı́a, la gráfica que se va a simular, se alimenta
al programa por medio de un archivo de texto. Para realizar la simulación
distribuida, es necesario que todos los procesadores participantes, conozcan
la gráfica y sepan cuales de los nodos de la gráfica les toca simular. Para esto,
se hará uso de otro archivo de entrada que será una tabla en donde se indique
21
en que procesador se simulará cada nodo. A este archivo le llamaremos la
“tabla de asignación de procesadores”.
Ya que todos los procesadores participantes cuenten con la gráfica y la
tabla de asignación de procesadores, el procesador maestro (con rank cero)
puede empezar la simulación, al enviar un mensaje de DESCUBRE al
primer nodo de la gráfica. Mientras tanto, los demás procesadores, esperan
a que llegue algún mensaje a alguno de los nodos que ellos están simulando.
La simulación que se lleva a cabo en el nodo maestro es igual que en la versión no distribuida, siempre y cuando los destinos de los mensajes que tenga
que enviar sean nodos que él este procesando. En el momento en el que uno
de los nodos a los que tenga que enviar algo no esté siendo simulado por
él, tendrá que enviar un mensaje externo (utilizando MPI) y eso hará que
otro de los procesadores comience a simular. Esto continuará funcionando
ası́, hasta que eventualmente todos los procesadores “despierten” y realicen
la parte de la simulación que les corresponde. Si en algún momento uno de los
procesadores se queda sin trabajo, quedará en espera de recibir algún otro
mensaje de los nodos vecinos que se estén simulando en otro procesador.
Los procesadores que no son el maestro terminarán su trabajo cuando todos
los nodos que esta simulando hayan enviado un mensaje REGRESA a su
respectivo padre. El procesador maestro terminará cuando todos excepto el
nodo inicial hayan mandado este mismo mensaje.
Para realizar la adaptación del código que ya se tenı́a, se intento hacerlo
modificándolo en la menor medida posible esto, con el objeto de que en un
futuro se facilite la simulación de otros algoritmos en este simulador. Para
ello, se comenzó por implementar una nueva clase que se encargarı́a de toda
la comunicación con MPI entre los diferentes procesadores (MPIComm).
El constructor de esta clase MPIComm::MPIComm(int argc, char
**argv) se encarga de inicializar MPI creando el comunicador y de asignar
los valores rank (número de procesador) y size (número total de procesadores que participarán).
Primero, se necesitaba de un método que transmitiera los archivos con el
gráfico y la tabla de asignación de procesadores a todos los procesadores participantes en la simulación. Este método tiene como prototipo:
void MPIComm::FBcast(int argc, char **filenames). Lo que hace este
método es hacer un “broadcast” de los dos archivos hacia todos los demás
procesadores. Estos lo reciben y crean dos archivos para guardarlos.
22
Para continuar, se necesitó implementar un método que pusiera en espera
a los procesadores que no fueran el maestro mientras no recibieran un primer
mensaje. Este método es: Event *MPIComm::FirstMsg(). Este método
utiliza una función de MPI que espera a recibir un mensaje, y detiene la
ejecución hasta que éste llegue. Cuando el mensaje llega, lo empaqueta en
un objeto de tipo Event y lo regresa para que se pueda iniciar la simulación
en este procesador.
El método void MPIComm::SendMsg(Event *e, int target)se encarga de mandar los mensajes externos hacia otros procesadores. El método
recibe como parámetros el Evento (que tiene el mensaje que se va a enviar) y
el destino. Para poder enviar el mensaje, lo convierte en un arreglo de cuatro
flotantes.
Se implemento un método que se encarga de recibir los mensajes externos:
Event * MPIComm::RecvMsg(). Este método recibe el mensaje como
un arreglo de flotantes y lo convierte a un objeto de tipo Event. Si la primera
casilla del arreglo que se recibe corresponde al código ASCII del caracter ’F’,
quiere decir que el procesador que envió el mensaje esta indicando que ha
terminado de simular a todos los nodos que el manejaba.
Se implementaron dos métodos, int MPIComm::getRank() y int MPIComm::getSize() que regresan el valor de size y el rank para cada procesador.
El método void MPIComm::end() se utiliza para que los procesadores
indiquen al maestro (rank cero) que ya han terminado de simular a todos los
nodos que le correspondı́an. Para esto, envı́a un arreglo de cuatro flotantes,
en donde la primera casilla es corresponde al código ASCII del caracter ’F’.
Se desarrollo otro método llamado int MPIComm::finished() que regresa el valor de la variable ended. En esta variable se cuentan los procesadores que han terminado de simular a todos sus nodos. Este método sólo
será utilizado por el maestro.
Finalmente, el destructor de esta clase MPIComm:: MPIComm() se
encarga de finalizar la comunicación con MPI cerrando el comunicador.
Una vez contando con la clase MPIComm se procedió a modificar el
código existente de manera que se utilizara a esta nueva clase para hacer la
comunicación externa entre los diferentes procesadores.
23
Para comenzar, se modificó la clase Graph, de manera que también cree
una estructura de datos (lista ligada) con la tabla de asignación de procesadores. Ası́ mismo, se implementó un método int Graph::getRank(int
pos) que recibe el número de un nodo y regresa el rank procesador que
lo está simulando. Para ello, fue necesario modificar la clase LinkList y
agregarle un método template<class T> T LinkList<T>::getNext(int
posit) que regresa el elemento que se encuentra tantas posiciones adelante
como lo indica el parámetro posit.
Para continuar, se modificó el main, de manera que manda a llamar
al método FBcast y al constructor de la clase MPIComm. Después, el
procesador maestro comienza su simulación de manera normal, mientras los
demás se ponen en espera de un primer mensaje, haciendo un llamado al
método FirstMsg de la clase MPIComm. Cuando cada procesador recibe
su primer mensaje, lo inserta en la cola de eventos y comienza su simulación.
También se modificó la clase DFS de manera que el método virtual int
receive(Event *e,Graph myNetwork) devuelve el valor uno cuando el nodo
que se está simulando, envı́a un mensaje de REGRESA.
Después se modificó el método void Simulation::run(Graph
myNetwork) de la clase Simulation. Primero se cuenta el número de nodos que está simulando el procesador actual; de esta manera, se puede saber
cuando el procesador ha terminado su trabajo, al contar también el número
de mensajes tipo REGRESA que se han enviado. Si se trata del procesador maestro, se resta uno al número de nodos simulados, por que el nodo
inicial nunca manda un mensaje REGRESA. Luego, se revisa si hay un
mensaje externo haciendo una llamada al método MPIComm::RecvMsg
y si lo hay se inserta en la cola de eventos. Después, en caso de que haya
eventos en la cola, se procede a desencolarlo y atenderlo. El método int
Process::receive(Event *e,Graph myNetwork) devolverá el valor uno
cuando se haya enviado un mensaje REGRESA; se cuenta cuantos mensajes de este tipo han aparecido en la variable fin. En los nodos que no son el
maestro, cuando este número coincida con el número de nodos que se están
simulando en el procesador actual, se habrá terminado la simulación; se saldrá del método run, para regresar al main y se mandará a rank cero un
mensaje indicando que ya terminó (utilizando el método MPIComm::end()).
El procesador maestro, terminará sólo cuando todos los demás procesadores
hayan terminado (para esto utiliza los métodos MPIComm::finished() y
MPIComm::getSize()) y además, ya todos los nodos que él este simulando
24
Figura 3: Grafo
(excepto el inicial) hayan mandado el mensaje REGRESA.
11.
Resultados Finales
Se corrió el algoritmo DFS distribuido en un grafo con seis nodos, utilizando diferentes tamaños de particiones. A continuación se presenta el resultado
de una corrida del simulador DFS normal y después, los resultados de las
diferentes corridas del algoritmo distribuido. El grafo que se utilizó es el que
se muestra en la figura 3.
1. Ejecución en un solo procesador. El grafo no se particiona (figura 4).
Resultado:
tamano de la red 6
Rank 0: 1@0 DESCUBRE from 1
Rank 0: 4@1 AVISO from 1
Rank 0: 2@1 AVISO from 1
Rank 0: 3@1 AVISO from 1
Rank 0: 4@1 DESCUBRE from 1
Rank 0: 2@2 AVISO from 4
Rank 0: 3@2 AVISO from 4
Rank 0: 5@2 AVISO from 4
25
Figura 4: Grafo sin partición
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
0:
0:
0:
0:
0:
0:
0:
0:
0:
0:
0:
0:
0:
0:
0:
0:
0:
0:
6@2 AVISO from 4
2@2 DESCUBRE from 4
1@3 AVISO from 2
3@3 AVISO from 2
3@3 DESCUBRE from 2
1@4 AVISO from 3
4@4 AVISO from 3
5@4 AVISO from 3
5@4 DESCUBRE from 3
4@5 AVISO from 5
6@5 AVISO from 5
6@5 DESCUBRE from 5
4@6 AVISO from 6
5@6 REGRESA from 6
3@7 REGRESA from 5
2@8 REGRESA from 3
4@9 REGRESA from 2
1@10 REGRESA from 4
En esta ejecución, todos los mensajes que se envı́an los nodos son “mensajes internos”; es decir, como hay un solo procesador simulando, no se utiliza
MPI para enviar mensajes entre procesadores.
2. Ejecución en dos procesadores. El grafo se particiona como se observa
26
Figura 5: Grafo particionado en dos
en la figura 5.
Resultado:
Rank 1: 4@1 AVISO from 1
Rank 1: 2@1 AVISO from 1
Rank 1: 4@1 DESCUBRE from 1
Rank 1: 2@2 AVISO from 4
Rank 1: 6@2 AVISO from 4
Rank 1: 2@2 DESCUBRE from 4
Rank 1: 4@4 AVISO from 3
Rank 1: 4@5 AVISO from 5
Rank 1: 6@5 AVISO from 5
Rank 1: 6@5 DESCUBRE from 5
Rank 1: 4@6 AVISO from 6
Rank 1: 2@8 REGRESA from 3
Rank 1: 4@9 REGRESA from 2
tamano de la red 6
Rank 0: 1@0 DESCUBRE from 1
Rank 0: 3@1 AVISO from 1
Rank 0: 3@2 AVISO from 4
Rank 0: 5@2 AVISO from 4
Rank 0: 1@3 AVISO from 2
Rank 0: 3@3 AVISO from 2
27
Figura 6: Grafo particionado en tres
Rank
Rank
Rank
Rank
Rank
Rank
Rank
0:
0:
0:
0:
0:
0:
0:
3@3 DESCUBRE from 2
1@4 AVISO from 3
5@4 AVISO from 3
5@4 DESCUBRE from 3
5@6 REGRESA from 6
3@7 REGRESA from 5
1@10 REGRESA from 4
Se puede identificar en que procesador se generó cada mensaje. Cuando
el nodo fuente y el nodo destino de un mensaje no se están simulando en un
mismo procesador se envı́a un mensaje externo utilizando MPI.
3. Ejecución en tres procesadores. El grafo se particiona como se observa
en la figura 6.
Resultado:
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
1:
1:
1:
1:
1:
1:
1:
1:
2@1
2@2
5@2
2@2
5@4
5@4
5@6
2@8
AVISO from 1
AVISO from 4
AVISO from 4
DESCUBRE from 4
AVISO from 3
DESCUBRE from 3
REGRESA from 6
REGRESA from 3
28
Figura 7: Grafo particionado en cuatro
Rank 2: 3@1 AVISO from 1
Rank 2: 3@2 AVISO from 4
Rank 2: 6@2 AVISO from 4
Rank 2: 3@3 AVISO from 2
Rank 2: 3@3 DESCUBRE from 2
Rank 2: 6@5 AVISO from 5
Rank 2: 6@5 DESCUBRE from 5
Rank 2: 3@7 REGRESA from 5
tamano de la red 6
Rank 0: 1@0 DESCUBRE from 1
Rank 0: 4@1 AVISO from 1
Rank 0: 4@1 DESCUBRE from 1
Rank 0: 1@3 AVISO from 2
Rank 0: 1@4 AVISO from 3
Rank 0: 4@4 AVISO from 3
Rank 0: 4@5 AVISO from 5
Rank 0: 4@6 AVISO from 6
Rank 0: 4@9 REGRESA from 2
Rank 0: 1@10 REGRESA from 4
4. Ejecución en cuatro procesadores. El grafo se particiona como se
observa en la figura 7.
Resultado:
29
Rank 1: 2@1 AVISO from 1
Rank 1: 2@2 AVISO from 4
Rank 1: 6@2 AVISO from 4
Rank 1: 2@2 DESCUBRE from 4
Rank 1: 6@5 AVISO from 5
Rank 1: 6@5 DESCUBRE from 5
Rank 1: 2@8 REGRESA from 3
Rank 3: 4@1 AVISO from 1
Rank 3: 4@1 DESCUBRE from 1
Rank 3: 4@5 AVISO from 5
Rank 3: 4@6 AVISO from 6
Rank 3: 4@4 AVISO from 3
Rank 3: 4@9 REGRESA from 2
Rank 2: 3@1 AVISO from 1
Rank 2: 3@2 AVISO from 4
Rank 2: 3@3 AVISO from 2
Rank 2: 3@3 DESCUBRE from 2
Rank 2: 3@7 REGRESA from 5
tamano de la red 6
Rank 0: 1@0 DESCUBRE from 1
Rank 0: 1@3 AVISO from 2
Rank 0: 5@2 AVISO from 4
Rank 0: 1@4 AVISO from 3
Rank 0: 5@4 AVISO from 3
Rank 0: 5@4 DESCUBRE from 3
Rank 0: 5@6 REGRESA from 6
Rank 0: 1@10 REGRESA from 4
Dado el grafo que se utilizo y la partición que se hizo, todos los mensajes
entre nodos tendrán que ser externos. Esto es, por que no se esta simulando
en un mismo procesador dos nodos que sean vecinos. Se podrı́a modificar
las particiones de manera que hubiera algunos nodos que pudieran mandar
mensajes internos.
5. Ejecución en cinco procesadores. El grafo se particiona como se observa en la figura 8.
Resultado:
30
Figura 8: Grafo particionado en cinco
Rank 1: 2@1 AVISO from 1
Rank 1: 2@2 AVISO from 4
Rank 1: 2@2 DESCUBRE from 4
Rank 1: 2@8 REGRESA from 3
Rank 3: 4@1 AVISO from 1
Rank 3: 4@1 DESCUBRE from 1
Rank 3: 4@4 AVISO from 3
Rank 3: 4@5 AVISO from 5
Rank 3: 4@6 AVISO from 6
Rank 3: 4@9 REGRESA from 2
Rank 2: 3@1 AVISO from 1
Rank 2: 3@2 AVISO from 4
Rank 2: 3@3 AVISO from 2
Rank 2: 3@3 DESCUBRE from 2
Rank 2: 3@7 REGRESA from 5
Rank 4: 5@2 AVISO from 4
Rank 4: 5@4 AVISO from 3
Rank 4: 5@4 DESCUBRE from 3
Rank 4: 5@6 REGRESA from 6
tamano de la red 6
Rank 0: 1@0 DESCUBRE from 1
Rank 0: 6@2 AVISO from 4
Rank 0: 1@3 AVISO from 2
Rank 0: 1@4 AVISO from 3
31
Figura 9: Grafo particionado en seis
Rank 0: 6@5 AVISO from 5
Rank 0: 6@5 DESCUBRE from 5
Rank 0: 1@10 REGRESA from 4
Igual que en el caso anterior, todos los mensajes entre nodos resultan ser
externos. Esto es, por que no se esta simulando en un mismo procesador dos
nodos que sean vecinos.
6. Ejecución en seis procesadores. El grafo se particiona como se observa
en la figura 9.
Resultado:
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
Rank
1:
1:
1:
1:
5:
5:
5:
2:
2:
2:
2:
2:
2@1
2@2
2@2
2@8
6@2
6@5
6@5
3@1
3@2
3@3
3@3
3@7
AVISO from 1
AVISO from 4
DESCUBRE from 4
REGRESA from 3
AVISO from 4
AVISO from 5
DESCUBRE from 5
AVISO from 1
AVISO from 4
AVISO from 2
DESCUBRE from 2
REGRESA from 5
32
Rank 4: 5@2 AVISO from 4
Rank 4: 5@4 AVISO from 3
Rank 4: 5@4 DESCUBRE from 3
Rank 4: 5@6 REGRESA from 6
Rank 3: 4@1 AVISO from 1
Rank 3: 4@1 DESCUBRE from 1
Rank 3: 4@4 AVISO from 3
Rank 3: 4@5 AVISO from 5
Rank 3: 4@6 AVISO from 6
Rank 3: 4@9 REGRESA from 2
tamano de la red 6
Rank 0: 1@0 DESCUBRE from 1
Rank 0: 1@3 AVISO from 2
Rank 0: 1@4 AVISO from 3
Rank 0: 1@10 REGRESA from 4
En este caso, dado que el número de procesadores es igual al número de
nodos de la gráfica, todos los mensajes tendrán que ser externos. No hay
una manera de particionar para que no suceda esto, por que cada procesador
deberá simular por lo menos un nodo.
12.
Observaciones y Conclusiones
Se puede observar en los resultados de la sección anterior, que la salida que
se obtiene en las diferentes ejecuciones es la misma, aunque los mensajes no
se encuentran ordenados según su etiqueta de tiempo. Esto no es un error del
algoritmo, como podrı́a parecer; lo que sucede, es que MPI redirige la salida a
pantalla de los diferentes procesadores y la manda al maestro (rank 0). Esto
quiere decir que el texto de salida se tiene que enviar utilizando la red, lo cual
toma un cierto tiempo; entonces, cuando finalmente llega a rank 0 para ser
desplegado, puede ser que el maestro ya haya mandado a desplegar algo que
se haya generado más tarde, pero que le haya llegado antes. Por otra parte,
cada procesador simulará a un ritmo diferente (dependiendo de su capacidad
y carga de trabajo) de esta manera, un procesador puede avanzar más rápido
y enviar a desplegar mensajes con etiqueta más avanzada que otro procesador
más lento.
Con los resultados, se puede ver la importancia de tener particiones ade33
cuadas a cada gráfica que se simule. A diferencia de lo que se podrı́a pensar,
en el ejemplo que se simuló (un grafo con seis nodos), no es conveniente
utilizar el número máximo de procesadores para hacer el trabajo. Si se hace
esto, todos los mensajes entre nodos tienen que ser externos, utilizando la red
local que comunica a las diferentes computadoras, y esto ocasionar una gran
reducción de la eficiencia. En el otro extremo, utilizando un solo procesador,
se tiene que todos los mensajes son internos, pero no se esta utilizando el potencial de la ejecución en paralelo. El caso óptimo, debe estar en algún punto
intermedio entre estos dos extremos. Una vez habiendo escogido el número
de procesadores que participarán, ahora se tiene que hacer una partición adecuada; la mejor manera de particionar será aquella en la que puedan quedar
la mayor cantidad de nodos vecinos simulándose en un mismo procesador.
Cabe mencionar que el uso de la herramienta para programación en paralelo MPI, hizo que la programación fuera mucho más sencilla. El uso de
sockets queda encapsulado y para hacer la comunicación entre procesadores
sólo se necesita hacer llamadas a funciones ya hechas.
13.
Trabajo por hacer
En esta sección se discutirán las perspectivas de trabajo que quedan por
desarrollar alrededor de este proyecto.
En el algoritmo DFS distribuido, como ya se indicó con anterioridad, no
existe la posibilidad de que los diferentes procesadores se desincronicen, por
lo cual no hay necesidad de implementar “roll back”. Para trabajos futuros
en donde se quiera simular otros algoritmos que sı́ lo necesiten, será necesario
implementar una clase que vaya guardando un historial para poder regresar
la simulación a un estado en donde no haya mensajes con etiqueta de tiempo
anterior a la del simulador. También será necesario modificar la clase de comunicación MPIComm de manera que se incluya el envı́o de “antimensajes”
para indicar a los demás procesadores que se ha hecho un “rollback” y que
todos los mensajes enviados a partir de cierto tiempo no sean tomados en
cuenta.
Para hacer la versión distribuida del algoritmo DFS, se tuvo que modificar
la condición de paro del ciclo iterativo. En la versión normal, la condición
de paro era que la cola de eventos quedara vacı́a. En la versión distribuida, cada procesador tiene una cola de eventos diferente, en donde están los
34
eventos concernientes únicamente a los nodos que él procesa. Entonces esta
cola puede estar vacı́a durante mucho tiempo en un procesador, mientras los
nodos que él simula no recibı́an mensajes de sus vecinos simulados en otros
procesadores. La condición de paro se modificó de manera que el algoritmo
termina cuando el número de mensajes REGRESA que se han enviado en
un procesador sea igual al número de nodos simulados en él. Esta condición
de paro, sin embargo, es especı́ficamente para el algoritmo DFS; queda pendiente desarrollar un algoritmo distribuido para calcular la condición de para
de manera que no sea especı́fica para el algoritmo DFS, de manera que se
puedan simular otros algoritmos de redes con este mismo simulador.
En los algoritmos distribuidos se tiene que dividir el problema que se
quiere resolver, de manera que cada uno de los procesadores participantes,
trabajará sobre una porción del mismo. En este caso, se divide la gráfica
y cada procesador simulará un número menor de nodos que el del grafo
original. Lo que se hizo en este trabajo, fue pedir al usuario que indique
en que procesador se simulará cada nodo. Un trabajo a futuro, puede ser
desarrollar un algoritmo que se encargue de hacer la división del gráfico de
manera inteligente sin que el usuario tenga que hacer este trabajo. Esto se
podrı́a implementar de manera que se pueda decidir entre indicar la división
del grafo con un archivo o que el simulador lo haga de manera automática.
Como se comenta en la sección anterior, la manera de particionar el grafo
será crucial en el desempeño del algoritmo, de manera que queda pendiente
investigar una manera metódica de generar las particiones de manera que el
desempeño del algoritmo distribuido sea óptimo.
35
Parte IV
APÉNDICES
14.
14.1.
Código fuente del simulador DFS distribuido
Clase: DFS / Archivo dfs.cc
#include “simulation.h”
#include “linklist.cc”
#include “mpi++.h”
using namespace std;
enum message { DESCUBRE = ’A’, REGRESA = ’B’, AVISO = ’C’ };
class DFS;
class Message;
class Message: public Event
{
private:
friend class DFS;
public:
// constructor nulo
Message()
{};
// constructor inicializado: nombre, tiempo, destino, origen
Message(char n, float t, int d, int o): Event(n, t, d, o)
{};
// destructor
˜Message()
{};
};
class DFS: public Model
{
private:
int visitado;
int padre;
int fin;
36
LinkList<int> sin visitar;
LinkList<int> esperando;
public:
virtual void init()
{
int i, *n;
fin=0;
visitado = 0;
padre = me;
while (n = neighbors->next()){
sin visitar.inqueue(i = *n);
}
};
void send(char msj, int dst,Graph myNetwork)
{
Event *e;
e = new Message(msj, clock+1.0, dst, me);
transmit(e,myNetwork);
}
virtual int receive(Event *e,Graph myNetwork)
{
Message *te;
atiende((Message *) e,myNetwork);
return(fin);
};
void continua exploracion(Graph myNetwork)
{
int *h,l;
if (!sin visitar.isEmpty()) {
l = sin visitar.dequeue();
send(DESCUBRE, l,myNetwork);
}
else {
if (padre != me){
fin=1;
send(REGRESA, padre,myNetwork);
}
};
37
};
void atiende(Message *e,Graph myNetwork){
int j = e->getSource();
switch(e->getName()){
case DESCUBRE:
cout << “Rank ” << MPIComm::getRank()
<< “: ” << me << “@” << clock << ”
DESCUBRE from “ << j << endl;
sin visitar.remove(j);
if (!visitado){
visitado = 1;
padre = j;
while (int *k = neighbors->next()) //
sin visitar NO!
{
if (padre != *k)
send(AVISO, *k,myNetwork);
};
continua exploracion(myNetwork);
};
break;
case REGRESA:
cout << “Rank ” << MPIComm::getRank()
<< “: ” << me << “@” << clock << “ REGRESA from ” << j << endl;
continua exploracion(myNetwork);
break;
case AVISO:
cout << “Rank ” << MPIComm::getRank()
<< “: ” << me << “@” << clock << “ AVISO from ” << j << endl;
sin visitar.remove(j);
break;
};
};
};
main(int argc, char **argv)
{
int i,j;
38
MPIComm mpi(argc,argv);
Simulator *myEngine = new Simulator(500.0);
mpi.FBcast(argc,argv);
Graph myNetwork(mpi.getRank());
if(mpi.getRank()==0)
cout << “tamano de la red ” << myNetwork.getSize() << endl;
Simulation myExperiment = Simulation(myNetwork, myEngine);
for (int i = 1; i <= myNetwork.getSize(); i++) {
DFS *newmodel = new DFS();
myExperiment.setModel(newmodel, i);
}
if (mpi.getRank()==0){
Message *e1 = new Message(DESCUBRE,0.0,1,1);
myExperiment.init(e1);
myExperiment.run(myNetwork);
}
else {
Message *e1;
e1=(Message *)mpi.FirstMsg();
myExperiment.init(e1);
myExperiment.run(myNetwork);
mpi.end();
}
}
14.2.
Clase: Simulation
14.2.1.
Archivo simulation.h
// Archivo: simulation.h
// interfaz de la clase ’Simulation’
// R. Marcelin J. (02/08/98)
#if !defined( SIMULATION )
#define SIMULATION
#include “graph.h”
#include “model.h”
#include “event.h”
39
#include
#include
#include
#include
#include
#include
“process.h”
“simulator.h”
“avltree.cc”
“mpicomm.h”
<iostream>
<fstream>
class Simulation
{
public:
Simulation(Graph graph, Simulator *engine);
˜Simulation(); //
void setModel(Model *model, int id);
void init(Event *e); //
void run(Graph myNetwork); //
private:
Graph net; //
AVLTree<Process> table; //
Simulator *myEngine; //
};
#endif
14.2.2.
Archivo simulation.cc
// Archivo: simulation.cc
// implementacion de la clase ’Simulation’
// R. Marcelin J. (02/08/98)
#include “simulation.h”
//@ Simulation(): constructor
//——————————————————————
Simulation::Simulation(Graph graph, Simulator *engine):
net(graph)
{
int i, *r;
Process *newproc;
40
LinkList<int> *row;
myEngine = engine;
for (i = 1; i <= net.getSize(); i++)
{
row = net.getRow(i);
newproc = new Process(*row, engine, i);
table.insert(*newproc, i);
};
for (i = 1; i <= net.getSize(); i++)
{
row = net.getRow(i);
while (r = row->next())
{
newproc = table.find(*r);
newproc->addRxNeighbor(i);
};
};
}
//@ ˜Simulation(): destructor
//——————————————————————
Simulation::˜Simulation()
{}
//@ setModel():
//——————————————————————
void Simulation::setModel(Model *model, int id)
{
Process *proc;
proc = table.find(id);
proc->setModel(model);
}
//@ init():
//——————————————————————
void Simulation::init(Event *e)
{
myEngine->insertEvent(e);
//@ run():
41
}
//——————————————————————
void Simulation::run(Graph myNetwork)
{
int t,i,fin=0,numproc=0,rank;
long j=1;
Event *e;
Process *nextproc;
rank=MPIComm::getRank();
for(i=1;i<=myNetwork.getSize();i++)
if(myNetwork.getRank(i)==rank)
numproc++;
if(rank==0) numproc–;
i=1;
while(1 /*myEngine->isOn()*/){
if (e = MPIComm::RecvMsg())
myEngine->insertEvent(e);
if(myEngine->isOn()){
e = myEngine->dequeueEvent();
t = e->getTarget();
nextproc = table.find(t);
fin+=nextproc->receive(e,myNetwork);
}
if(rank==0){
if(MPIComm::finished()==MPIComm::getSize()-1)
if(fin==numproc && !myEngine->isOn()) break;
}
else if(fin==numproc && !myEngine->isOn()) break;
}
}
14.3.
Clase: Model
14.3.1.
Archivo model.h
//
//
//
//
Archivo: model.h
intefaz de la clase ’Model’
R. Marcelin J. (02/08/99)
esta es una version modificada que extiende la definicion de la clase
42
// MODELO, para permitir la concatenacion de automatas (29/10/01).
#if !defined( MODEL )
#define MODEL
#include “event.h”
#include “linklist.cc”
#include “graph.h”
#include “mpicomm.h”
//#include “intset.h”
class Model
{
public:
Model(); // constructor
˜Model(); // destructor
virtual void init();
virtual void init(int pid);
void transmit(Event *e,Graph myNetwork); // envia evento
void transmitAll(Event *e);
virtual int receive(Event *e,Graph myNetwork); // recibe event
friend class Process;
protected:
Process *myProcess; // proceso a cargo
int me;
LinkList<int> *neighbors;
LinkList<Model *> partners; // esta es la modificacion
float clock;
};
#endif
14.3.2.
//
//
//
//
//
Archivo model.cc
Archivo: model.cc
implementacion de la clase ’Model’
R. Marcelin J. (02/08/99)
esta es una version modificada que extiende la definicion de la clase
MODELO, para permitir la concatenacion de automatas (29/10/01).
43
#include “event.h”
#include “model.h”
#include “process.h”
#include <iostream.h>
//@ Model(): constructor
//——————————————————————
Model::Model()
{
clock = 0;
}
//@ ˜Model(): destructor
//——————————————————————
Model::˜Model()
{
}
//@ init():
//——————————————————————
void Model::init()
{
}
//@ init():
//——————————————————————
void Model::init(int pid)
{
//@ transmit(): transmite evento
//——————————————————————
void Model::transmit(Event *e,Graph myNetwork)
{
myProcess->transmit(e,myNetwork);
}
//@ transmitAll():
//——————————————————————
void Model::transmitAll(Event *e)
{
myProcess->transmitAll(e);
//@ receive(): recibe evento
//——————————————————————
int Model::receive(Event *e,Graph myNetwork)
{
44
}
}
}
14.4.
Clase: Event
14.4.1.
Archivo event.h
// Archivo: event.h
// interfaz de la clase ’Event’
// R. Marcelin J. (2/08/99)
#if !defined( EVENT )
#define EVENT
class Event
{
public:
Event();
Event(char name, float time, int target, int source); //
˜Event(); //
char getName(); //
float getTime(); //
int getSource(); //
int getTarget(); //
protected:
char name; // nombre
float time; // estampilla de tiempo
int target; // destino
int source; // origen
};
#endif
14.4.2.
Archivo event.cc
// Archivo: event.cc
// implementacion de la clase ’Event’
// R. Marcelin J. (2/08/99)
#include “event.h”
//@ Event(): constructor
//——————————————————————
45
Event::Event()
{
}
//@ Event(): constructor
//——————————————————————
Event::Event(char name, float time, int target, int source)
{
name = name;
time = time;
target = target;
source = source;
}
//@ ˜Event(): destructor
//——————————————————————
Event::˜Event(void)
{
//@ getName():
//——————————————————————
char Event::getName(void)
{
return name;
}
}
//@ getTime()
//——————————————————————
float Event::getTime(void)
{
return time;
}
//@ getSource()
//——————————————————————
int Event::getSource(void)
{
return source;
}
//@ getTarget()
//——————————————————————
int Event::getTarget(void)
{
return target;
}
46
14.5.
Clase: Process
14.5.1.
Archivo process.h
// Archivo: process.h
// interfaz de la clase ’Process’
// R. Marcelin J. (02/08/99)
// esta es una version modificada que extiende la definicion de la clase
// MODELO, para permitir la concatenacion de automatas (29/10/01).
#if !defined( PROCESS )
#define PROCESS
#include “model.h”
#include “event.h”
#include “simulator.h”
#include “linklist.cc”
#include “graph.h”
#include “mpicomm.h”
//#include “intset.h”
class Process
{
public:
Process(LinkList<int> & tx, Simulator *engine, int id);
˜Process(); //
void setModel(Model *model);
void transmit(Event *e,Graph myNetwork);
//
void transmitAll(Event *e); //
int receive(Event *e,Graph myNetwork); //
void addTxNeighbor(int id); //
void addRxNeighbor(int id); //
int *getTxNeighbor(); // devuelve ap. al item, o nulo
int *getRxNeighbor(); // devuelve ap. al item, o nulo
friend class Model;
private:
int id; //
Model *myModel; //
Simulator *myEngine; //
LinkList<int> txSet; //
47
LinkList<int> rxSet; //
};
#endif
14.5.2.
Archivo process.cc
// Archivo: process.cc
// implementacion de la clase ’Process’
// R. Marcelin J. (02/08/99)
// esta es una version modificada que extiende la definicion de la clase
// MODELO, para permitir la concatenacion de automatas (29/10/01).
#include “process.h”
#include <iostream.h>
//@ Process(): constructor
//——————————————————————
Process::Process(LinkList<int> & tx, Simulator *eng, int id):
txSet( tx), myEngine(eng), id( id)
{
/*txSet = new LinkList<int>( tx);*/
}
//@ ˜Process(): destructor
//——————————————————————
Process::˜Process()
{
//@ setModel():
//——————————————————————
void Process::setModel(Model *model)
{
int pid;
Model **m1, *m2;
myModel = model;
myModel->myProcess = this;
myModel->me = id;
myModel->neighbors = &txSet;
myModel->init();
pid = 1;
while (m1=(myModel->partners.next())) {
48
}
m2=*m1;
m2->myProcess = this;
m2->me = id;
m2->neighbors = &txSet;
m2->init(pid++);
m2->partners.insert(model, 1);
};
}
//@ transmit():
//——————————————————————
void Process::transmit(Event *e, Graph myNetwork)
{
int target,targetrank;
target=e->getTarget();
targetrank=myNetwork.getRank(target);
if(targetrank==MPIComm::getRank()){
myEngine->insertEvent(e);
}
else{
MPIComm::SendMsg(e,targetrank);
}
}
//@ sendAll():
//——————————————————————
void Process::transmitAll(Event *e)
{
// int *to;
// Event *ecopy;
// while (to = getTxNeighbor())
// { ecopy = new Event(e->getName(), e->getTime()+1, *to,
e->getSource());
// myEngine->insertEvent(ecopy);
// };
}
//@ receive():
49
//——————————————————————
int Process::receive(Event *e,Graph myNetwork)
{
int fin;
Model **m1, *m2;
myModel->clock = e->getTime();
while (m1=(myModel->partners.next())) {
m2=*m1;
m2->clock = myModel->clock;
};
fin=myModel->receive(e,myNetwork);
return(fin);
}
//@ addTxNeighbor():
//——————————————————————
void Process::addTxNeighbor(int id)
{
txSet.insert( id);
//@ addRxNeighbor():
//——————————————————————
void Process::addRxNeighbor(int id)
{
rxSet.insert( id);
//@ getTxNeighbor():
//——————————————————————
int *Process::getTxNeighbor()
{
return txSet.next();
//@ getRxNeighbor():
//——————————————————————
int *Process::getRxNeighbor()
{
return rxSet.next();
14.6.
Clase: Simulator
14.6.1.
Archivo simulator.h
// Archivo: simulator.h
// interfaz de la clase ’Simulator’
50
}
}
}
}
// R. Marcelin J. (02/08/99)
#if !defined( SIMULATOR )
#define SIMULATOR
#include “event.h”
#include “avltree.cc”
class Simulator
{
public:
Simulator(float d); //
˜Simulator(); //
void insertEvent(Event *e); //
Event *dequeueEvent(); //
int isOn(); //
private:
float duration;
float clock; //
AVLTree<Event *> agenda; //
};
#endif
14.6.2.
Archivo simulator.cc
// Archivo: simulator.cc
// implementacion de la clase ’Simulator’
// R. Marcelin J. (02/08/98)
#include “simulator.h”
//@ Simulator(): constructor
//——————————————————————
Simulator::Simulator(float d)
{
clock = 0.0;
duration = d;
}
//@ ˜Simulator(): destructor
//——————————————————————
Simulator::˜Simulator()
51
{
//@ insertEvent():
//——————————————————————
void Simulator::insertEvent(Event *e)
{
float key = e->getTime();
agenda.insert(e, key);
}
}
//@ dequeueEvent():
//——————————————————————
Event *Simulator::dequeueEvent()
{
Event *e;
e = agenda.minval();
clock = e->getTime();
return e;
}
//@ isOn():
//——————————————————————
int Simulator::isOn()
{
return (!agenda.isEmpty())&&(clock <= duration);
14.7.
Clase: Graph
14.7.1.
Archivo graph.h
// Archivo: graph.h
// interface de la clase ’Graph’
// R. Marcelin J. (02/08/99)
#if !defined( GRAPH )
#define GRAPH
#include <fstream.h>
#include <iostream.h>
#include “mpicomm.h”
#include “linklist.cc”
const int NEWL = 10;
52
}
class Graph
{
public:
Graph(int rank);
// constructor
˜Graph();
// destructor
int getSize();
// devuelve num. vertices
LinkList<int> *getRow(int id); // devuelve un renglon de ’g’
int getRank(int pos); // devuelve el elemento de la posicion dada
private:
int size;
// taman˜o de la grafica
LinkList<LinkList<int> *> g; // ’matriz’ de adyacencias
LinkList<int> t; // tabla de asignacion de procesadores
};
#endif
14.7.2.
Archivo graph.cc
// Archivo: graph.cc
// implementacion de la clase ’Graph’
// R. Marcelin J. (02/08/99)
#include “graph.h”
//@ Graph(): constructor
//——————————————————————
Graph::Graph(int rank)
{
char c,fgraphname[8]=“.graph ”,ftablename[8]=“.table ”;
int temp;
int proc;
int vertice;
int weight;
LinkList<int> *v;
fgraphname[6]=ftablename[6]=rank+’0’;
ifstream ins(fgraphname);
ifstream ftable(ftablename);
if (!ins) {
cerr << “No pude abrir el archivo{}n”;
exit(1);
53
};
for (size =0; !ins.eof(); size++) {
v = new LinkList<int>();
while ((ins.get(c))&&(c!=NEWL)) {
ins.unget();
vertice = 0;
while ((ins.get(c))&&(c!=’ ’)&&(c!=’,’)){
temp = c - ’0’;
vertice=10*vertice+temp;
};
if (c==’,’){
weight = 0;
while ((ins.get(c))&&(c!=’ ’)&&(c!=’,’)&&(c!=NEWL)){
temp = c - ’0’;
weight=10*weight+temp;
};
ins.unget();
};
while ((ins.get(c))&&(c==’ ’));
ins.unget();
if (weight)
v->insert(vertice, weight);
else
v->insert(vertice);
};
g.insert(v, size + 1);
};
ins.close();
if (!ftable) {
cerr << “No pude abrir el archivo{}n”;
exit(1);
};
while(!ftable.eof()){
proc=0;
while(ftable.get(c) && c!=NEWL) {
if(c<’0’ —— c>’9’) {
while(ftable.get(c) && c!=NEWL);
ftable.unget();
}
54
else
proc=10*proc+(c-’0’);
}
t.inqueue(proc);
}
ftable.close();
}
//@ Graph(): destructor
//——————————————————————
Graph::˜Graph()
{
//@ getSize(): devuelve num. de vertices
//——————————————————————
int Graph::getSize()
{
return size;
}
}
//@ getRow(): devuelve la lista de vecinos del vertice ’id’
//——————————————————————
LinkList<int> *Graph::getRow(int id)
{
return *(g.find(id));
}
//@ getRank(): devuelve el elemento de la posicion dada
//——————————————————————
int Graph::getRank(int pos)
{
return(t.getNext(pos));
}
14.8.
Clase: MPIComm
14.8.1.
Archivo mpicomm.h
// Archivo: mpicomm.h
// interface de la clase ’MPIComm’
// R. Marcelin J. (02/08/99)
#if !defined( MPICOMM )
#define MPICOMM
#include <fstream.h>
#include <iostream.h>
55
#include “event.h”
#include “mpi++.h”
class MPIComm
{
public: //metodos:
MPIComm(int argc, char **argv);// constructor
MPIComm();
void FBcast(int argc, char **filenames);
static int getRank();
static int getSize();
static Event *FirstMsg();
static Event *RecvMsg();
static void SendMsg(Event *e, int target);
void end();
static int finished();
private: //variables:
static int rank;
static int size;
static int ended;
};
#endif
14.8.2.
Archivo mpicomm.cc
// Archivo: mpicomm.cc
// implementacion de la clase ’MPIComm’
// R. Marcelin J. (02/08/99)
#include “mpicomm.h”
int MPIComm::rank;
int MPIComm::size;
int MPIComm::ended;
//@ MPIComm(): constructor
//——————————————————————
MPIComm::MPIComm(int argc, char **argv)
{
MPI::Init(argc,argv);
rank = MPI::COMM WORLD.Get rank();
56
size = MPI::COMM WORLD.Get size();
ended = 0;
}
//@ FBcast(): hace un broadcast de los archivos
//—————————————————————–
void MPIComm::FBcast(int argc, char **filenames)
{
int graphlength,tablelength;
char graphoutname[8]=“.graph ”,tableoutname[8]=“.table ”,*graphbuf,*tablebuf;
ifstream fgraphin,ftablein;
ofstream fgraphout,ftableout;
if (rank == 0){
if (argc < 3){
cout << “Por favor indique el nombre del archivo con la
grafica y la tabla de asignacion de procesadores” << endl;
graphlength=-10;
}
else{
fgraphin.open(filenames[1], ios::binary );
ftablein.open(filenames[2], ios::binary );
if(!fgraphin.good() —— !ftablein.good()){
cout << “Nombre de archivo incorrecto” << endl;
graphlength=-10;
}
else{
fgraphin.seekg(0, ios::end);
graphlength = fgraphin.tellg();
fgraphin.seekg(0, ios::beg);
graphbuf = new char[graphlength];
fgraphin.read(graphbuf,graphlength);
fgraphin.close();
graphbuf[graphlength-1]=’{}0’;
ftablein.seekg(0,ios::end);
tablelength = ftablein.tellg();
ftablein.seekg(0,ios::beg);
tablebuf = new char[tablelength];
ftablein.read(tablebuf,tablelength);
57
ftablein.close();
tablebuf[tablelength-1]=’{}0’;
}
}
}
MPI::COMM WORLD.Barrier();
if (size!=0){
MPI::COMM WORLD.Bcast(&graphlength,1,MPI::INT,0);
if (graphlength==-10){
MPI::Finalize();
exit(1);
}
MPI::COMM WORLD.Bcast(&tablelength,1,MPI::INT,0);
if (rank!=0) {
graphbuf = new char[graphlength];
tablebuf = new char[tablelength];
}
MPI::COMM WORLD.Bcast(graphbuf,graphlength,MPI::CHAR,0);
MPI::COMM WORLD.Bcast(tablebuf,tablelength,MPI::CHAR,0);
MPI::COMM WORLD.Barrier();
}
graphoutname[6]=rank+’0’;
fgraphout.open(graphoutname,ios::binary);
fgraphout.write(graphbuf,graphlength);
fgraphout.close();
tableoutname[6]=rank+’0’;
ftableout.open(tableoutname,ios::binary);
ftableout.write(tablebuf,tablelength);
ftableout.close();
}
//@ ˜MPIComm: destructor
//———————————————–
MPIComm::˜MPIComm()
{
MPI::Finalize();
}
//@ FirstMsg(): espera al primer mensaje para este rank
Event *MPIComm::FirstMsg()
58
{
float msg[4];
int i;
MPI::COMM WORLD.Recv(msg,4,MPI::FLOAT,MPI::ANY SOURCE,MPI::ANY TA
Event *e1 = new Event((char)msg[0],msg[1],(int)msg[2],(int)msg[3]);
return(e1);
}
Event * MPIComm::RecvMsg()
{
float *msg;
msg = new float[4];
if(MPI::COMM WORLD.Iprobe(MPI::ANY SOURCE,MPI::ANY TAG)){
MPI::COMM WORLD.Recv(msg,4,MPI::FLOAT,MPI::ANY SOURCE,MPI::ANY
if(msg[0]==(float)’F’){
ended++;
return(NULL);
}
else{
Event *e1 = new Event((char)msg[0],msg[1],(int)msg[2],(int)msg[3]);
return(e1);
}
}
else return(NULL);
}
void MPIComm::SendMsg(Event *e, int target)
{
float msg[4];
msg[0]=(float)e->getName();
msg[1]=e->getTime();
msg[2]=(float)e->getTarget();
msg[3]=(float)e->getSource();
MPI::COMM WORLD.Send(msg,4,MPI::FLOAT,target,1);
}
//@ GetRank(): devuelve el identificador de procesador
//———————————————–
int MPIComm::getRank()
59
{
return(rank);
}
//@ GetSize(): devuelve el nÃo mero de procesadores
//———————————————–
int MPIComm::getSize()
{
return(size);
}
//@ end(): Envia un mensaje a rank cero indicano que el
// procesador ha terminado su simulacion
//————————————————
void MPIComm::end(){
float msg[4];
msg[0]=(float)’F’;
msg[1]=0;
msg[2]=0;
msg[3]=0;
MPI::COMM WORLD.Send(msg,4,MPI::FLOAT,0,1);
}
//@ finished: Indica cuantos procesadores han terminado
// su simulación
//————————————————–
int MPIComm::finished()
{
return (ended);
}
14.9.
//
//
//
//
Clase: LinkList / Archivo linklist.cc
Archivo: linklist.cc
implementacion de las clases parametrizadas ’LinkItem’ y ’LinkList’
R. Marcelin J. (21/06/99).
(25/10/01) agregue los metodos “insertmarked()”.
#if !defined( LINKLIST )
#define LINKLIST
template <class T> class LinkItem; // item de una lista ligada
template <class T> class LinkList; // lista ligada
//class IntSet;
template <class T> class LinkItem
60
{
public:
LinkItem(); // constructor por omision
LinkItem(T & info); // constructor de tipos basicos
LinkItem(T & info, float key); // constructor de clases compuestas
˜LinkItem(); // destructor
LinkItem<T> *getPos(); //
LinkItem<T> *getPre(); //
T &getInfo(); //
void setPos(LinkItem<T> * pos); //
void setPre(LinkItem<T> * pre); //
void setMark(); //
void resetMark(); //
int isMarked(); //
friend class LinkList<T>; // acceso a los atrib. privados
// friend class IntSet;
protected:
float key; // para establecer un orden
T info; // campo de informacion
int marked; //
LinkItem<T> *pos; // apunta al vecino posterior
LinkItem<T> *pre; // apunta al vecino previo
};
template <class T> class LinkList // eficiente para ˜cientos de items
{
public:
LinkList(); // constructor por omision
˜LinkList(); // destructor
int getLength(); // devuelve la long. de la lista
int isEmpty();
// verdadero si esta vacia
void insert(LinkItem<T> *t); // agrega en orden segun la llave
void insert(T & info, float key);//
void insert(T & info); //
void insertmarked(T & info, float key);//
void insertmarked(T & info); //
void remove(float key); // elimina al item que tiene la llave
61
void inqueue(LinkItem<T> *t); // agrega al final
void inqueue(T & info, float key); //
void inqueue(T & info); //
/* void insertnoorder(LinkItem<T> *t);
void insertnoorder(T & info); */
T dequeue(); // extrae al primero, usar c/isEmpty()
T *find(float key); // busca un item por su llave
T *next(); // devuelve el sig. item sin visitar
// o 0, si ya acabo.
T *next(float & key); // tambien devuelve su llave de orden
// como efecto lateral
T getNext(int pos); // devuelve el item que esta tantas posiciones adelante
protected:
LinkItem<T> *head; // Estos son los pivotes de la
LinkItem<T> *tail; // lista.
int length;
};
//@ LinkItem(): constructor por omision
//——————————————————————
template<class T> LinkItem<T>::LinkItem()
{
key = -1;
pos = 0;
pre = 0;
marked = 0;
// TRACE(lnk, -1, (“LinkItem constructor{}n”));
}
//@ LinkItem(): constructor para tipos basicos
//——————————————————————
template<class T> LinkItem<T>::LinkItem(T & info)
{
info = info;
key = info;
pos = 0;
62
pre = 0;
marked = 0;
// TRACE(lnk, -1, (“LinkItem constructor{}n”));
}
//@ LinkItem(): constructor para clases
//——————————————————————
template<class T> LinkItem<T>::LinkItem(T & info, float key): info( info)
{
key = key;
pos = 0;
pre = 0;
marked = 0;
// TRACE(lnk, -1, (“LinkItem constructor{}n”));
}
//@ ˜LinkItem(): destructor
//——————————————————————
template<class T> LinkItem<T>::˜LinkItem()
{
//TRACE(lnk, -1, (“LinkItem destructor{}n”));
}
//@ getPos():
//——————————————————————
template<class T> LinkItem<T> *LinkItem<T>::getPos()
{
return pos;
}
//@ getPre():
//——————————————————————
template<class T> LinkItem<T> *LinkItem<T>::getPre()
{
return pre;
}
//@ getInfo():
//——————————————————————
template<class T> T &LinkItem<T>::getInfo()
{
return info;
}
//@ setPos():
//——————————————————————
template<class T> void LinkItem<T>::setPos(LinkItem<T> * pos)
63
{
pos = pos;
}
//@ setPre():
//——————————————————————
template<class T> void LinkItem<T>::setPre(LinkItem<T> * pre)
{
pre = pre;
}
//@ setMark():
//——————————————————————
template<class T> void LinkItem<T>::setMark()
{
marked = 1;
}
//@ resetMark():
//——————————————————————
template<class T> void LinkItem<T>::resetMark()
{
marked = 0;
}
//@ isMarked():
//——————————————————————
template<class T> int LinkItem<T>::isMarked()
{
return marked;
//@ LinkList(): constructor por omision
//——————————————————————
template<class T> LinkList<T>::LinkList()
{
head = new LinkItem<T>();
tail = new LinkItem<T>();
head->pos = tail;
tail->pre = head;
length = 0;
// TRACE(lnk, -1, (“LinkList constructor{}n”));
}
//@ ˜LinkList(): destructor
//——————————————————————
template<class T> LinkList<T>::˜LinkList()
{
// TRACE(lnk, -1, (“LinkList destructor{}n”));
//@ getLength():
64
}
}
//——————————————————————
template<class T> int LinkList<T>::getLength()
{
return length;
//@ getNext():
//——————————————————————
template<class T> T LinkList<T>::getNext(int posit)
{
LinkItem<T> *aux;
int i;
aux=head->pos;
for(i=1;i<posit;i++){
if(aux==tail){
return(-1);
}
aux=aux->pos;
}
return(aux->info);
}
//@ isEmpty():
//——————————————————————
template<class T> int LinkList<T>::isEmpty()
{
return (head->pos == tail);
}
}
// @ insert(): agrega en orden segun la llave ’t.key’
//——————————————————————
template<class T> void LinkList<T>::insert(LinkItem<T> *t)
{
LinkItem<T> *h1, *h2;
h2 = head;
h1 = h2->pos;
while ((h1!=tail)&&(t->key >= h1->key)){
h2 = h1; h1 = h1->pos;
};
h2->pos = t; t->pre = h2; h1->pre = t; t->pos = h1;
length++;
}
// @ insert();
65
//——————————————————————
template<class T> void LinkList<T>::insert(T & info, float key)
{
LinkItem<T> *h1, *h2, *t;
t = new LinkItem<T>( info, key);
insert(t);
}
//@ insert():
//——————————————————————
template<class T> void LinkList<T>::insert(T & info)
{
LinkItem<T> *h1, *h2, *t;
t = new LinkItem<T>( info);
insert(t);
}
// @ insertmarked():
//——————————————————————
template<class T> void LinkList<T>::insertmarked(T & info, float key)
{
LinkItem<T> *h1, *h2, *t;
t = new LinkItem<T>( info, key);
t->setMark();
insert(t);
}
//@ insertmarked():
//——————————————————————
template<class T> void LinkList<T>::insertmarked(T & info)
{
LinkItem<T> *h1, *h2, *t;
t = new LinkItem<T>( info);
t->setMark();
insert(t);
}
//@ remove(): elimina al item con la llave indicada
//——————————————————————
template<class T> void LinkList<T>::remove(float key)
66
{
LinkItem<T> *h1, *h2, *h3;
h2 = head;
h1 = h2->pos;
while ((h1!=tail)&&(h1->key!= key)) {
h2 = h1; h1 = h1->pos;
};
if (h1!=tail) {
h3 = h1;
h1 = h1->pos;
h2->pos = h1;
h1->pre = h2;
delete h3;
length–;
};
}
//@ inqueue(): agrega al final
//——————————————————————
template<class T> void LinkList<T>::inqueue(LinkItem<T> *t)
{
LinkItem<T> *h1, *h2;
h1 = tail;
h2 = h1->pre;
h2->pos = t; t->pre = h2; h1->pre = t; t->pos = h1;
length++;
}
//@ inqueue():
//——————————————————————
template<class T> void LinkList<T>::inqueue(T & info, float key)
{
LinkItem<T> *t;
t = new LinkItem<T>( info, key);
inqueue(t);
}
//@ inqueue():
67
//——————————————————————
template<class T> void LinkList<T>::inqueue(T & info)
{
LinkItem<T> *t;
t = new LinkItem<T>( info);
inqueue(t);
}
//@ dequeue():
//——————————————————————
template<class T> T LinkList<T>::dequeue()
{
LinkItem<T> *h1, *h2;
T temp;
h1 = head->pos; h2 = h1->pos;
head->pos = h2; h2->pre = head;
h1->pos = h1->pre = 0;
temp = h1->info;
delete h1;
length–;
return temp;
}
//@ find(): devuelve al item que contiene la llave, 0 si falla
//——————————————————————
template<class T> T *LinkList<T>::find(float key)
{
LinkItem<T> *h1, *h2;
h2 = head;
h1 = h2->pos;
while ((h1!=tail)&&(h1->key!= key)) {
h2 = h1; h1 = h1->pos;
};
if (h1!=tail)
return &(h1->info);
else
return 0;
}
68
//@ next(): devuelve el siguiente item sin visitar, 0 si acabo
//——————————————————————
template<class T> T *LinkList<T>::next()
{
LinkItem<T> *h1, *h2;
T temp;
h2 = head;
h1 = h2->pos;
while ((h1!=tail)&&(h1->isMarked())) {
h2 = h1; h1 = h1->pos;
};
if (h1!=tail){
h1->setMark();
return &(h1->info);
}
else {
h2 = head;
h1 = h2->pos;
while (h1!=tail){
h1->resetMark();
h2 = h1; h1 = h1->pos;
};
return 0;
};
}
//@ next(): tambien devuelve su llave de orden, como efecto lateral.
//——————————————————————
template<class T> T *LinkList<T>::next(float & key)
{
LinkItem<T> *h1, *h2;
T temp;
h2 = head;
h1 = h2->pos;
while ((h1!=tail)&&(h1->isMarked())){
h2 = h1; h1 = h1->pos;
};
if (h1!=tail){
69
h1->setMark();
key = h1->key;
return &(h1->info);
}
else {
h2 = head;
h1 = h2->pos;
while (h1!=tail){
h1->resetMark();
h2 = h1; h1 = h1->pos;
};
return 0;
};
}
#endif
14.10.
Clase: AVLTree / Archivo: avltree.cc
// Archivo: avltree.cc
// implementacion de las clases parametrizadas ’AVLItem’ y ’AVLTree’
// R. Marcelin J. (21/06/99)
#if !defined ( AVLTREE )
#define AVLTREE
//#include “trace.h” // para rastrear operaciones
//DEFINE TRACE MODULE(avl, “avl”);
template <class T> class AVLItem; // item de un arbol balanceado
template <class T> class AVLTree; // Adelson, Velski y Landis
template <class T> class AVLItem
{
public:
AVLItem(); // constructor
AVLItem(T & info); // constructor
AVLItem(T & info, float key); // constructor
˜AVLItem(); // destructor
private:
70
float key; // para establecer un orden
T info; // campo de informacion
int bal; // altura izq - altura der : -1,0,1
AVLItem *pre; // vecino izquierdo
AVLItem *pos; // vecino derecho
friend class AVLTree<T>; // acceso a los atrib. privados
};
// se usa en AVLTree
enum search {
FAIL = 0 ,
// no lo encontro
EUREKA = 1 , // lo encontro en un hijo
LFOUND = 2 , // es descendiente de un hijo izq.
RFOUND = 3 }; // es descendiente de un hijo
der.
template <class T> class AVLTree // eficiente para ˜miles de items
{
public:
// constructor
AVLTree();
// Destructor
˜AVLTree();
// total de items insertados
int getSize();
// verdadero si el arbol esta vacio
int isEmpty();
// descenso recursivo en preorden, desde la raiz
void visit(AVLItem<T> *t, int nivel);
void visit();
// balancea arbol podado por la izquierda
AVLItem<T> *lbalance(AVLItem<T> *t);
// balancea arbol podado por la derecha
AVLItem<T> *rbalance(AVLItem<T> *t);
// incrementa el arbol y verifica su balance
void insert(T & info);
void insert(T & info, float key);
void insert(AVLItem<T> *n);
71
AVLItem<T> *insert(AVLItem<T> *n, AVLItem<T> *t);
// devuelve el item con la menor clave de todo el arbol
// usar con isEmpty()
T minval();
T minval(AVLItem<T> *t, enum search &result);
// busca al item con la mayor clave de un subarbol
AVLItem<T> *maxval(AVLItem<T> *t, T & info, enum search
&result);
// elimina un item particular, segun su llave
int remove(float key);
enum search remove(float key, AVLItem<T> *t);
// devuelve ap. a item que busca por su llave, 0 en otro caso
T *find(float key);
T *find(float key, AVLItem<T> *t);
private:
AVLItem<T> *root; // ra¡z
int size; // taman˜o del arbol
int balance; // 1 si debe verificarse el balance
};
//@ AVLItem(): constructor por omision
//——————————————————————
template<class T> AVLItem<T>::AVLItem()
{
key = -1;
pos = 0;
pre = 0;
bal = 0;
// TRACE(avl, -1, (“AVLItem constructor{}n”));
}
//@ AVLItem(): constructor para tipos basicos
//——————————————————————
template<class T> AVLItem<T>::AVLItem(T & info)
{
info = info;
key = info;
72
pos = 0;
pre = 0;
bal = 0;
// TRACE(avl, -1, (“AVLItem constructor{}n”));
}
//@ AVLItem(): constructor para clases
// es mejor inicializar por fuera un miembro compuesto
//——————————————————————
template<class T> AVLItem<T>::AVLItem(T & info, float key) : info( info)
{
key = key;
pos = 0;
pre = 0;
bal = 0;
// TRACE(avl, -1, (“AVLItem constructor{}n”));
}
//@ ˜AVLItem(): destructor
// —————————————————————–
template <class T> AVLItem<T>::˜AVLItem()
{
// TRACE(avl, -1, (“AVLItem destructor{}n”));
// AVLTree(): Constructor
// —————————————————————–
template<class T> AVLTree<T>::AVLTree()
{
root = 0;
size = 0;
balance = 0;
// TRACE(avl, -1, (“AVLTree constructor{}n”));
}
// AVLTree(): Destructor
// —————————————————————–
template<class T> AVLTree<T>::˜AVLTree()
{
// TRACE(avl, -1, (“AVLTree destructor{}n”));
73
}
}
// getSize(): total de items insertados
// —————————————————————–
template<class T> int AVLTree<T>::getSize()
{
return size;
// isEmpty(): verdadero si el arbol esta vacio
// —————————————————————–
template<class T> int AVLTree<T>::isEmpty()
{
return (root == 0);
// visit(): comienza por la ra¡z
// —————————————————————–
template<class T> void AVLTree<T>::visit()
{
visit(root, 0);
}
}
}
// visit(): descenso rec. en preorden, imprime contenido y profundidad
// —————————————————————–
template<class T> void AVLTree<T>::visit(AVLItem<T> *t, int nivel)
{
if (t != 0) {
visit(t->pre, nivel+1);
cout << t->key << “:” << nivel << “:” << t->bal << “{}n”;
visit(t->pos, nivel+1);
};
}
// lbalance(): balancea un arbol podado por la izquierda
// —————————————————————–
template<class T> AVLItem<T> *AVLTree<T>::lbalance(AVLItem<T>
*t)
{
AVLItem<T> *t1,*t2, *t3;
int b2, b3;
t1 = t;
switch (t1->bal){
case -1:
t1->bal = 0;
break;
case 0:
t1->bal = 1; balance = 0;
74
break;
case 1:
t2 = t1->pos;
b2 = t2->bal;
if (b2 >= 0) // rotación simple RR
{
t1->pos = t2->pre;
t2->pre = t1;
if (b2 == 0){
t1->bal = 1;
t2->bal = -1;
balance = 0;
}
else {
t1->bal = 0;
t2->bal = 0;
};
t1 = t2;
}
else // rotación doble RL
{
t3 = t2->pre;
b3 = t3->bal;
t2->pre = t3->pos;
t3->pos = t2;
t1->pos = t3->pre;
t3->pre = t1;
t1->bal = (b3 == 1)? -1: 0;
t2->bal = (b3 == -1)? 1: 0;
t3->bal = 0;
t1 = t3;
};
};
return t1;
};
// rbalance(): balancea un arbol podado por la derecha
// —————————————————————–
75
template<class T> AVLItem<T> *AVLTree<T>::rbalance(AVLItem<T>
*t)
{
AVLItem<T> *t1,*t2, *t3;
int b2, b3;
t1 = t;
switch (t1->bal) {
case 1:
t1->bal = 0;
break;
case 0:
t1->bal = -1;
balance = 0;
break;
case -1:
t2 = t1->pre;
b2 = t2->bal;
if (b2 <= 0) // rotación simple LL
{
t1->pre = t2->pos;
t2->pos = t1;
if (b2 == 0)
{
t1->bal = -1;
t2->bal = 1;
balance = 0;
}
else{
t1->bal = 0;
t2->bal = 0;
};
t1 = t2;
}
else // rotación doble LR
{
t3 = t2->pos;
b3 = t3->bal;
t2->pos = t3->pre;
76
t3->pre = t2;
t1->pre = t3->pos;
t3->pos = t1;
t1->bal = (b3 == -1)? 1: 0;
t2->bal = (b3 == 1)? -1: 0;
t3->bal = 0;
t1 = t3;
};
};
return t1;
};
// insert(): comienza exploracion por la ra¡z
// —————————————————————–
template<class T> void AVLTree<T>::insert(T & info)
{
AVLItem<T> *n;
n = new AVLItem<T>( info);
root = insert(n, root);
}
// insert(): comienza exploracion por la ra¡z
// —————————————————————–
template<class T> void AVLTree<T>::insert(T & info, float key)
{
AVLItem<T> *n;
n = new AVLItem<T>( info, key);
root = insert(n, root);
}
// insert(): comienza exploracion por la ra¡z
// —————————————————————–
template<class T> void AVLTree<T>::insert(AVLItem<T> *n)
{
root = insert(n, root);
}
// insert(): devuelve ap. al subarbol incrementado y rebalanceado.
// —————————————————————–
template<class T> AVLItem<T> *AVLTree<T>::insert(AVLItem<T>
*n, AVLItem<T> *t)
{
AVLItem<T> *t1, *t2, *t3;
77
t1 = t;
if (t1 == 0) {
t1 = n; balance = 1; size++; // primera vez
}
else if (n->key < t1->key) // sin el =
{
t1->pre = insert(n, t1->pre); // desciende por la izquierda
if (balance) {
switch(t1->bal){
case 1:
t1->bal = 0;
balance = 0;
break;
case 0:
t1->bal = -1;
break;
case -1:
t2 = t1->pre;
if (t2->bal == -1) // rot. simple LL
{
t1->pre = t2->pos; t2->pos = t1;
t1->bal = 0;
t1 = t2;
}
else{
t3 = t2->pos; // rot. doble LR
t2->pos = t3->pre; t3->pre = t2;
t1->pre = t3->pos; t3->pos = t1;
t1->bal = (t3->bal == -1)? 1: 0;
t2->bal = (t3->bal == 1)? -1: 0;
t1 = t3;
};
t1->bal = 0;
balance = 0;
};
};
}
else {
78
t1->pos = insert(n, t1->pos); // desciende por la derecha
if (balance){
switch(t1->bal) {
case -1:
t1->bal = 0;
balance = 0;
break;
case 0:
t1->bal = 1;
break;
case 1:
t2 = t1->pos;
if (t2->bal == 1) // rot. simple RR
{
t1->pos = t2->pre;
t2->pre = t1;
t1->bal = 0;
t1 = t2;
}
else {
t3 = t2->pre; // rot. doble RL
t2->pre = t3->pos;
t3->pos = t2;
t1->pos = t3->pre;
t3->pre = t1;
t1->bal = (t3->bal == 1)? -1: 0;
t2->bal = (t3->bal == -1)? 1: 0;
t1 = t3;
};
t1->bal = 0;
balance = 0;
};
};
};
return t1;
};
// minval(): devuelve el item con la menor llave de todo el arbol
79
// —————————————————————–
template<class T> T AVLTree<T>::minval()
{
enum search result = FAIL;
T info;
AVLItem<T> *t;
if (root->pre == 0) {
t = root;
info = root->info;
root = root->pos;
result = EUREKA;
delete t;
}
else
info = minval(root, result);
if (result != FAIL) size–;
return info;
};
// minval(): descenso rec. por el lado izquierdo
// —————————————————————–
template<class T> T AVLTree<T>::minval(AVLItem<T> *t, enum search
&result)
{
AVLItem<T> *t1, *t2;
T info;
t1 = t;
if (t1->pre == 0) {
info = t->info;
result = EUREKA;
}
else{
info = minval(t1->pre, result);
if (result == LFOUND) {
if (balance) t1->pre = lbalance(t1->pre);
}
else if (result == EUREKA) {
t2 = t1->pre; // procede a podar a su hijo menor
80
t1->pre = t2->pos; // y lo borra
result = LFOUND;
delete t2;
balance = 1;
};
};
return info;
};
// maxval(): busca al item con la mayor clave de un subarbol
// —————————————————————–
template<class T> AVLItem<T> *AVLTree<T>::maxval(AVLItem<T>
*t, T & info, enum search &result)
{
AVLItem<T> *t1, *t2;
t1 = t;
if (t1->pos == 0) {
info = t1->info; // procede a podar a su hijo mayor
t2 = t1;
t1 = t1->pre;
result = EUREKA;
delete t2;
balance = 1;
}
else{
t1->pos = maxval(t1->pos, info, result);
if (balance) t1 = rbalance(t1);
result = RFOUND;
};
return t1;
};
//@ remove(): inicia la busqueda por la raiz
//——————————————————————
template<class T> int AVLTree<T>::remove(float key)
{
enum search result = FAIL;
T info;
AVLItem<T> *d;
result = remove( key, root);
81
if (result == EUREKA){
d = root;
if (root->pos == 0) {
root = root->pre;
delete d;
}
else if (root->pre == 0) {
root = root->pos;
delete d;
}
else {
root->pre = maxval(root->pre, info, result);
root->setInfo( info);
if (balance) root = lbalance(root);
};
}
else if (result == LFOUND) {
if (balance) root = lbalance(root);
}
else if (result == RFOUND) {
if (balance) root = rbalance(root);
};
if (result != FAIL) size–;
return result;
};
//@ remove(): elimina a un item segun su llave y verifica el balance
//——————————————————————
template<class T> enum search AVLTree<T>::remove(float key, AVLItem<T>
*t)
{
enum search result;
AVLItem<T> *t1, *t2;
T info;
t1 = t;
if (t1 == 0){
result = FAIL; // no existe
}
82
else if ( key == t1->key) {
result = EUREKA; // “-lo encontre!”
}
else if ( key < t1->key) {
result = remove( key, t1->pre); // desciende por la izq.
if (result == RFOUND){
if (balance) t1->pre = rbalance(t1->pre);
}
else if (result == LFOUND){
if (balance) t1->pre = lbalance(t1->pre);
}
else if (result == EUREKA) {
t2 = t1->pre;
if (t2->pos == 0) {
t1->pre = t2->pre;
delete t2;
balance = 1;
}
else if (t2->pre == 0) {
t1->pre = t2->pos;
delete t2;
balance = 1;
}
else // el mayor de sus menores
{
t2->pre=maxval(t2->pre, info, result);
t2->setInfo( info);
if (balance) t1->pre = lbalance(t1->pre);
};
};
result = LFOUND;
}
else{
result = remove( key, t1->pos); // desciende por la der.
if (result == RFOUND){
if (balance) t1->pos = rbalance(t1->pos);
}
else if (result == LFOUND) {
83
if (balance) t1->pos = lbalance(t1->pos);
}
else if (result == EUREKA) {
t2 = t1->pos;
if (t2->pos == 0) {
t1->pos = t2->pre;
delete t2;
balance = 1;
}
else if (t2->pre == 0){
t1->pos = t2->pos;
delete t2;
balance = 1;
}
else // el mayor de sus menores
{
t2->pre=maxval(t2->pre, info, result);
t2->setInfo( info);
if (balance) t1->pos = lbalance(t1->pos);
};
};
result = RFOUND;
};
return result;
};
// find():
// —————————————————————–
template<class T> T *AVLTree<T>::find(float key)
{
return find( key, root);
}
// find(): devuelve ap. a item que busca por su llave, 0 en otro caso
// —————————————————————–
template<class T> T *AVLTree<T>::find(float key, AVLItem<T> *t)
{
T *info;
if (t == 0)
info = 0; // no lo encontro
else if ( key == t->key)
84
info = &(t->info); // aqui esta
else if ( key < t->key)
info = find( key, t->pre); // desciende por la izquierda
else
info = find( key, t->pos); // desciende por la derecha
return info;
};
#endif
15.
Bibliografı́a
Robbins, Kay A & Robbins, Steven. “UNIX, Programación Prática”.
Ed. Prentice Hall Hispanoamericana, México, 1997.
Liberty Jesse, “C++ para principiantes”. Ed. Pearson Education, México, 1999.
http://www.mpi-forum.org
MPI: http://www.millennium.berkeley.edu/docs/mpi/
PVM: http://www.csm.ornl.gov/pvm/
MPICH: http://www.mcs.anl.gov/mpi/mpich/
LAM/MPI: http://www.mpi.nd.edu/lam/
JPVM (pvm para java): http://www.isye.gatech.edu/chmsr/jPVM/
MPICH para Windows NT:
http://www.jics.utk.edu/mpint/WINMPICH.txt
Parallel computing (517 of the best sites selected by humans):
http://www.cbel.com/Parallel computing/?order=alpha
Ricardo Marcelin, Sergio Rajsbaum. Curso de principios de computación
distribuida:
http://www.cs.auckland.ac.nz/ ute/220ft/graphalg/node10.html
85
Algoritmo DFS:
http://ww5.java3.datastructures.net:8081/handouts/DFS.pdf
http://infosun.fmi.uni-passau.de/GTL/manual/a00103.html
86
Descargar