Está en la página 1de 55

El Algoritmo de Floyd

Capítulo 6
Grafos con Pesos
• Un grafo dirigido en la cual hay asociado
con cada arista un número positivo (el
“peso”) se llama un grafo dirigido con
pesos.
• El largo de una trayectoria de un vértice u
a otro vértice v es la suma de los pesos de
las aristas que componen la trayectoria.
El Problema de Todos Pares
Distancias mas Cortas
• Dado un grafo dirigido con pesos, ¿cuales
son las trayectorias de largos mínimos (es
decir “distancias mas cortas”) entre todos
los pares de vértices?
La Matríz de Adyacencias

• Representar un grafo dirigido con pesos G con n


vértices por una matríz MG como sigue:
• Si 1,2, … ,n son los vértices, entonces
el elemento en la fila #i y la columna #j es
0 si i=j, es ∞ (un número mas grande que
cualquier peso) si no hay arista de I a j, y es
el peso de la arista de i a j si tal arista existe.
Lamemos MG la matríz de adyacencias.
Ejemplo
4 A B C D E
A
A 0 6 3 ∞ ∞
B
3 6 B 4 0 ∞ 1 ∞
1 3 C ∞ ∞ 0 5 1
C 5
1 D ∞ 3 ∞ 0 ∞
D
E ∞ ∞ ∞ 2 0
E 2

Matríz de Adyacencias
El Algoritmo de Floyd
for k  0 to n-1
for i  0 to n-1
for j  0 to n-1
a[i,j]  min (a[i,j], a[i,k] + a[k,j])
endfor
endfor
endfor
La Solución al Ejemplo Anterior
4 A B C D E
A
A 0 6 3 6 4
B
3 6 B 4 0 7 1 8
1 3 C 10 6 0 3 1
C 5
1 D 7 3 10 0 11
D
E 9 5 12 2 0
E 2

Matríz de Distancias
La Idea del Algoritmo
La trayectoria mas corta de i a k
que pasa por 0, 1, …, k-1
i

Computed k
in previous
La trayectoria mas corta iterations
de i a j que pasa por
0, 1, …, k-1 La trayectoria mas corta
de k a j que pasa por
j 0, 1, …, k-1
El Diseño del Algoritmo Paralelo
• Particionar
• Patronos de Comunicaciones
• Aglomeración y Asignación
Particionar
• En el seudo código la misma asignación
se ejecuta n3 veces.
• No hay paralelismo funcional.
• Usemos descomposición de dominio:
particionar la matriz A en sus n2
elementos.
Comunicaciones

Poner al dia
Tareas a[3,4]
priimitivas Cuando k=1

Iteración k:
Iteración k:
Toda tarea
Toda tarea
en la fila k
en la columna
emite su valor
k emite su valor
a los procesos
a los procesos
en la misma
en la misma
columna
fila
Aglomeración y Asignación
• El número de tareas es estatico
• Las comunicaciones entre las tareas son
regulares
• El tiempo de computación por tarea es
constante
• Un buena estrategia en este caso es
– Aglomerar tareas para minimizar las
comunicaciones
– Crear una tarea por proceso MPI
Dos Descomposiciones
Bloques de Filas Bloques de Columnas
“Rowwise block striped” “Columnwise block striped”
Comparación de
Descomposiciones
• Bloques de Columnas
– Se eliminan las emisiones dentro de las
columnas
• Bloques de Filas
– Se eliminan las emisiones dentro de las
columnas
– Escribir la salida es mas simple
• Escoger descomposión en bloque de filas
Unos Preliminares sobre
Descomposición en Bloques
• Dividir una sucesión de datos en p
bloques de aproximadamente el mismo
tamaño de tal forma que se puede
computar “facilmente” lo siguiente:
• Dado el rango #i de un proceso,
determinar los datos que el proceso #i
maneja
• Dado i-ėsimo dato, determinar el proceso
que lo maneja.
Una Descomposición de Bloque de
datos numerados 0,1,…,n-1
• El primer dato manejado por el proceso i
es i n/p y el último es (i + 1) n/p -1

• El proceso que maneja el j-esimo dato es


