programación paralela

35
Universidad de Oriente Núcleo de Sucre Escuela de Ciencias Departamento de Matemáticas Programa de la Licenciatura En Informática PARALELIZACION DEL ALGORITMO DE SUBSECUENCIA COMÚN MÁS LARGA CON MPI-C. Tutor: Prof. Ana Fuentes Bachiller: Jesús Alejandro Machado Ortiz C.I: 24877491

Upload: jesusalejandromachadoortiz

Post on 25-Dec-2015

32 views

Category:

Documents


3 download

DESCRIPTION

Programación con MPI

TRANSCRIPT

Page 1: Programación Paralela

Universidad de Oriente

Núcleo de Sucre

Escuela de Ciencias

Departamento de Matemáticas

Programa de la Licenciatura En Informática

PARALELIZACION DEL ALGORITMO DE SUBSECUENCIA COMÚN MÁS LARGA CON MPI-C.

Tutor:

Prof. Ana Fuentes

Bachiller:

Jesús Alejandro Machado Ortiz

C.I: 24877491

Cumaná, enero del 2014

Page 2: Programación Paralela

INTRODUCCIÓN

La eficiencia de un computador, depende directamente del tiempo requerido para poder ejecutar una instrucción básica y del número de instrucciones que pueden ser ejecutadas concurrentemente. Esta eficiencia puede ser incrementada por avances en la arquitectura y por avances tecnológicos. Un caso de esto es la nueva gama de procesadores de INTEL y AMD. Estos avances en la arquitectura incrementa la cantidad de trabajo que se puede realizar por el manejo de instrucciones, específicamente por su capacidad de ciclo de cada procesador. Gracias a las memorias cache, memorias paralelas, canales, memoria intercalada y procesadores paralelos.

Pero a pesar de estos grandes avances, hay un problema primordial a atacar, el cual es mejorar la eficiencia de nuestros programas. Es decir, se debe cambiar la forma en la cual se nos ha enseñado la programación. Ya debería considerarse la programación paralela como una rama primordial y activa en la incursión de nuevos programadores. Aprovechar al máximo las características hardware presente, realizar clúster, mandar instrucciones a las tarjetas de video a través de CUDA para optimizar un proceso matemático. Solo así podríamos considerar una pregunta, que es tema de debate en muchas oportunidades: ¿Algún día el software alcanzará al máximo al hardware? ¿Lo pasará de largo? ¿Hay un límite en alguno de ellos?

La aplicación que se tratará en este proyecto de desarrollo, consiste en una mezcla de estos tres tipos de aplicaciones (de gran procesamiento de cálculo, de entrada y salida de datos y de comunicación), como se verá más adelante. Se irá indagando con la forma más factible de implementarla y ejecutarla satisfactoriamente. Esto con el fin de solucionar una problemática en dicha área de estudio, como lo es “Los algoritmo de Manejo de Caracteres”.

Antes de continuar, cabe a resaltar ¿Qué es MPI? Interfaz de Pase de Mensaje, según la [wikipedia] es un estándar de funciones contenidas en una biblioteca de paso de mensaje diseñada con el fin de facilitar la explotación de múltiples procesadores. Y el paso de mensaje en sí, es una técnica empleada en la programación concurrente para aportar sincronización entre procesos y permitir la exclusión mutua (para saber más de este tema, indague la temática de gestión de procesos y concurrencia) de manera similar a como lo hacen los semáforos, monitores entre otros.

Page 3: Programación Paralela

Planteamiento

La información se representa normalmente a través de palabras, que está compuesta por símbolos o caracteres. Que vienen a representar un lenguaje (español, inglés, alemán, entre otros), permitiéndonos entender y comprender a través de un proceso cognoscitivo el “mensaje”. Observando esta analogía, se afirma que las computadoras realizan el mismo proceso, reciben a través de una entrada, procesan y emiten una respuesta. Esto da pie al siguiente algoritmo, el cual analiza cadenas de caracteres para obtener información relevante para un propósito específico.

En el caso que se estudiará en este trabajo, el cual deberá procesar secuencias de símbolos o caracteres, que representarán una información y darán como respuesta “La Subsecuencia Común Más Larga”. Ahora, ¿Qué será la Subsecuencia Común más Larga? Son una forma natural para representar información.

Actualmente existen muchos problemas relacionados con las cadenas de caracteres, pero el que más frecuencia se presenta es el de comparación de secuencias. Como ejemplo, en el análisis de secuencias biológicas la similitud de secuencias es uno de los conceptos de mayor importancia, principalmente por el hecho de observar las posibles relaciones de evolución compartidas por los organismos. Esta puede ser expresada como un porcentaje de identidad útil para determinar las homologías entre secuencias, la cual puede ser, por ejemplo, precisar si comparten un mismo ancestro (recordando a Charles Darwin). Este porcentaje de identidad es una tasa entre el número de columnas con caracteres idénticos encontrados en un alineamiento y el número de símbolos de la secuencia más larga.

Definición Formal:

Navarro, Gonzalo expresa en su libro “A Guided Tour To Aproximate String Matching”. ACM Computing Surveys, (2001) pp. 31 -88. Dice:

