Programación Paralela: LU Decomposition usando la

Anuncio
Programación Paralela: LU Decomposition usando la
biblioteca de funciones PVM
Raymundo Domı́nguez Colı́n
*
Centro de Investigación y de Estudios Avanzados del IPN
Av. Instituto Politécnico Nacional No. 2508 Col. San Pedro Zacatenco.
México D.F, 07360. Tel.(+52 55) 5061 38000 ext: 3758 y 3756
En este documento se presenta una descripción de la versión paralela de la Factorización LU utilizando los módulos de la máquina virtual PVM. Se dá una breve descripción del funcionamiento de
las rutinas utilizadas y se describe el modelo maestro-escalvo utilizado en esta versión, que es diferente
a las entregas anteriores. Se describe el algoritmo utilizado, pruebas, comprobación de resultados y
gráficas de rendimiento en las que se muestra que es fáctible realizar paralelismo en algunos segmentos del código que permiten aumentar su rendimiento y aprovechar la potencia del funcionamiento de
múltiples procesadores trabajando al mimso tiempo.
1. Introducción
En este documento se presenta una manera de aplicar paralelismo a la Factorización LU, utilizando
rutinas propias de la biblioteca PVM (Parallel Virtual Machine). Básicamente La Factorización
LU de una matriz cuadrada A, es la descomposición de esta en el producto de una matriz diagonal
inferior unitaria L y una matriz superior U . Se consideró el método de la factorización LU por ser
uno de los más directos y el que se presta mejor para la paralelización debido a la independencia de
los ciclos internos. Este método modifica la matriz de entrada An×n realizando substituciones con el
complemento Schur con respecto a la posición a11 de A. La versión paralela fue implementada
usando las rutinas de la biblioteca PVM basaándose en el modelo de comunicación maestro-esclavo.
¿Qué es cómputo paralelo?
El cómputo paralelo consiste en un conjunto de procesos (o procesadores) que trabajan juntos en
la solución de un problema computacional. Este tipo especial de cómputo comienza a tomar gran
fuerza debido a que la velocidad de los procesadores sencillos ya no puede mejorar, incluso la Ley de
Moore fue rota en 2007 debido a que aumentó el consumo de energı́a de los procesadores [1]. De esa
manera, el desempeño llegará usando múltiples procesadores.
PVM
PVM es un software que permite una collección heterogenes de computadores Linux y/o Windows
trabajar juntas a través de una red que actúa como una gran computadora sencila. De esta manera,
muchos problemas de cómputo pueden ser resueltos de manera efectiva usando el poder y la memoria
de muchas computadoras. Tiene la ventaja de que es un software portable y de que el código es de
libre distribución y que ha sido compilado en todos lados, desde laptops hasta CRAYs []. PVM
permite a los usuarios explotar el hardware existente en sus computadoras para resolver problemas
* Email:
[email protected]
Programación Paralela, Factorización LU
2
Raymundo Domı́nguez Colı́n
grandes con un mı́nimo cosoto adicional.
Para el diseño de esta versión de la factorización LU se uso el modelo maesteo-esclavo que, a diferencia
de las versiones anteriore, en esta ocasión el proceso maestro participa de manera activa tanto en
la distribución de trabajo como en la comunicación con los procesos esclavos en cada una de la
iteraciones del programa. Todos los procesos tienen su propio bloque de instrucciones y datos, los
procesan y se comunican con el maestro mediante el paso de mensajes logrando de esta manera
modificar, a partir de los datos propios, un mismo bloque de memoria.
En este documento se describe la manera en la que se implementó una versión paralela para el
problema de la Factorización LU, el algoritmo empleado, las gráficas de desempeño de aceleración
respecto a la versión secuencial.
2. Descripción del problema: Paralelización de la Factorización LU
La Factorización LU es uno de los principios básicos del algebra lı́neal y es usada principalmente
para solucionar sistemas de ecuaciones lineales de la forma Ax = b [2] siendo A una matriz no
singular y también para calcular el determinante. Aunque es posible resolver estos sistemas usando
la inversa de A, es decir, x = A−1 , casi nunca es una buena idéa debido principalmente a razones
de eficiencia y en lugar de eso, la factorización de A de la forma A = LU donde L y U son matrices
superiores (U solo tiene ceros por encima de la diagonal) e inferiores (L solo tiene ceros debajo de la
diagonal) respectivamente, es más eficiente.
A manera de ejemplo, una matriz A3×3 , se puede factorizar como:

l11
A = LU ⇒ l21
l31
0
l22
l32
 
0
u11
0 × 0
l33
0
u12
u22
0
 
u13
a11
u23  = a21
u33
a31
a12
a22
a32