(p (j+1)-1)/ n
Ejemplo
Supongamos que los datos son a0,a1,…,a212 y que
p=8. Entonces
• Proceso 0 maneja a a0, … , a25
• Proceso 1 maneja a a26, … , a52
• Proceso 2 maneja a a53, … , a78
• Proceso 3 maneja a a79, … , a105
• Proceso 4 maneja a a106, … , a132
• Proceso 5 maneja a a133, … , a158
• Proceso 6 maneja a a159, … , a185
• Proceso 7 maneja a a186, … , a212
Comparación de Tamaños de
Bloque para los dos Métodos
Tamaño de
Proceso Bloque

p=8
0 26
1 27 n=213
P
=
2 26
3 27
4 27
5 26
6 27
7 27
Macros
• La directiva #define define un identificador
y una sucesión de caracteres que se
sustituirá por el identificador cada vez que
se encuentra en el programa.
• Macros pueden tener argumentos, como
funciones.
• Para definir un macro que usa
argumentos, insertar parametros entre
parenteses en la definición.
Macros para la Descomposición en
Bloques con el Método 2
#define BLOCK_LOW(id,p,n) ((id)*(n)/(p))
//El indice menor controlado por el proceso id

#define BLOCK_HIGH(id,p,n) (BLOCK_LOW((id)+1,p,n)-1)


//El indice mayor controlado por el proceso id

#define BLOCK_SIZE(id,p,n) (BLOCK_LOW((id)+1)-BLOCK_LOW(id))


//La cantidad de elementos controlados por el proceso id

• #define BLOCK_OWNER(index,p,n) (((p)*((index)+1)-1)/(n))


//El rango del proceso que controla el indice index
La Necesidad de Parenteses en
Macros con Argumentos
• Son necesarias para asegurar la
sustitución correcta en todos los casos.
• Ejemplo:
#define ABS(a) (a) < 0 ? –(a) : (a)

Sin parentesis, el compilador convertiria


ABS(10-20) a 10-20 < 0 ? -10-20 : 10-20
cuyo valor sería -30 en vez de 10.
La Entrada de la Matríz de
Adyacencias de un Archivo