En inglés Longest Common Subsequence (LCS) –Sean las secuencias x y y que pertenecen a∑, y con una longitud M y N (M >= N) respectivamente, el LCS de la secuencias x y y consiste en encontrar una Subsecuencia de x y de y de máxima longitud posible.

Se deduce entonces como el LCS(x,y) es una Subsecuencia de x , y e igualmente, como x y y son supersecuencias de LCS(x,y). El LCS no permite la operación de sustitución, únicamente las operaciones de inserción y eliminación.

Page 4: Programación Paralela

Un ejemplo grafico de una LCS.

Como se puede observar los comunes son los que se encuentran en ambas cade-nas, esto no es al azar, hay un método mediante el cual se obtiene la LCS.

Para el llenado de esta tabla, nos basamos en las siguientes premisas:

E M A N C H A

E 0 0 0 0 0 0 0

M 0 1 1 1 1 1 1

A 0 1 2 2 2 2 2

C 0 1 2 2 3 3 3

H 0 1 2 2 3 4 4

A 0 1 2 2 3 4 5

D 0 1 2 2 3 4 5

O 0 1 2 2 3 4 5

Y escribimos la palabra de arriba hacia abajo quedandonos:

“MACHA”. Los marcados en rojo son los puntos dominantes para cada sección.

Page 5: Programación Paralela

ALGORITMO SECUENCIAL

Procesamiento por Filas.#include <string.h>#include <stdio.h>#include <stdlib.h>

/* * Definimos dos funciones para buscar el maximo de dos numero * y el minimo de dos numeros tambien.*/

#define MAX(x,y) (((x) >= (y)) ? (x) : (y))#define MIN(x,y) (((x) <= (y)) ? (x) : (y))

/* * @parametro char * x, primer string recibido por archivos. * @parametro char * y, segundo string recibido por archivos. * @parametro unsigned short int **c, matriz LCS. */

void LCS_procesar(char *x, char *y,unsigned short int **c);/* * @parametro unsigned shor int **c, la matriz LCS. * @parametro char *x, primer string recibido por archivos. * @parametro int i, indice de las filas de la matriz. * @parametro int j, indice de las columnas de la matriz. */

void LCS_mostrar(unsigned short int **c,char *x, int i, int j);/* * @parametro char *infile, nombre del archivo al cual leera el string */

char* LCS_leer(char *infile);

//Se tiene argc, argv como puntos de entrada de datos externos al main,//Que en este codigo, se usan para pasar el nombre de los archivos(flujos,//de entrada).

int main(int argc, char** argv){//si argc es diferente de 3, esto quiere decir que no se coloco//el flujo de entrada tanto para string 1 y string 2.//una validacion previa a la ejecucion.if(argc != 3){

printf("Porfavor, use 2 archivos de lectura");exit(0);

}//1 - leemos los archivos, creamos los string y se lo asignamos a//nuestras variables.char *X = LCS_leer(argv[1]);char *Y = LCS_leer(argv[2]);

Page 6: Programación Paralela

//Tomando el tamaño de X y Y, para porteriormente crear la matrizint len_X = (int) strlen(X);int len_Y = (int) strlen(Y);//En esta validacion, hacemos un posible estimado de que tan grande//podria ser nuestra entrada, y segun los requisitos de nuestra maquina//restringir un tamaño en especifico.if(len_X + len_Y +len_X*8 + (len_X*len_Y)*2 > 2147483648){

printf("Ocurrio un error de desbordamiento de datos");return 0;

}//Declaramos la matriz de la forma **c, para que sea dinámica.//Permitiendo manejadar filas y columnas de tamaño variable.unsigned short int **c;//definimos la filaint fila;//Asignamos memoria dinamicamente, que sera igual a [tam(x) +1]

c = malloc(sizeof(unsigned short int*)*(len_X+1));for(fila = 0; fila < len_X+1; fila++){

//Aqui por asignamos [tam(x) + 1][tam(y) +1] -> por eso el ciclo,

//a cada [tam(x) + 1][0...tam(y)+1]c[fila] = malloc(sizeof(unsigned short int)*(len_Y+1));

}LCS_procesar(X,Y,c);LCS_mostrar(c,X,len_X,len_Y);printf("\n\n");printf("El tamano de la subsecuencia comun mas larga es: %d\

n",c[len_X][len_Y]);for(fila = 0; fila < len_X+1; fila++){

free(c[fila]);}free(c);free(X);free(Y);

return 0;}

//Leer un archivo que contiene cadena de caracteres,//se ignoraran nuevas lineas y espacios.char* LCS_leer(char *infile){ FILE *FIN; char c = 'n'; char *S = NULL; char *T; int count = 0;

if((FIN = fopen(infile,"r"))==NULL){ printf("Ocurrio un problema abriendo el archivo: %s\n",infile);

Page 7: Programación Paralela

exit(0); }