a13
a23 
a33
Una factorización LDU es una factorización de la forma:
A = LDU
donde D es una matriz diagonal y L y U son matrices triangulares unitarias, lo que significa que
todas las entradas en la diagonal de L y U son 1. Una factorización LU P es de la forma
AP = LU
donde L y U son de nuevo matrices inferior y superior y P es una matriz permutación, es decir, una
matriz de ceros y unos que tiene exáctamente un 1 en cada fila y columna.
El problema consiste en a partir de un algoritmo secuencial, lograr modificarlo de alguna manera en
algunas de sus partes más significativas de modo que las tareas y los datos puedan ser distribuidos en
diferentes procesos que estén comunicados y sincronizados entre si, de tal modo que las operaciones
que realizen no se traslapen o haya corrupción de datos.
Programación Paralela, Factorización LU
Factorización LU usando rutinas de PVM
3
Algoritmo secuencial
El algoritmo secuencial produce una matriz triangular superior U y una matriz triangular unitaria
inferior L. Para facilitar la posterior paralelización, se realizarón una serie de modificaciones a este
algoritmo (Doolitle).Debido a que la matriz U tiene solo 0’s debajo de la diagonal y debido a que
no se verifican ni modifican esas entradas, el código no las modifica; por otro lado, debido a que la
matriz de salida L tiene solo 1’s debajo de su respectiva diagonal y 0’s sobre la misma, tampoco se
tocan esas entradas. Ası́, el algoritmo 1 computa solo las entradas más significantes de las matrices
L y U.
Algoritmo 1 Algoritmo secuencial para encontrar la factorización LU
Requiere: A
for k ← 1 to n do
ukk ← akk
for i ← k + 1 to n do
lik ← aik /ukk
uki ← aki
end for
for i ← k + 1 to n do
for j ← k to n do
aij ← aij − lik ukj
end for
end for
end for
return L and U
3. Descripción del algoritmo paralelo
El problema principal al momento de diseñar un algoritmo paralelo consiste en responder a las
preguntas ¿Cómo distribuir los datos?, y ¿cuáles datos?. Desde luego que los datos que interesan
son los de la matriz A; la distribución que se haga de estos sumado a una modificación eficiente del
algoritmo secuencial y a la sincronización entre los procesos involucrados, nos darán un programa que
despues de ciertas iteraciones debe superar al algoritmo secuencial, aunque esto no siempre sucede.
La distribución de datos de la matriz A se consigue analizando detenidamente el algoritmo secuencial
mostrado anteriormente (algoritmo 1). La mayor parte del trabajo el este algoritmo secuencial es la
actualización:
aij := aij − aik akj
para el elemento aij de la matriz con i, j, ≥ k + 1. Ası́ que si distribuimos estos valores aij , aik y akj a
diferentes procesos, se puede actualizar la matriz considerando solo estos elementos, Esto es porque
la actualización de una fila i se realiza con pocos elementos, es decir solo aik , akj son usados para esta
actualización y vienen de la columna k o de la fila k de la matriz. Ası́ que un hilo puede computar
el nuevo valor de ai j usando la comunicación entre los valores aik , akj tal como se veen la figura 1.
Programación Paralela, Factorización LU
4
Raymundo Domı́nguez Colı́n
Figura 1. Se muestra que la fila aij se actualiza con la comunicación de los valores aik , akj
Esquemas de particionamiento
Debido a las especificaciones de PVM, cada procesador tendrá sus propios datos, en este caso serı́a
la matriz A que debe factorizarse. La distribución de los datos para cada procesador se realiza de la
siguiente manera: las filas de la matriz se van a distribuir entre el número de procesadores que se
vayan a ejecutar, esto es que como tanto el tamaño del problema como el número de procesadores
son dinámicos, se repartirán también dinamicamente. En la figura 2 se muestra la manera en la que
se dividen los datos de la matriz A siendo esta una matriz cuadrada de n × n.
Figura 2. Se muestra la segmentación de la matriz A
El vector v es un vector columna de tamaño n − 1, wT es un vector fila también de tamaño n − 1 y A0
es una submatriz de (n − 1) × (n − 1). El elemento A0 − vwT /aa11 es lo que se le llama Complemento
Schur [3] de A con respecto a a11 . Entonces como puede verse que se requieren a lo más n − 1
procesos para que a cada uno le corresponda una fila de la matriz A0 . Esta distribución se realiza
dividiendo el valor de n − 1 entre el número de procesadores -1 también, de tal modo que que haya
una correspondencia equivalente entre todos los procesadores, es decir que si tenemos una matriz de
tamaño n = 100 y se ejecutará con P = 3 procesos, tenemos que a cada proceso le tocan Pn−1
−1 filas
para modificar.
Programación Paralela, Factorización LU
Factorización LU usando rutinas de PVM
5
Figura 3. Se muestra la distribución de las filas a cada uno de los procesadores
Ahora bien, si se observa bien la forma en la que trabaja el algoritmo, puede apreciarse que hay más
iteraciones en la parte baja de la matriz que en la parte alta, por lo tanto la distribución de las filas
entre los procesos no debe ser de filas contiguas pues se cae en un caso llamado mapeo bloqueante. El
problema con este tipo de mapeo es que es una gran carga desbalanceada y provoca muchos tiempos
muertos de los procesos que ya han terminado de procesar las filas que les corresponden. La solución
a esta carga desbalanceada es el llamado mapeo cı́clico.
El mapeo cı́clico divide la filas de la matriz entre n/P (P es el número de procesadores) secciones, y
entonces le asigna a cada procesador una fila de cada sección. De esta manera se mejora el problema
de la carga desvalanceada. Por ejemplo, suponga a la matriz A de 16 × 16, y tenemos 4 procesadores
(P = 4), la tabla 1 lista las filas que le son dadas a cada procesador.
Mapeo Bloqueante
Mapeo Cı́clico
P0
0, 1,, 2, 3
0, 4, 8, 12
P1
4, 5, 6, 7
1, 5, 9, 13
P2
8, 9, 10, 11
2, 6, 10, 14
P3
12, 13, 14, 15
3, 7, 11, 15
Cuadro 1. Ejemplo de mapeos bloqueante y cı́clico
Comunicación
Se implementaron dos diferentes tipos de comunicaciones entre los procesadores. Todas usando la
instrucción pvm send() de PVM pero enviando diferentes tipos de datos, un tipo eran enteros simples
y los otros eran buffers de datos de tipo float. Los enteros son más que nada el tamaño del problema,
ı́ndices que hay que comparar con datos localesde cada procesador entre otros. Para el caso de los
buffer, cuando un procesador ha completada las operaciones sobre las filas que le corresponden,
empaqueta sus datos y los envia al maestro quien, actualiza la matriz de trabajo y vuelve a enviar
las filas a los procesadores. Esto significa que todos los procesadores deben esperar hasta que reciben
sus datos.
Modelo maestro-esclavo
Básicamente, el algoritmo empleado en esta versión utiliza una versión mejorada del modelo maestroesclavo que se manejó en las versiones anteriores. Se define un procesador que fungirá como maestro
y será este el que se encargue de repartir el trabajo a los otros procesadores. Además de esto, en cada
iteración este proceso maestro recibe las filas ya procesadas por los demás, actualiza la matriz y vuelve
a enviar las filas para la siguiente iteración. El algoritmo 2 define el pseudocódigo de instrucciones
que maneja el proceso padre y el siguiente algoritmo define lo realizado por cada uno de los otros
procesos.
Programación Paralela, Factorización LU
6
Raymundo Domı́nguez Colı́n
Algoritmo 2 Bloque de instrucciones del padre
Requiere: a, n, buf f er
//otros procesos (llenado de matriz, declaración de variables, etc)
if myrank == 0 then
for k ← 0 to n − 1 do
for h ← 1 to numprocesos do
buf f er ← f ilapivote(a)// a partir de buffer[0] asigna la fila pivote
buf f er ← asignaf ilas()// ahora le asignamos la filas que le toca procesar al proceso h
pvm initsend();
pvm send(k)//envı́a el valor de k; es necesario para los procesos hijos
pvm sed(buffer, h)// envı́a el buffer al proceso h
end for
// recepcion de filas ya procesadas por los procesos
for i ← 1 to numproc − 1 do
pvmrecv(recvbuf f er, i)// recibe buffer de hijo i
actualizaM atriz(recvbuf f er, a)// actualiza las filas de a con la info del buffer
end for
end for//for de k
else
for iter ← 0 to n − 1 do
//trabajo de los hijos
pcm recv(k 0 , SOU RCE) //recibiendo ı́ndice k y almacenando en k’
pcm recv(recvbuf f, SOU RCE) //recibiendo el buffer
subA ← creaSubM atriz();
cont ← 1
for i ← 1 to numT areasHijo[myrank] do
if indiceTarea[cont] ¿k’ then
// no se debe de tocar la fila pivote k’
subAi,k0 ← subAi,k0 /subAk0 ,k0
for j ← k 0 + 1 to n do
subAi,j ← subAi,j − subAi,k0 ∗ subAk0 ,k0
end for
end if
cont ← cont + 1
end for
buf f er ← llenaBuf f er()
pvm initsend(buffer, SOURCE); //enviando el buffer modificado al padre
end for
end if
Mecanismos de coordinación y sincronización entre procesos
Cada proceso recibe solamente una copia de las filas que le tocan procesar de acuerdo al mapeo cı́clico
y realizará las operaciones sólo en estas filas. Además del trabajo asignado, cada procesador recibe
una copia de la fila pivote, que no es más que la fila de la matriz principal cuyo ı́ndice es el valor
de k. En cada iteración, los procesos deben conocer las modificaciones realizadas por cada uno de
los otros procesos, ası́ que existe una comunicación triangular entre ellos en los que se intercambian
la información de las filas procesadas por cada uno mediante las rutinas PVM de paso de mensajes.
Cada uno de los procesadores empaqueta los datos de sus filas en un buffer y los envı́a al maestro que
los recibe y los vuelve a enviar para la siguiente iteración. Entonces a cada proceso le corresponde
una o más filas de la matriz, que va a actualizar de manera paralela a los otros procesos como se ve
en la figura 3. En la figura 4 se muestra un esquema del mecanismo de funcionamiento del programa.
Programación Paralela, Factorización LU
Factorización LU usando rutinas de PVM
7
Figura 4. Esquema de procesamiento y comunicación de los procesos
4. Resultados
a) Ejemplo(s) y verificación de resultados. A manera de ejemplo, desarrollemos la siguiente
matriz A con la ayuda de 2 procesos de ejecución:
06
BB3
BB1
@4
2
3
9
2
5
1
1
2
2
6
1
4
5
6
1
6
2
1
1
6
7
1
CC
CC
A
El primer paso es dividir el trabajo a cada uno de los procesos. En este caso tenemos un valor de n = 5
y número de procesos p = 3, pero de acuerdo al algoritmo, solo necesitaremos procesar a lo más n − 1
filas y utilizaremos p − 1 procesos debido a que tenemos un proceso maestro. Este proceso hace la
función de un orquestador que inicializa el tamaño de la matriz, reparte el trabajo entre los procesos
y espera los resultados para actualizar la matriz y repetir la operación. El proceso de envı́o consiste
en preparar un buffer de tamaño n + (n ∗ numf ilas); donde n es el tamaño de la matriz y numf ilas
son las filas que le toca a cada proceso. En este caso son dos. Este buffer se llena primero con la fila
pivote y después con las filas que le corresponden a cada proceso. Por ejemplo, el primer buffer para el
proceso con rank = 1 quedarı́a ası́: buffer = {6, 3, 1, 4, 2, 3, 9, 2, 5, 1, 4, 5, 6, 1, 6}, que corresponden
a la fila 0, 1 y 3 de la matriz.
Los procesos reciben cada quien su buffer y crean una submatriz de dos dimensiones para poder realizar
Programación Paralela, Factorización LU
8
Raymundo Domı́nguez Colı́n
las operaciones propias del algoritmo más facilmente. Como en este caso son dos procesos, se le asigna
dos filas a cada uno quedando el proceso 1 con las filas 1 y 3, mientras que el proceso 2 se queda con las
filas 2 y 4. Cada hilo actualizará sus filas en cada iteración y luego envı́a esas filas que ha modificado al
proceso maestro. Esta es una fase de sincronización, es decir, el envı́o de las filas es bloqueante pues un
proceso no puede continuar hasta que el maestro le envı́e más trabajo, ası́ que si el proceso 1 termina
primero, tendrá que esperar a que el otro termine.
De acuerdo al algoritmo 2, el valor k 0 nos indica cuál sera nuestro elemento a11 , el vector v, el vector
wT y la matriz A0 . La fila k 0 = 0 la llamaremos pivote y no se alterará durante la actualización de las
demás filas, de hecho, las n − 1 filas se actualizan a partir de esta y del elemento aij correspondiente.
Entonces con k 0 = 0 e i = primerf ila del proceso en cuestión, si el proceso tiene rank = 1, entonces
i = 1, pues es la primer fila que le corresponde procesar. Ası́ que para el proceso 1, el elemento aik0 = 3
se divide con ak0 k0 = 6, mientras que para el proceso 2 los valores son aik0 = 1, pues la primer fila a
procesar de este proceso es la 2, y el ak0 k0 = 6 también; se inicializa el valor de j = k 0 + 1 = 1, y se entra
en el primer ciclo de ejecución en el que se actualizarán todos los valores de la fila i con el incremente
de j hasta n. La matriz A se va modificando con:
aij = aij − aik0 × a0j
Ası́ que despues de este primer ciclo, i primera iteración, estas dos filas modificadas por ambos procesos
deben ser conocidas por el otro, entonces cada proceso prepara un buffer de envı́o con la información
de sus dos filas procesadas (la fila pivote no es necesario que la conozca el proceso maestro); el maestro
recibe los paquetes de los procesos y realiza una actualización de la matriz con estas nuevas filas y se
repite la operación hasta que se cumple las condiciones del ciclo.
Por ejemplo, en esta primera iteración, cuando el proceso padre recibe el paquete del proceso 1, la
matriz queda con los siguientes valores:
06
BB 0,5
BB 1
@0,67
2
0
7,5
2
2,99
1
6
1,5
2
5,33
1
2
3
6
−1,68
6
1
CC
CC
A
9
0
1
4,66
7
Ası́ que solo resta esperar las filas 2 y 4 (ya procesadas) que del proceso 2. Ası́ que al final de la primera
iteración, la matriz queda ası́:
06
BB 0,5
BB0,16
@0,67
0,33
3
7,5
1,52
2,99
0,01
1
1,5
1,84
5,33
0,67
4
3
5,36
−1,68
4,68
1
CC
CC
A
2
0
0,68
4,66
6,34
Entonces el proceso padre ya tiene lo que necesita para envı́ar de nuevo las filas a cada proceso con
los nuevos valores. Ahora con el incremento de k, se vuelve a repetir la operación, solo que ahora el
proceso 1 ya no ejecuta sus dos filas sino solo una, la fila 3, esto es porque en la siguiente iteración
la fila 1 se ha comvertido en fila pivote y no require de modificación alguna por lo que esperará hasta
que todos los demás procesadores terminen para poder ser liberado. En cambio el hilo 2 aún le quedan
algunas iteraciones más que realizar. Al terminar todos los procesos su ejecución, nos queda la matriz
resultado siguiente:
06
BB 0,5
BB0,17
@0,67
0,33
Programación Paralela, Factorización LU
3
7,5
0,2
0,4
0,0
1
1,5
1,53
3,09
0,43
4
3
4,73
−17,48
−0,15
1
CC
CC
A
2
0
0,67
2,61
6,43
Factorización LU usando rutinas de PVM
9
Esta matriz se divide para obtener finalmente las matrices L y U buscadas y se muestran como sigue:
01
B
B0,50
0,17
L=B
B
@0,67
0,33
0
1
0,20
0,40
−0,00
0
0
1
3,09
0,43
0
0
0
1
−0,15
1
CC
CC
A
0
0
0 ×U =
0
1
06,00
BB 0
BB 0
@0
0
3,00
7,50
0
0
0
1,00
1,50
1,53
0
0
4,00
3,00
4,73
−17,48
0
1 06
CC BB3
CC = BB1
A @4
2,00
0,00
0,67
2,61
6,43
2
3
9
2
5
1
1
2
2
6
1
4
5
6
1
6
La salida del programa nos muestra (por razones de legibilidad) solo la matriz inicial y la matriz final
resultante. Si el tamaño de la matriz es mayor a 15, el programa imprime solo el tiempo de ejecución
que tardó el programa en realizar la ejecución.
Figura 5. Salida del programa.
b) Gráfica de tiempos de ejecución vs. Número de procesadores PVM
# Procesos
1
2
4
6
7
n100
0.38
0.18
0.48
0.06
0.05
n200
0.44
0.36
0.38
0.38
0.35
n500
6.73
6.66
6.72
6.89
5.72
n1000
40.32
55.13
58.34
50.01
59.13
Cuadro 2. Tabla de resultados
Programación Paralela, Factorización LU
2
1
1
6
7
1
CC
CC
A
10
Raymundo Domı́nguez Colı́n
Figura 6. Matriz de 100 × 100 con diferentes proceso de ejecución
Figura 7. Matriz de 200 × 200 con diferentes proceso de ejecución
Figura 8. Matriz de 500 × 500 con diferentes proceso de ejecución
Programación Paralela, Factorización LU
Factorización LU usando rutinas de PVM
11
Figura 9. Matriz de 1000 × 1000 con diferentes proceso de ejecución
c) Análisis de complejidad del algoritmo con procesos PVM
Para este análisis, hay que tomar en cuenta además del tiempo de cómputo que toma el algoritmo, el
costo de comunicación. Como el algoritmo hace uso de la eliminación Gaussiana, esto le toma O(n3 )
operaciones. Asumiendo que cada operación de punto flotante toma unidades de tiempo y que n es
suficientemente grande, el tiempo de cómputo es T (1, n) = 23 ∗ n3 + costo de comunicación.
La computación y comunicación en el desarrollo del algoritmo y paso de mensajes es lo que cuesta más
en esta versión paralela de la factorización LU. Con
d) Ajuste de curvas: expresión matemática
e) Gráfica de aceleración (tiempo secuencial/tiempo paralelo)
Programación Paralela, Factorización LU
12
Programación Paralela, Factorización LU
Raymundo Domı́nguez Colı́n
Factorización LU usando rutinas de PVM
13
5. Conclusiones
En este momento el tiempo de la versión paralela ya está muy cerca del tiempo de la versión secuencial de PVM, sin embargo estamos ya muy cerca de lograrlo. La distribución de datos que se
implementó en está versión mejoró en gran medida los tiempos vistos en las anteriores entregas. Se
está considerando una manera de reducir la longitud de los buffers de comunicación entre el proceso
maestro contra los otros procesos. Esto es porque a medida que aumenta el valor de k, hay columnas que ya no se modifican, entonces ya no es necesario envı́ar el tamaño del buffer completo sino
solamente la parte que en realidad le va a servir al proceso receptor. De igual manera, los procesos
receptores reducirán el tamaño del buffer de respuesta al maestro lo cuál reducira tiempos de comunicación y es posible que se supere los tiempo dados por la versión secuencial. De hecho como
puede verse en las gráficas de aceleración, ya lo hace en algunos casos pero comienza a dispararse
para valores de n = 1000.
Se pretende realizar estas modificaciones para la entrega final y aplicarlo también a la versión de
MPI y threads.
5.0.1 Anexo: Código del programa
1
2
/*
FActorizacion LU version PVM
3
4
*/
5
6
7
/* defines and prototypes for the PVM library */
#include "funciones.h"
8
9
10
11
12
13
14
15
16
int main(int argc, char* argv[]){
int i, j, blk, rec, numfil, residuo, valor, fila, band, inf;
int mult =0, indice;
int *tareas, *hilos;
float **array, *aux = NULL, *results = NULL;
int lista[MAXPROC][MAXTAREAS] = {0};
float *aux2 = NULL, *filas = NULL, **subA = NULL;//arreglos de los hijos
clock_t comienzo, final;
17
Programación Paralela, Factorización LU
14
18
19
20
21
22
Raymundo Domı́nguez Colı́n
float promedio;
struct timeval tinicial, tfinal, tdiferencia;
struct timezone zone;
long
totalsec=0,totalmic=0;
long ajustesec=0,ajustemicro=0;
23
24
srand((unsigned int)time((time_t *)NULL));
25
26
27
/* find out my task id number */
mytid = pvm_mytid();
28
29
30
31
32
mygid = pvm_joingroup(GRUPO);
if (mygid < 0){
pvm_perror(argv[0]); pvm_exit(); return -1;
}
33
34
ntids = pvm_siblings(&tids);
35
36
37
38
39
40
for(i=0; i< ntids; i++){
if(tids[i] == mytid){
myrank = i;
break;
}
41
42
}
43
44
45
46
47
48
49
50
51
52
blk = atoi(argv[1]);
if( blk < ntids){
if(myrank ==0){
printf("Valores incorrectos\n ");
printf("1. El tamanio de la matriz debe ser mayor o
igual al numero de procesadores\n");
}
pvm_exit(); return -1;
}
53
54
55
56
57
58
59
if (myrank == 0){//maestro
totalsec=0;
totalmic=0;
ajustesec=0;
ajustemicro=0;
60
61
62
63
64
65
if(blk <=15)
band = 1;
else
band = 0;
//comienzo = clock();
66
67
68
69
70
71
72
//gettimeofday (&tinicial, &zone);
array = creaMatrizDoble(blk);
llenaMatrizDoble(array, blk);
if(band)
imprimeMatrixDoble(array, blk, blk);
//fflush(stdout);
Programación Paralela, Factorización LU
Factorización LU usando rutinas de PVM
15
73
74
75
76
77
/* enviando el tamao de la matrix*/
pvm_initsend(PvmDataDefault);
pvm_pkint(&blk, 1, 1);
pvm_mcast(tids, ntids, TAG);
78
79
80
81
//reparticion de trabajo
numfil = (blk -1) / (ntids-1);
residuo = (blk-1) % (ntids-1);
82
83
84
85
86
87
88
89
90
91
92
fila = 1;
indice = 1;
for(i=1; i < ntids; i++){//inicia ciclo para repartir las tareas entre
if(residuo > 0){
//todos los hijos
valor = numfil +1;
residuo--;
}
else{
valor = numfil;
}
93
tareas = (int*) realloc ( tareas, sizeof(int) * (valor + 1) );
tareas[0] = valor;
//numero de tareas del hijo
lista[i-1][0] = valor;
for(j = 1; j<= valor; j++){
tareas[j] = fila;
lista[i-1][j] = fila;
fila += ntids-1;
94
95
96
97
98
99
100
101
102
}
103
//tareas[indice] = valor;
pvm_initsend(PvmDataDefault);//enviando el valor del vector de tareas
pvm_pkint(&valor, 1, 1);
pvm_send(tids[i], TAG);
104
105
106
107
pvm_initsend(PvmDataDefault);//enviando el vector de tareas para
pvm_pkint(tareas, (valor + 1), 1);//cada uno de los hijos
pvm_send(tids[i], TAREAS);
108
109
110
111
mult = mult + valor;
indice++;
fila = indice;
112
113
114
115
}
116
117
118
119
120
121
122
123
124
int k, hijo, pos, y, cont, row, p, res, formula, block;
float *aux = NULL, *results = NULL;
//clock_t comienzo = clock();
gettimeofday (&tinicial, &zone);
for(k=0; k< blk-1; k++){//bucle principal
for(hijo = 1; hijo < ntids; hijo++){//reparto de tareas
formula = blk + (blk * lista[hijo -1][0]);
// printf("hijo %d iteraciom %d tamanio de vector %d \n", hijo, k, formula);
125
126
127
aux = (float*) realloc ( aux, sizeof(float) * formula);
for(pos = 0; pos < blk; pos++)//llenando la fila pivote
Programación Paralela, Factorización LU
16
Raymundo Domı́nguez Colı́n
aux[pos] = array[k][pos];//-->llenando con la fila pivote k
cont = 1;//-->para escoger la primera fila
for(j=0; j< lista[hijo-1][0]; j++){
row = lista[hijo-1][cont];
for(y=0; y< blk; y++, pos++)
aux[pos] = array[row][y];
cont++;
}
128
129
130
131
132
133
134
135
136
pvm_initsend(PvmDataDefault);//enviando el valor de k
pvm_pkint(&k, 1, 1);
pvm_send(tids[hijo], TAG);
137
138
139
140
pvm_initsend(PvmDataDefault);//enviando el buffer
pvm_pkfloat(aux, formula, 1);
pvm_send(tids[hijo], FILAS);
141
142
143
144
}
145
146
147
148
149
150
151
//recibiendo los resultados de los hijos!!
for(res = 1; res < ntids; res++){
block = blk * lista[res-1][0];//memoria para el buffer de recepcion
results = (float*) realloc ( results, sizeof(float) * block );
pvm_recv(tids[res], RESULTS);
pvm_upkfloat(results, block, 1);
152
//metiendo el buffer a la matriz original
cont=1;
153
154
155
for(p=0; p< lista[res-1][0]; p++ ){
row = lista[res-1][cont];
for(y=0; y < blk; y++){
array[row][y] = results[ p * blk + y ];
}
cont++;
printf("\n ");
}
156
157
158
159
160
161
162
163
164
}
165
166
} //end over bucle principal sobre k
167
168
169
170
}//end over if
else {//trabajo de los hijos
int res, n, tam, k2, size, cont, band, info, iter, virtualk, kill;
171
172
173
pvm_recv(tids[SOURCE], TAG);//recibe el tamao del problema
pvm_upkint(&n, 1, 1);
174
175
176
pvm_recv(tids[SOURCE], TAG);//tamao del vector de tareas
pvm_upkint(&tam, 1, 1);
177
178
179
180
hilos = (int*) realloc (hilos, sizeof(int) * tam + 1);
pvm_recv(tids[SOURCE], TAREAS);//# tareas de cada hilo y desgloce
pvm_upkint(hilos, tam + 1, 1);
181
182
for(iter =0; iter < n-1; iter ++){//ciclo de recepcion de los hijos
Programación Paralela, Factorización LU
Factorización LU usando rutinas de PVM
17
pvm_recv(tids[SOURCE], TAG);//recibiendo el valor de la fila pivote k
pvm_upkint(&k2, 1, 1);
183
184
185
186
187
size = n + ( n * hilos[0]);//calculando el tamao del buffer de recepcion
aux2 = (float*) realloc ( aux2, sizeof(float) * size);//del hijo myrank
188
189
190
pvm_recv(tids[SOURCE], FILAS);
pvm_upkfloat(aux2, size, 1);
//recibiendo el buffer
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
//printf("\nhijo %d creando submatriz iteracion %d \n", myrank, k2);
subA = creaSubMatriz( (hilos[0] + 1), n);
//
printf("\n\nhijo [%d] imprimo mis filas \n", myrank);
uneArregloMatriz(aux2, subA, hilos[0] + 1, n);
//imprimeMatrixDoble(subA, hilos[0] + 1, n);
cont=1;
//desarrollo de la elimincion gaussian
for(i=1; i< hilos[0]+1; i++ ){
if(hilos[cont] > k2){//evaluando si estamos en la columna
if( (subA[virtualk][k2] == 0 )){//|| ( subA[virtualk][k2] < 0.05 ) ){
printf("--- La matriz es singular. No tiene solucion --abortando programa..");
for(kill= 0; kill < ntids; kill++){
if(myrank != kill)
pvm_kill( tids[kill]);
206
}
pvm_exit();
return -1;
207
208
209
210
211
212
213
214
}
subA[i][k2] = subA[i][k2] / subA[virtualk][k2]; //pivote
for(j = k2 +1; j< n; j++)
subA[i][j] = subA[i][j] - subA[i][k2] * subA[virtualk][j];
215
}
cont++;
216
217
218
}
219
220
221
222
223
//reservando memoria
int pos = 0, tambuff;
tambuff = n * hilos[0];
filas = (float*) realloc ( filas, sizeof(float) * tambuff);
224
225
formaBuffer(filas, subA, hilos[0], n);
226
227
228
229
230
//enviando los resultados al padre
pvm_initsend(PvmDataDefault);
pvm_pkfloat(filas, tambuff, 1);//empaketando el buffer
pvm_send(tids[SOURCE], RESULTS);
231
232
233
}//end over for de iter
}//end over else
234
235
236
237
if(myrank == 0 && band ){
printf("---- RESULTADO------\n");
//imprimeMatrices(array, blk);
Programación Paralela, Factorización LU
18
Raymundo Domı́nguez Colı́n
238
239
}
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
if( pvm_barrier(GRUPO, ntids) == 0){
//final = clock();
gettimeofday (&tfinal, &zone);
if(myrank == 0){ //libearcion de memoria
//printf("El proceso ejecutado con %d procesadores duro %.8f segundos", ntids-1,
//(double)((final - comienzo)/(double) (CLOCKS_PER_SEC)) );
//fflush(stdout);
//gettimeofday (&tfinal, &zone);
tdiferencia=calcular_deltat (tinicial, tfinal);
totalsec=totalsec+tdiferencia.tv_sec;
totalmic=totalmic+tdiferencia.tv_usec;
ajustesec=totalmic/1000000;
ajustemicro=totalmic -(ajustesec*1000000);
promedio=(((totalsec*1000000)+totalmic) / blk * blk);
printf("El tiempo de procesamiento fue de %.4f microsegundos = %.4f segundos",
promedio,promedio/1000000);
257
258
259
260
261
262
263
264
265
266
freematrix(array, blk);
}
free(aux);
free(tareas);
free(results);
free(hilos);
free(aux2);
}
pvm_exit();
267
268
269
printf("its done!");
fflush(stdout);
270
271
return 0;
272
273
274
275
276
277
//**********************************************************************************
//biblioteca de funciones funciones.h
#include <pvm3.h> #include <stdio.h> #include <stdlib.h> #include <time.h>
//#include "funciones.h"
/* Maximum number of children this program will spawn */ #define MAXNTIDS
32 #define MAXROW
10
278
279
#define MAXPROC 32 #define MAXTAREAS 1000
280
281
282
283
284
/* Message tags */ #define SOURCE 0 #define TAG
5 //para transmitir un solo valor (int) #define ARRA
de matrices y arreglos (float) #define TAREAS 11 //transmicion de las tareas de cada proceso (int) #define
FILAS 20 #define RESULTS 21 #define GRUPO "pvmLU"
285
286
extern unsigned _floatconvert; #pragma extref _floatconvert
287
288
int *tids; int ntids; int mytid, mygid; //variables globales int myrank;
289
290
void imprimeMatrices(float **matriz, int tam){ int i,j;
291
292
printf("---------- Matriz L ------------\n");
Programación Paralela, Factorización LU
Factorización LU usando rutinas de PVM
19
for(i=0; i< tam; i++){
for(j=0; j< tam; j++){
if(i==j)
printf(" 1 ");
else if(i>j)
printf(" %2.2f ", matriz[i][j]);
else
printf(" 0 ");
}
printf("\n ");
293
294
295
296
297
298
299
300
301
302
303
}
printf("---------- Matriz U ------------\n");
for(i=0; i< tam; i++){
for(j=0; j< tam; j++){
if(i>j)
printf(" 0 ");
else
printf(" %2.2f ", matriz[i][j]);
}
printf("\n ");
}
304
305
306
307
308
309
310
311
312
313
314
315
}
316
317
318
319
320
321
void uneArregloMatriz(float *aux, float **matA, int subtam, int tam){ int i,j, desp;
for(i=0; i< subtam; i++)
for(j=0; j< tam; j++)
matA[i][j] = aux[i * tam + j];
}
322
323
324
325
326
327
328
329
float ** creaSubMatriz(int subtam, int tam){ float **m; int i;
m = (float**) malloc (sizeof(float*) * subtam );
for(i = 0; i< subtam; i++){
m[i] = (float*) malloc (sizeof(float) * tam);
//
printf("reservando...\n");
}
return m; }
330
331
332
333
334
335
336
337
338
339
void formaBuffer(float *filas, float **subA, int subtam, int tam){ int i,j;
for(i=0; i< subtam; i++){
for(j=0; j< tam; j++){
filas[i * tam + j] = subA[i+1][j];
//
printf(" %.2f ", filas[i * tam + j]);
//
fflush(stdout);
}//printf("\n ");
}
}
340
341
342
343
float * creaMatriz(int blk){ float *m;
m = (float*)malloc(sizeof(float)*blk * blk);
return m; }
344
345
346
347
float ** creaMatrizDoble(int blk){ int i; float **aux;
aux = (float**) malloc (blk * sizeof(float*));
for(i=0; i<blk; i++)
Programación Paralela, Factorización LU
20
348
349
Raymundo Domı́nguez Colı́n
aux[i] = (float*) malloc(blk * sizeof(float));
return aux; }
350
351
352
353
354
355
356
357
void llenaMatriz(float *m, int blk){ int i, len;
len = blk * blk;
len = blk*blk;
for (i = 0; i < len; i++){
m[i] = (float)(rand()%9)+1;
}
}
358
359
void imprimeMatriz(float *matriz, int blk){ int i,j;
360
for(i=0; i < blk; i++){
for(j=0; j<blk; j++){
printf(" %.2f ", matriz[i*blk + j]);
fflush(stdout);
}
printf("\n ");
}
361
362
363
364
365
366
367
368
}
369
370
371
372
373
void uneMatrices(float *aux, float **matA, int tam){ int i,j, desp;
for(i=0; i< tam; i++)
for(j=0; j< tam; j++)
matA[i][j] = aux[i * tam + j];
374
375
}
376
377
378
379
380
381
382
383
384
385
void imprimeMatrixDoble(float **m, int subtam, int tam){ int i,j;
for(i=0; i< subtam; i++){
for(j=0; j < tam; j++){
printf(" %2.2f ", m[i][j]);
fflush(stdout);
}
printf("\n ");
}
printf("\n "); }
386
387
388
389
390
391
392
393
394
395
396
397
398
399
void llenaMatrizDoble(float **mat, int tam){ int i, j, val, cont=0;
for(i=0; i< tam; i++){//llenando la matriz
for(j=0; j< tam; j++){
val = (rand() % 9) + 1;
if(i==j)
mat[i][j] = (float) val;
else{
mat[i][j] = (float) val;
mat[j][i] = mat[i][j];
}
}
}
}
400
401
402
void freematrix(float **mat, int tam){ int i;
for( i=0; i<tam; i++ )
Programación Paralela, Factorización LU
Factorización LU usando rutinas de PVM
free(mat[i]);
free(mat);
403
404
405
21
}
406
Referencias
[1] Rob Bisseling. the bulk synchronus parallel model, 2008. Online in http://www.math.uu.nl/people/
bisselin/PSC/psc2 3.pdf. Accessed 24-Febrero-2008.
[2] Mattwb. Lu decomposition, 2000. Online in http://everything2.com/index.pl?node id=701232. Consultada el 22-Enero-2008.
[3] Wikipedia. Schur complement — wikipedia, the free encyclopedia, 2008. Online in http://en.
wikipedia.org/w/index.php?title=Schur complement&oldid=185516241. Accessed 28-Febrero-2008.
Programación Paralela, Factorización LU
Descargar