Archivo
La Entrada de la Matríz de
Adyacencias
• La matríz se guarda en el orden de filas (“row
major order” en un archivo”.
• Si hay p procesos, entonces para cada
i=0,1,…,p-2, el proceso p-1 lee fila in/p hasta
la fila (i+1)n/p -1 y las envia al proceso i.
Después, lee las últimas filas para el mismo.
• La razón por la cual p-1 hace este trabajo es
que no hay ningun proceso que va a ser
responsible por mas filas que el p-1 (Ejercicio
6.1)
Comunicación Punto-Punto
• Envuelve dos procesos
• Un proceso envia un mensaje
• El otro proceso receibe el mensaje
Enviar
int MPI_Send (
void *mensage,
int cantidad,
MPI_Datatype tipo,
int dest,
int etiqueta,
MPI_Comm comunicador
)
Recibir
int MPI_Recv (
void *mensage,
int cantidad,
MPI_Datatype tipo,
int fuente,
int etiqueta,
MPI_Comm comunicador,
MPI_Status *estatus//un apuntador a un
//record de tipo MPI_Status
)
El Código para Eniviar/Recibir
if (ID == j) {

Recibir de i

}

if (ID == i) {

Enviar a j

}
Volver de MPI_Send
• La función bloquea hasta que el buffer
este libre
• Tipicamente el mensaje se envia a un
buffer de mensaje que permite la
devolución de control al proceso que llama
Volver de MPI_Recv
• La función bloquea hasta que el mensaje
se haya recibido o hasta que un error se
haya detectado
• Cuando occure un error, el record
contiene información acerca del proceso
que envió el mensaje, el valor de la
etiqueta, y la condición de error.
Punto Muerto (“Deadlock”)
• Ocurre cuando un proceso espera una
condición que nunca occura.
• Es facil escribir código de enviar/recibir
que llegue a un punto muerto
– Dos procesos reciben antes de enviar.
– La etiqueta de enviar no es la misma como la
etiqueta de recibir.
– Un proceso envia un mensaje a una
destinación incorrecta.
Ejemplo
Float a,b,c;
Int id;
MPI_Status estatus;

If(id==0)
MPI_Recv(&b,1,MPI_FLOAT,1,0,MPI_COMM_WORLD,&estatus);
MPI_Send (&a,1,MPI_FLOAT,1,0,MPI_COMM_WORLD);
C=(a+b)/2.0;
else if (id==1){
MPI_Recv(&a,1,MPI_FLOAT,0,0,MPI_COMM_WORLD,&estatus);
MPI_Send (&b,1,MPI_FLOAT,0,0,MPI_COMM_WORLD);
C=(a+b)/2.0
La Función MPI_Bcast
• int MPI_Bcast (
void *buffer, /* Direccion del primer
elemento */
int count, /* número de elementos para
emitir */
• MPI_Datatype datatype, /* Tipo de
elementos */
• int root, /* ID de proceso que emite */
• MPI_Comm comm) /* Communicador */
Ejemplo de MPI_Bcast
MPI_Bcast(&k,1,MPI_INT,0,MPI_COMM_WORLD)
asigna el valor de la variable entero k del
proceso 0 a la variable k de todos los
procesos en MPI_COMM_WORLD.
Arreglos Dinámicos
Unidimensionales/
main()
{
Int i, n=10;
int *A;
A=(int *) malloc (n* sizeof(int));
//Ahora, A se puede usar como un arreglo ordinario.
// Por ejemplo,
for (i=0;i<n;i++)
{
A[i]=2*i;
printf("%d\n",A[i]);
}
}
Arreglos Dinámicos
Bidimensionales
main()
{
int i,j,m=4,n=3;
int **B, *Bstorage;
Bstorage=(int *) malloc (m*n*sizeof(int));//asignar memoria para arreglo mXn
B= (int **) malloc (m*sizeof(int *)); //asignar memoria para el arreglo
//de apuntadores a las filas
for (i=0;i<m;i++)
B[i]=&Bstorage[i*n]; //Inicializar el arrego de apuntadores a las filas

//Ahora podemos tratar como un m Xn arreglo ordinario.


//Por ejemplo,

for (i=0;i<m;i++)
{
for (j=0;j<n;j++)
{
B[i][j]=i*j;
printf("%d ",B[i][j]);
}
printf("\n");
}
}
main
• /*Leer archivo que contiene la matríz de
distancias*/
• /*Imprimir la matríz de distancias*/
• /*Llamar la función
compute_shortest_paths*/
• /*Computar tiempo total*/
• /*Imprimir la nueva matríz de distancias
Algoritmo de Floyd
Declaraciones y Inicializaciones
#include <stdio.h>
#include <mpi.h>
#include "../MyMPI.h"
typedef int dtype;
#define MPI_TYPE MPI_INT
int main (int argc, char *argv[])
{ dtype **a; /* Doubly-subscripted array */
dtype *storage; /* Local portion of array elements */
int i, j, k;
int id; /* Process rank */
int m; /* Rows in matrix */
int n; /* Columns in matrix */
int p; /* Number of processes */
double time, max_time;
void compute_shortest_paths (int, int, int**, int);
MPI_Init (&argc, &argv);
MPI_Comm_rank (MPI_COMM_WORLD, &id);
MPI_Comm_size (MPI_COMM_WORLD, &p);
Algoritmo de Floyd (2)
read_row_striped_matrix (argv[1],(void **) a,MPI_type,m,n,MPI_COMM_WORLD);

If (m != n) terminate (id,”Matrix must be square\n”);

print_row_striped_matrix((void **) a, MPI_TYPE, m,n,MPI_COMM_WORLD);

compute_shortest_paths(id,p, (dtype **) a,n);

print_row_striped_matrix((void **) a, MPI_TYPE, m,n,MPI_COMM_WORLD);

MPI_Finalize();

}
compute_shortest_paths
• void compute_shortest_paths (int id, int p, dtype **a, int n)
• {
• int i, j, k;
• int offset; /* Local index of broadcast row */
• int root; /* Process controlling row to be bcast */
• int *tmp; /* Holds the broadcast row */
• tmp = (dtype *) malloc (n * sizeof(dtype));
• for (k = 0; k < n; k++)
• { root = BLOCK_OWNER(k, p, n);
• if (root == id)
• { offset = k - BLOCK_LOW(id, p, n);
• for (j = 0; j < n; j++)
• tmp[j] = a[offset][j];
• }
• MPI_Bcast (tmp, n, MPI_TYPE, root, MPI_COMM_WORLD);
• for (i = 0; i < BLOCK_SIZE(id, p, n); i++)
• for (j = 0; j < n; j++)
• a[i][j] = MIN(a[i][j], a[i][k] + tmp[j]);
• }
• free (tmp);
• }
Complexidad Computacional
• El bucle exterior se ejecuta n veces
• El bucle en el medio se ejecuta a lo sumo
most n/p veces
• La complexidad es (n3/p)
Complexidad de Comunicaciones
• El bucle exterior contiene un Broadcast, lo
que contribuye una complexidad de
(nlog p) para cada una de las n
iteraciones. La complexidad de tiempo
total para comunicaciones es (n2log p)
• La complexidad de tiempo total es
(n2log p +n3/p)
La Entrada al Programa Paralela
de Floyd
• La entrada (y también la salida) es una
matríz de distancias.
• La función
read_row_striped_matrix
lee esta matríz de un archivo y
distribuye las filas entre los procesos
en un arreglo dinámico bidimensional
• La entrada tiene que ser de la forma de
un arreglo dinámico bidimensional
Un Program para Generar una
Matríz de Distancias Aleatorias
• Este programa es una variación de un
programa, genMat4floyd, escrito por
Andrea di Blas
• Hay 2 entradas en la linea de comando:
n (el número de vértices)
salida (el nombre del archivo de salida)
Un Programa para Crear un
Archivo con una Matríz de
• *
Distancias Dada
#include <stdio.h>
#include <stdlib.h>
/******************************************************************************/
int main(int argc, char *argv[])
{ int i, j;
int n;
FILE *fp;
int *Astorage;
int **A;
int x;
if(argc != 3)
{ printf("\nDebe ser: generar <n> <archivo> ");
printf("\ndonde la matriz de distancias es nxn");
printf("\n");
exit(1);
}
Programa generar(cont)
n=atoi(argv[1]);
//Abrir archivo para escribir
if((fp = fopen(argv[2], "w")) == NULL)
{ printf("\n*** no se puede escribir en archivo %s ***\n", argv[2]);
exit(1);
}

/* escribir las dimensiones n y n en el archivo */


fwrite(&n, sizeof(int), 1, fp);
fwrite(&n, sizeof(int), 1, fp);
// Asignar memoria para almacenar el arreglo
if((Astorage = (int *)malloc(n * n * sizeof(int))) == NULL)
{ printf( "\n*** no hay memoria ***\n");
exit(2);
}
//Asignar memoria para los apuntadores a las filas
if((A = (int **)malloc(n * sizeof(int *))) == NULL)
{ printf("\n*** no hay memoria ***\n");
exit(2);
}
Program generar (cont)
/* inicializar arreglo de apuntadores */
for(i = 0; i < n; ++i)
A[i] = &Astorage[i * n];
/* Entrar la matriz de distancias desde el teclado*/
/* set all values */
for(i = 0; i < n; ++i)
for(j = 0; j < n; ++j)
{
printf("A[%d][%d]=",i,j);
scanf("%d",&A[i][j]);
}

/* escribir el arreglo en el archivo */


fwrite(Astorage, sizeof(int), n * n, fp);

fclose(fp);
return(0);
}
• }
Función para Leer Matríz de
Distancias
/** Process p-1 opens a file and inputs a two-dimensional
matrix, reading and distributing blocks of rows to the
other processes.*/