while(( c = fgetc(FIN))!= EOF){ if ( c != '\n'){ count++; //se ensancha, una forma de decirlo la cadena de caracteres dinamicamente. T = (char *) realloc((void *) S, (count+1)*sizeof(char)); if(T != NULL){ S = T; S[count-1] = c; S[count]='\0'; } else{ free(S); printf("Error de (re) asignacion de memoria para guardar la cadena en %s\n",infile); exit(1); } } } fclose(FIN); return S;}

void LCS_procesar(char *x, char *y, unsigned short int **c){ int fila, columna; int len_X = (int) strlen(x); int len_Y = (int) strlen(y);

//Hacemos 0's la primera columna y la primera fila for(fila = 0; fila <= len_X; fila++){ c[fila][0] = 0; } for(columna = 1; columna <= len_Y; columna++){ c[0][columna] = 0; }

for(fila = 1; fila <= len_X; fila++){ for(columna = 1; columna <= len_Y; columna++){ //Rellenar c[fila][columna] con el valor apropiado if(x[fila-1] == y[columna-1]){ c[fila][columna] = c[fila-1][columna-1]+1; } else{ c[fila][columna] = MAX(c[fila-1][columna],c[fila][columna-1]); } } }

Page 8: Programación Paralela

return;}void LCS_mostrar(unsigned short int **c,char *x,int i, int j){ if(i == 0 || j == 0) return; if((c[i][j] - 1 == c[i-1][j - 1]) && (c[i][j] - 1 == c[i-1][j ]) && (c[i][j] - 1 == c[i ][j - 1])){ LCS_mostrar(c,x,i-1,j-1); printf("%c",x[i-1]); } else if(c[i][j] == c[i-1][j]) LCS_mostrar(c,x,i-1,j); else LCS_mostrar(c,x,i,j-1); return;

}Este código fue escrito y probado en un entorno Linux Ubuntu 14.04,

teniendo como compilador base GCC. Con el IDE CODEBLOCKS, y se deberá usar la siguientes líneas para su ejecución.

- gcc LCS.c –o LCS - ./LCS file1.txt file2.txt

Si no dispone de este método, coloque directamente en el código las rutas de los archivos. Ambos archivos son de lectura (de entrada) y se muestra la respuesta por la pantalla.

Page 9: Programación Paralela

PERFILADO DEL PROGRAMA (profiling)

Perfil Plano:

Cada muestra cuenta con 0.01 segundos.

% Acumulado TotalTiempo Segundos Seg Llamadas Llamadas

por segun-do

Llamadas por segun-do

Nombre

100 2.49 2.49 1 2.49 2.49 LCS_proce-sar

0.00 2.49 0.00 2 0.00 0.00 LCS_leer0.00 2.49 0.00 1 0.00 0.00 LCS_mostrar

Leyenda:

- %, el porcentaje de la duración total del programa utilizado por esta fun-ción.

- Acumulado, suma de los números de segundos representados por esta función y los enumerados superior a ella.

- Segundos, el numero de segundos representados por esta función sola-mente. Esto es una especie de listado importante.

- Llamadas, el número de veces que esta función fue invocada, si esta fun-ción no fue invocada, no se perfila y queda en blanco.

- Tiempo / Llamadas, El número medio de milisegundos en esta función llamada por llamada, s esta función se perfila.

- Total, el número medio de milisegundos empleados en esta función de llamada y sus descendientes por llamada.

- Nombre, el nombre de la función. Este es el ítem de menor importancia para este listado. El índice muestra la ubicación de la función en el lista-do gprof.

Page 10: Programación Paralela

Grafo de Llamadas:

Granularidad: Para cada muestra abarca 2 byte(s) para 0.40% de 2.49 segundos.

Índice % Tiem-po

Hijos(propagación) Llamadas Nombre

2.49 0.00 1/1 Main [2][1] 100.0 2.49 0.00 1 LCS_procesar [1]

<espontaneo>[2] 100.0 0.00 2.49 Main [2]

2.49 0.00 1/1 LCS_procesar [1][1]

0.00 0.00 2/2 LCS_leer [3]0.00 0.00 1/1 LCS_mostrar [4]

[4]0.00 0.00 2/2 Main [2]

[3] 0.0 0.00 0.00 2 LCS_leer [3]13537 LCS_mostrar [4]

0.00 0.00 1/1 Main [2][4] 0.00 0.00 1 + 13537 LCS_mostrar [4]

13537 LCS_mostrar[4]

Esta tabla describe el árbol de llamadas del programa, y fue ordenada por la cantidad total de tiempo invertido en cada función y sus hijos.

Cada entrada en esta tabla se compone de varias líneas. La línea con el nú-mero de índice en el margen izquierdo enumera la función actual. Las líneas ante-riores se enumeran las funciones que llaman a esta función, y las líneas de abajo se enumeran las funciones de este llamado. Estas líneas se listan:

- Índice, un número único asignado a cada elemento de la tabla. Los nú-meros están ordenados numéricamente por los índices. El número de ín-dice se imprime al lado de cada nombre de la función de modo que sea más fácil mirar hacia arriba cuando la función está en la tabla.

- Tiempo, este es el tiempo total que se gastó en esta función y sus hijos, tenga en cuenta que debido a los diferentes punto de vista, las funciones excluidas no se suman al 100%.

- Hijos, esta es la cantidad total de tiempo que se propaga en esta función por sus hijos.

- Llamadas, este es el número de veces que la función sea llamada. Si la función se llamó de forma recursiva, y es seguido por un + y el número de llamadas recursivas.

- Nombre, el nombre de la función actual. El número de clasificación es impreso después de ella. Esta función es un miembro de un ciclo, el ciclo numérico es impreso entre los nombres de las funciones y el índice.

Page 11: Programación Paralela

Para los padres de las funciones, los campos tienen los siguientes significados:

- Esta es la cantidad de tiempo que se propaga directamente de la función padre.

- Hijos, esta es la cantidad de tiempo que se propaga a partir de los hijos de la función en este padre.

- Llamadas, este es el número de veces que este padre llama a la función, cabe resaltar que el número total de veces que la función fue llamada. Las llamadas recursivas a la función no son incluido en el número después de la /.

- Nombre, este es el nombre del padre, Índice de los padres estará impreso después de ella. Si el padre es un miembro de un ciclo, el número de ciclo se imprime entre el nombre y el número de índi-ce.

Si los padres de la función no se pueden determinar, la palabra “espontá-nea” se imprime en el nombre del campo, y todos los demás campos estarán en blanco.

Para los hijos de las funciones, los campos tienen los siguientes significados:

- Hijos (propagación), Esta es la cantidad de tiempo que se propaga directamente desde el hijo desde la función.

- Esto se llama el número de veces que la función llama a este hijo, el número total de veces que el hijo fue llamado. Las llamadas re-cursivas por el hijo no son enumerados en el número después de ‘/’.

- Nombre, este es el nombre del hijo. Índice del hijo está impreso después del número de ella. Si el niño es un miembro de un ciclo, el número de ciclo se imprime entre el nombre y el número de ín-dice.

Si hay algún ciclo en el gráfico de llamadas, hay una entrada para el “ciclo como un todo”. Esta entrada muestra que llamó al ciclo (como su padre) y los miembros del ciclo (como los hijos). La línea + llamadas recursivas muestra el nú-mero de llamadas a funciones que eran interna para el ciclo, y la entrada de llama-das para cada miembro de muestra, para ese miembro, ¿cuántas veces se le llamó de otros miembro del ciclo?

Índice del nombre de las funciones: [3] LCS_leer - [4] LCS_mostrar - [1] LCS_pro-cesar

Se pudo observar que en el siguiente análisis de perfilado la función LCS_procesar, consumió gran cantidad de recursos, por ende es muy factible para realizarse su paralelización. En un tiempo de 2.49 s, para el caso de prueba de este ejercicio, se conto con 8665 y 8504 caracteres por cadena respectivamente, gene-rando una matriz de 7.3704.330 posiciones. Y con un tiempo de acceso cuadrático O (N2).

Page 12: Programación Paralela

Otro detalle a resaltar es la cantidad de veces que se llamó a la función LCS_mostrar, con una cantidad de sub-llamadas a ella misma, de 13537 veces. La explicación de por qué ocurre este fenómeno está en que es una función recursiva. Por ello se obtiene este valor, una implementación más trivial causaría más espacio en memoria y un rendimiento de recorrido un poco mejor. Pero en este caso se es-cogió sacrificar rendimiento, por utilización de espacio. Ya que se pretende a pos-teriori realizar una implementación paralela de dicho algoritmo.

Page 13: Programación Paralela

ALGORITMO SECUENCIAL

Procesamiento Diagonal.#include <string.h>#include <stdio.h>#include <stdlib.h>#define MAX(x, y) (((x) >= (y)) ? (x) : (y))#define MIN(x, y) (((x) <= (y)) ? (x) : (y))

void LCS_procesar(char *x, char *y, unsigned short int **c);void LCS_mostrar(unsigned short int **c, char *x, int i, int j);char* LCS_leer(char *infile);

int main(int argc, char** argv){

if( argc != 3 ) { printf("Porfavor use 2 archivos de lectura "); exit(0); } char *X = LCS_leer(argv[1]); char *Y = LCS_leer(argv[2]); int len_X = (int) strlen(X); int len_Y = (int) strlen(Y); if( len_X+len_Y + len_X*8 + (len_X*len_Y)*2 > 4294967296 /* 4*2^30 */ ) { printf("Desbordamiento de la memoria. Limitado solo a 4gb de ram.\n"); return 0; } unsigned short int **c; int fila; c = malloc(sizeof(unsigned short int*) * (fila+1)); for( fila = 0; fila < fila+1; fila++) c[fila] = malloc(sizeof(unsigned short int)*(len_Y+1)); LCS_procesar(X, Y, c); LCS_mostrar(c, X, len_X, len_Y); printf("\n\n"); printf("El tamano del LCS: %d\n", c[len_X][len_Y]); /* Liberando memoria :D C siempre haciendolo todo dificil */ for( fila= 0; fila < len_X+1; fila++) free(c[fila]); free(c); free(X); free(Y); return 0;}

char* LCS_leer(char *infile){ FILE *FIN; char c = 'n'; char *S = NULL; char *T; int count=0;

if( (FIN = fopen(infile, "r")) == NULL )

Page 14: Programación Paralela

{ printf("Problema de apertura con el archivo %s.\n", infile); exit(0); }

while ( (c=fgetc(FIN)) != EOF ) { if( c != '\n' ) { count++;

T = (char *) realloc((void *) S, (count+1)*sizeof(char));

if( T != NULL) { S = T; S[count-1] = c; S[count] = '\0'; } else { free(S); printf("Error (re)asignando memoria %s\n", infile); exit(1); } } }

fclose(FIN);

return S;}

Page 15: Programación Paralela

PERFILADO DEL PROGRAMA (profiling)

Cada muestra cuenta con 0.01 segundos.

% Acumulado TotalTiempo Seg Seg Llama-

dasLlamadas por segundo

Llamadas por segun-do

Nombre

100 13.75 13.75 1 13.75 13.75 LCS_pro-cesar

0.00 13.75 0.00 2 0.00 0.00 LCS_leer0.00 13.75 0.00 1 0.00 0.00 LCS_mos-

trar

- %, el porcentaje de la duración total del programa utilizado por esta fun-ción.

- Acumulado, suma de los números de segundos representados por esta función y los enumerados superior a ella.

- Segundos, el numero de segundos representados por esta función sola-mente. Esto es una especie de listado importante.

- Llamadas, el número de veces que esta función fue invocada, si esta fun-ción no fue invocada, no se perfila y queda en blanco.

- Tiempo / Llamadas, El número medio de milisegundos en esta función llamada por llamada, s esta función se perfila.

- Total, el número medio de milisegundos empleados en esta función de llamada y sus descendientes por llamada.

- Nombre, el nombre de la función. Este es el ítem de menor importancia para este listado. El índice muestra la ubicación de la función en el lista-do gprof.

Page 16: Programación Paralela

Grafo de Llamadas:

Granularidad: Para cada muestra abarca 2 byte(s) para 0.07% de 13.75 segundos.

Índice % Tiem-po

Hijos(propagación) Llamadas Nombre

13.75 0.00 1/1 Main [2][1] 100.0 13.75 0.00 1 LCS_procesar [1]

<espontaneo>[2] 100.0 0.00 13.75 Main [2]

13.75 0.00 1/1 LCS_procesar [1][1]

0.00 0.00 2/2 LCS_leer [3]0.00 0.00 1/1 LCS_mostrar [4]

[4]0.00 0.00 2/2 Main [2]

[3] 0.0 0.00 0.00 2 LCS_leer [3]13537 LCS_mostrar [4]

0.00 0.00 1/1 Main [2][4] 0.00 0.00 1 + 13537 LCS_mostrar [4]

13537 LCS_mostrar[4]

Esta tabla describe el árbol de llamadas del programa, y fue ordenada por la cantidad total de tiempo invertido en cada función y sus hijos.

Cada entrada en esta tabla se compone de varias líneas. La línea con el nú-mero de índice en el margen izquierdo enumera la función actual. Las líneas ante-riores se enumeran las funciones que llaman a esta función, y las líneas de abajo se enumeran las funciones de este llamado. Estas líneas se listan:

- Índice, un número único asignado a cada elemento de la tabla. Los nú-meros están ordenados numéricamente por los índices. El número de ín-dice se imprime al lado de cada nombre de la función de modo que sea más fácil mirar hacia arriba cuando la función está en la tabla.

- Tiempo, este es el tiempo total que se gastó en esta función y sus hijos, tenga en cuenta que debido a los diferentes punto de vista, las funciones excluidas no se suman al 100%.

- Hijos, esta es la cantidad total de tiempo que se propaga en esta función por sus hijos.

- Llamadas, este es el número de veces que la función sea llamada. Si la función se llamó de forma recursiva, y es seguido por un + y el número de llamadas recursivas.

- Nombre, el nombre de la función actual. El número de clasificación es impreso después de ella. Esta función es un miembro de un ciclo, el ciclo numérico es impreso entre los nombres de las funciones y el índice.

Page 17: Programación Paralela

Para los padres de las funciones, los campos tienen los siguientes significados:

- Esta es la cantidad de tiempo que se propaga directamente de la función padre.

- Hijos, esta es la cantidad de tiempo que se propaga a partir de los hijos de la función en este padre.

- Llamadas, este es el número de veces que este padre llama a la función, cabe resaltar que el número total de veces que la función fue llamada. Las llamadas recursivas a la función no son incluido en el número después de la /.

- Nombre, este es el nombre del padre, Índice de los padres estará impreso después de ella. Si el padre es un miembro de un ciclo, el número de ciclo se imprime entre el nombre y el número de índi-ce.

Si los padres de la función no se pueden determinar, la palabra “espontá-nea” se imprime en el nombre del campo, y todos los demás campos estarán en blanco.

Para los hijos de las funciones, los campos tienen los siguientes significados:

- Hijos (propagación), Esta es la cantidad de tiempo que se propaga directamente desde el hijo desde la función.

- Esto se llama el número de veces que la función llama a este hijo, el número total de veces que el hijo fue llamado. Las llamadas re-cursivas por el hijo no son enumerados en el número después de ‘/’.

- Nombre, este es el nombre del hijo. Índice del hijo está impreso después del número de ella. Si el niño es un miembro de un ciclo, el número de ciclo se imprime entre el nombre y el número de ín-dice.

Page 18: Programación Paralela

GRAFO DE DEPENDENCIAS

Grafo de dependencias del MAIN

int main(int argc, char** argv){ C1: if( argc != 3 ) { I1: printf("Porfavor use 2 archivos de lectura "); I2: exit(0); } I3: char *X = LCS_leer(argv[1]); I4: char *Y = LCS_leer(argv[2]); I5: int len_X = (int) strlen(X); I6: int len_Y = (int) strlen(Y); C2: if( len_X+len_Y + len_X*8 + (len_X*len_Y)*2 > 4294967296 /* 4*2^30 */ ) { I:7 printf("Desbordamiento de la memoria. Limitado solo a 4gb de ram.\n"); I:8 return 0; } I9: unsigned short int **c; I10: int fila; I11: c = malloc(sizeof(unsigned short int*) * (len_X+1)); I:12 for( fila = 0; fila < len_X+1; fila++) I13: c[fila] = malloc(sizeof(unsigned short int)*(len_Y+1)); F1: LCS_procesar(X, Y, c); F2: LCS_mostrar(c, X, len_X, len_Y); I14: printf("\n\n"); I15: printf("El tamano del LCS: %d\n", c[len_X][len_Y]); /* Liberando memoria :D C siempre haciendolo todo dificil */ I16: for( fila= 0; fila < len_X+1; fila++) I17: free(c[fila]); I18: free(c); I19: free(X); I20: free(Y); I21: return 0;}

Page 19: Programación Paralela

Grafo de Dependencia LCS_leer

char* LCS_leer(char *infile){ I1: FILE *FIN; I2: char c = 'n'; I3: char *S = NULL; I4: char *T; I5: int count=0; C1: if( (FIN = fopen(infile, "r")) == NULL ) { I6: printf("Problema de apertura %s.\n", infile); I7: exit(0); } I8: while ( (c=fgetc(FIN)) != EOF ) { C2: if( c != '\n' ) { I9: count++; I10: T = (char *)realloc((void *)S, (count+1)*sizeof(char)); C3: if( T != NULL) { I11: S = T; I12: S[count-1] = c; I13: S[count] = '\0'; } C4: else { I14: free(S); I15: printf("Error (re)asignando %s\n", infile); I16: exit(1); } } }

I17: fclose(FIN); I18: return S;}

Page 20: Programación Paralela

Grafo de Dependencia LCS_procesar

void LCS_procesar(char *x, char *y, unsigned short int **c){I1: int fila;I2: int columna;I3: int diagonal;I4: int len_X = (int) strlen(x);I5: int len_Y = (int) strlen(y);I6: for( fila = 0; fila <= len_X; fila++ ) I7: c[fila][0] = 0;I8: for( columna = 1; columna<= len_Y; columna++ ) I9: c[0][columna] = 0;I10: for(diagonal = 2; diagonal <= len_X+len_Y; diagonal++){ I11: for(fila = MIN(len_X, diagonal-1); fila >= MAX(1, diagonal-len_Y);fila--){ I12: columna = diagonal - fila; C1: if(x[fila-1] == y[columna-1]) { I13: c[fila][columna] = c[fila-1][columna-1] + 1; } C2: else { I14: c[fila][columna] = MAX(c[fila-1][columna],c[fila][columna-1]); } }} I15 return;}

Page 21: Programación Paralela

Grafo de Dependencia LCS_mostrar

void LCS_mostrar(unsigned short int **c, char *x, int i, int j){ c1: if( i == 0 || j == 0 ) I1: return;

C2: if( (c[i][j]-1 == c[i-1][j-1]) &&(c[i][j]-1 == c[i-1][j ]) && (c[i][j]-1 == c[i ][j-1]) {

I2: LCS_mostrar(c, x, i-1, j-1); I3: printf("%c", x[i-1]); } C3: else if( c[i][j] == c[i-1][j] ) I4: LCS_mostrar(c, x, i-1, j); C4: else I5: LCS_mostrar(c, x, i, j-1);

C5: return;}

Page 22: Programación Paralela

ALGORITMO PARALELO

#include <mpi.h>#include <string.h>#include <stdio.h>#include <stdlib.h>#define MAX(x, y) (((x) >= (y)) ? (x) : (y))#define MIN(x, y) (((x) <= (y)) ? (x) : (y))//Estructura que contendrá la información y argumentos para el LCS typedef struct LCS { char *x; //string x char *y; //string y short int **c; //Matriz int m; // tamaño de filas int n; //tamaño de columnas }LCS_T; MPI_Status status; //el status, sirve para comunicación directas e indirectas. void LCS_procesar(LCS_T *, int, int); //Arma la matriz del LCS char* LCS_leer(char *);//Lee de un archivo el flujo de datos void distribuir_datos(LCS_T *, char *, int, int);//Se aplica la distribución de datos a los procesadores. void LCS_mostrar(short int **c,char *x, int i, int j); // Se muestra el LCS.int main(int argc, char** argv){ //Cuando se trabaja con MPI, //es necesario tener identificadores por proceso. int totalprocesos, miproc; LCS_T LCS_info; //Meta Contenedor Global. char* string1; MPI_Init(&argc, &argv); //Iniciamos el entorno MPI MPI_Comm_size(MPI_COMM_WORLD, &totalprocesos); //obtenemos el numero de procesos MPI_Comm_rank(MPI_COMM_WORLD, &miproc); //obtenemos el identificador único de ese proceso if( miproc == 0 ) { //Se tomara al proceso 0, como al proceso raíz. if( argc != 3 ) { printf("Utilizar archivos externos para cargar los datos.\n"); printf("Ejemplo: %s file1 file2\n", argv[0]); MPI_Abort(MPI_COMM_WORLD,0); //Se aborta el entorno MPI; } //se lee desde el archivo string1 = LCS_leer(argv[1]); LCS_info.y = LCS_leer(argv[2]); //se obtiene el tamano de filas y columnas LCS_info.m = (int) strlen(string1); LCS_info.n = (int) strlen(LCS_info.y); } //Se usara Broadcast para y & Scatter para x distribuir_datos(&LCS_info, string1, miproc, totalprocesos); //Crear e inicializar las matrices. int i; LCS_info.c = malloc(sizeof(int*)*LCS_info.m+1); for(i = 0; i <= LCS_info.m; i++) { LCS_info.c[i] = malloc(sizeof(int)*LCS_info.n+1); LCS_info.c[i][0] = 0; // La primera columna es 0

Page 23: Programación Paralela

} if (miproc == 0) { for (i = 0; i <= LCS_info.n; i++) LCS_info.c[0][i] = 0; // La primera fila es 0 } LCS_procesar( &LCS_info, miproc, totalprocesos ); if( miproc == totalprocesos - 1 ) printf("Length of LCS: %d\n", LCS_info.c[LCS_info.m][LCS_info.n]); if( miproc == 0 ) {

LCS_mostrar(LCS_info.c, LCS_info.x, LCS_info.m, LCS_info.n); printf("\n\n"); printf("Numero de Procesos: %d\n", totalprocesos); } MPI_Finalize(); return 0;}

char* LCS_leer(char *infile) { FILE *FIN; char c = 'n'; char *S = NULL; char *T; int count=0; int buffsize=0; int totalprocesos; int i; MPI_Comm_size(MPI_COMM_WORLD, &totalprocesos); if( (FIN = fopen(infile, "r")) == NULL ) { printf("Problemas de apertura al archivo: %s.\n", infile); MPI_Abort(MPI_COMM_WORLD,0); } while ( (c=fgetc(FIN)) != EOF ) { if( c != '\n' ) { /* Ignore new lines & EOF */ count++; if( count >= buffsize ){ buffsize += totalprocesos; T = (char *) realloc((void *) S, buffsize*sizeof(char)); if( T != NULL) { S = T; memset(&S[buffsize-totalprocesos], '\0', totalprocesos); } else { free(S); printf("Error ( re) asignación de memoria para almacenar en cadena %s\n", infile); MPI_Abort(MPI_COMM_WORLD,0); } } S[count-1] = c; } } fclose(FIN); return S;}

Page 24: Programación Paralela

/******************************************************************* Distribuir los datos necesarios a los procesos *******************************************************************/void distribuir_datos(LCS_T *LCS_info, char* string1, int miproc, int totalprocesos ) { //Se envia el tamaño de columnas a todos los procesadores. MPI_Bcast( &LCS_info->n, 1, MPI_INT, 0, MPI_COMM_WORLD); //si es el procesador principal, asignamos dinamicamente el tamano de y igual al tamano de filas. if( miproc!= 0 ) LCS_info->y = malloc(LCS_info->n*sizeof(char)); //Pasamos y a todos los procesadores MPI_Bcast( LCS_info->y, LCS_info->n, MPI_CHAR, 0, MPI_COMM_WORLD ); //Pasamos las filas a los procesadores. MPI_Bcast( &LCS_info->m, 1, MPI_INT, 0, MPI_COMM_WORLD); //Hacemos el balanceo de carga int intervalo = (totalprocesos + LCS_info->m - LCS_info->m % totalprocesos) / totalprocesos; LCS_info->x = malloc((intervalo+1)*sizeof(char)); bzero(LCS_info->x, intervalo+1); //establece los primeros n bytes del area a patir de de s a 0. //Distribuir el mismo numero de elementos a cada procesador, se requiere que sea intervalo divisible por el total de procesos. MPI_Scatter( string1, intervalo, MPI_CHAR, LCS_info->x, intervalo, MPI_CHAR, 0, MPI_COMM_WORLD); //Se toma el nuevo tamaño de filas. LCS_info->m = (int) strlen(LCS_info->x); return;}void LCS_diagonal(int k, LCS_T *LCS_info){ //El core bastante igual al secuencial, solo que aqui el trabajo se realizara a traves de diagonales. //Para trabajar mejor los bloques. int i,j; int start = MAX(1,k-LCS_info->n); int finish = MIN(LCS_info->m,k-1); for( i = start; i <= finish; i++ ) { j = k - i; if(LCS_info->x[i-1] == LCS_info->y[j-1]) LCS_info->c[i][j] = LCS_info->c[i-1][j-1] + 1; else { if( LCS_info->c[i-1][j] >= LCS_info->c[i][j-1] ) LCS_info->c[i][j] = LCS_info->c[i-1][j]; else LCS_info->c[i][j] = LCS_info->c[i][j-1]; } }}

void LCS_procesar(LCS_T *LCS_info, int miproc, int totalprocesos ){ int diag, col;// printf("N: %d\n", LCS_info->n); /* For each diagonal */ for(diag = 2; diag <= LCS_info->m+LCS_info->n; diag++) { /* Receive value of first row to myid+1 */

Page 25: Programación Paralela

if (miproc != 0 && diag-1 <= LCS_info->n ) { MPI_Recv(&LCS_info->c[0][diag-1], 1, MPI_INT, miproc-1, 0, MPI_COMM_WORLD,&status);

} /* Populate diagonal k of process's c matrix */ LCS_diagonal(diag, LCS_info); /* Send value last row to myid-1 */ if (miproc!= totalprocesos-1 && diag > LCS_info->m ) { col = diag - LCS_info->m; MPI_Send(&LCS_info->c[LCS_info->m][col], 1, MPI_INT, miproc+1, 0, MPI_COMM_WORLD);// printf("Process %d sent c[%d][%d] = %d\n", myid, LCS_info->m, col, LCS_info->c[LCS_info->m][col]); } } return;}

void LCS_mostrar(short int **c,char *x,int i, int j){ if(i == 0 || j == 0) return; if((c[i][j] - 1 == c[i-1][j - 1]) && (c[i][j] - 1 == c[i-1][j ]) && (c[i][j] - 1 == c[i ][j - 1])){ LCS_mostrar(c,x,i-1,j-1); printf("%c",x[i-1]); } else if(c[i][j] == c[i-1][j]) LCS_mostrar(c,x,i-1,j); else LCS_mostrar(c,x,i,j-1); return;}

Page 26: Programación Paralela

CONCLUSIONES

Durante la ejecución de este proyecto, se observo con gran agrado las poten-cialidades que tiene este nuevo paradigma de la multiprogramación. En donde una gran responsabilidad de las cosas recae en los programadores, como lo es gestio-nar los datos, analizando sus dependencias, observar su congruencia, estudiar las distintas topologías físicas que permiten la comunicación de los procesos. Esto con el fin de optimizar el código y aprovechar en su totalidad las nuevas características hardware.

El LCS “Longest Common Substring ” “Subsecuencia común más larga” es de los problemas en donde se necesita un gran estudio de sus dependencias y del comportamiento secuencial del mismo. Ya que como se observo en el código des-crito con anterioridad, el procesamiento era complicado resolverlo a través de sus filas, puesto que la dependencia imposibilitaba en gran medida la repartición equi-tativa de las tareas. Por esto se opto a una nueva forma, “procesando sus diagona-les”. Aquí se minimizó en gran parte esta dependencia, a sacrificio de un poco de tiempo. De 2.47 segundos aproximadamente en el secuencial por filas, llegamos a 13s aproximadamente en el secuencial por diagonales.

A la hora de paralelizarlo, basándome en el grafo de dependencias. Conclui-mos que era factible y se llevo a cabo dicha labor. Se escogió la función LCS_proce-sar para la realización de la paralización, ya que LCS_mostrar se dificultaba su pa-ralelización. Se obtuvo un tiempo relativo de 9.30 seg. Un mejoramiento del 28% de rendimiento respecto al diagonal secuencial. Y un desmejoramiento de 256% respecto a algoritmo secuencial. También cabe resaltar que las pruebas realizadas fueron en un entorno muy simple y de bajos recursos.

En un entorno con 8 núcleos y 16gb de ram, podría encontrarse con un me-jor rendimiento del algoritmo aquí desarrollado.

Page 27: Programación Paralela

BIBLIOGRAFIA

George Em Kardiadakis y Robert M. Kirby II. University Cambridge. Parallel Scientific Computing in C++ and MPI,

Francisco Hidrobo y Herbert Hoeger. Introducción a MPI (Message Passing Interface).

Wilson Eduardo Soto Forero. Estudio Comparativo de Algoritmos para el Problema de la Subsecuencia Común Más Larga Restringida. Bogotá, Junio 2010.

Jones, Neil C: An introduction to bioinformatics algorithms. MIT Press, Cam-bridge, MA, (2004). ISBN 0262101068

Navarro, Gonzalo: A Guided Tour To Approximate String Matching . ACMComputing Surveys, (2001), 33(1), pp. 31 – 88.