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