void read_row_striped_matrix (
char *s, /* IN - File name */
void ***subs, /* OUT - 2D submatrix indices */
void **storage, /* OUT - Submatrix stored here */
MPI_Datatype dtype, /* IN - Matrix element type */
int *m, /* OUT - Matrix rows */
int *n, /* OUT - Matrix cols */
MPI_Comm comm) /* IN - Communicator */
{
int datum_size; /* Size of matrix element */
int i;
int id; /* Process rank */
FILE *infileptr; /* Input file pointer */
int local_rows; /* Rows on this proc */
void **lptr; /* Pointer into 'subs' */
int p; /* Number of processes */
void *rptr; /* Pointer into 'storage' */
MPI_Status status; /* Result of receive */
int x; /* Result of read */
read_row_striped_matrix (cont)
MPI_Comm_size (comm, &p);
MPI_Comm_rank (comm, &id);
datum_size = get_size (dtype);

/* Process p-1 opens file, reads size of matrix,


and broadcasts matrix dimensions to other procs */

if (id == (p-1)) {
infileptr = fopen (s, "r");
if (infileptr == NULL) *m = 0;
else {
fread (m, sizeof(int), 1, infileptr);
fread (n, sizeof(int), 1, infileptr);
}
}
MPI_Bcast (m, 1, MPI_INT, p-1, comm);

if (!(*m)) MPI_Abort (MPI_COMM_WORLD, OPEN_FILE_ERROR);

MPI_Bcast (n, 1, MPI_INT, p-1, comm);


read_row_striped_matrix (cont)
local_rows = BLOCK_SIZE(id,p,*m);

/* Dynamically allocate matrix. Allow double subscripting


through 'a'. */

*storage = (void *) my_malloc (id,


local_rows * *n * datum_size);
*subs = (void **) my_malloc (id, local_rows * PTR_SIZE);

lptr = (void *) &(*subs[0]);


rptr = (void *) *storage;
for (i = 0; i < local_rows; i++) {
*(lptr++)= (void *) rptr;
rptr += *n * datum_size;
}
Comparación ventajas y
desventajas con Distrak
• El algoritmo de Floyd y Warshall
• El algoritmo de Dijkstra no funciona con pesos
negativos. El siguiente
• algoritmo (Floyd y Warshall) permite hallar la
ruta m´as corta a´un en presencia
• de pesos negativos, siempre y cuando no haya
ciclos de costo total negativo.
• Además, a diferencia del algoritmo de Dijkstra,
este algoritmo no halla las
• distancias m´as cortas desde un v´ertice fijo u,
sino todos los pares de distancias
read_row_striped_matrix (cont)
/* Process p-1 reads blocks of rows from file and
sends each block to the correct destination process.
The last block it keeps. */

if (id == (p-1)) {
for (i = 0; i < p-1; i++) {
x = fread (*storage, datum_size,
BLOCK_SIZE(i,p,*m) * *n, infileptr);
MPI_Send (*storage, BLOCK_SIZE(i,p,*m) * *n, dtype,
i, DATA_MSG, comm);
}
x = fread (*storage, datum_size, local_rows * *n,
infileptr);
fclose (infileptr);
} else
MPI_Recv (*storage, local_rows * *n, dtype, p-1,
DATA_MSG, comm, &status);
}
Función para Imprimir la Matríz de
Distancias
• print_row_striped_matrix
• Tambien en MyMPI.c
Como Usar el Programa Paralelo
de Floyd
• Crear un archivo que contiene la matríz de
distancias utilizando o generar.c (para un
grafo especifico) o genMat4floyd.c (para un
grafo a la azar)
• Si el archivo de objeto está en generar o
genMat4floyd, respectivamente, entonces se
ejecutan por
• ./generar n archivo
• o
./genMat4floyd n r archivo semilla
Compilar y Ejecutar
• Compilar MyMPI.c y floyd.c separadamente para
crear archivos o
mpicc –c MyMPI.c
mpicc –c floyd.c
• Link:
mpicc MyMPI.o floyd.o -o floyd
• Ejecutar:
mpirun –np p ./floyd archivo
donde p es un número específico de
procesadores
Ejercicio
• (6.3 del texto)
Indicar los cambios que hay que hacer en
la implementación de Floyd si asignamos
bloques de columnas a los procesos en
vez de bloques de filas.

También podría gustarte