Вы находитесь на странице: 1из 25

PROYECTO FINAL

CONVOLUCIÓN DE IMÁGENES
PARALELIZACIÓN MPI – PTHREAD&MUTEX - HIBRIDA

AUTORES:
NÉCTOR ROSALES ORTUÑO - RAZVAN ANDREI LISMANU - JEFFERSON MAX TOMALA VILLARREAL

MULTIPROCESADORES

1
Contenido
VERSIÓN – PTHREADS&MUTEX ....................................................................... 3
conbordes.c ................................................................................................................................................................... 3
Más información ........................................................................................................................................................... 6
Pruebas de eficiencia .................................................................................................................................................... 8
Valores obtenidos ..................................................................................................................................................... 8
Comprobaciones en el terminal .................................................................................................................................... 9

VERSIÓN - MPI ............................................................................................. 10


mpi.c............................................................................................................................................................................ 11
Más información ......................................................................................................................................................... 14
Pruebas de eficiencia .................................................................................................................................................. 15
Valores obtenidos ................................................................................................................................................... 15
Comprobaciones en el terminal .................................................................................................................................. 15

VERSIÓN - HÍBRIDA....................................................................................... 17
hibrido.c ...................................................................................................................................................................... 17
Más información ......................................................................................................................................................... 21
Pruebas de eficiencia .................................................................................................................................................. 22
Valores obtenidos ................................................................................................................................................... 22
Comprobaciones en el terminal .................................................................................................................................. 23
Comprobaciones con MPI con 2,3 y 4 procesadores .............................................................................................. 23
Comprobaciones HÍBRIDO con 2,3 y 4 procesadores y con 1 hilo .......................................................................... 24
Comprobaciones HÍBRIDO con 2,3 y 4 procesadores y con 4 hilos ........................................................................ 24
Comprobaciones HÍBRIDO con 2,3 y 4 procesadores y con 8 hilos ........................................................................ 25

2
VERSIÓN – PTHREADS&MUTEX
Nuestro programa en líneas generales lee una imagen, dicha imagen es una matriz donde cada pixel corresponde a
una posición de la misma, sobre esta imagen se aplicará un filtro de desenfoque, para realizar la versión paralela, lo
que hemos realizado ha sido dividir la matriz por columnas, dividiendo el “ancho” de la imagen entre el “número de
hilos” que quisiéramos generar, los parámetros que recogeremos para procesar la imagen “Final” son el nombre de
la imagen, el número de hilos y el tamaño del núcleo o filtro. El resultado que obtenemos será una imagen con la
misma resolución que la original, pero dependiendo del tamaño del núcleo aplicado esta imagen será más borrosa o
menos como veremos más detalladamente a continuación:

conbordes.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pgm.h"
#include <pthread.h>

// gettimeofday
#include <sys/time.h>

// funcion para simplicar el proceso de obtener el instante de tiempo


double get_wall_time(){
struct timeval time;
if (gettimeofday(&time,NULL)){
// Handle error
return 0;
}
return (double)time.tv_sec + (double)time.tv_usec * .000001;
}

/*Declaramos las variables necesarias*/


/*Variable necesaria para realizar la exclusión mutua en el acceso a las variables
globales*/
pthread_mutex_t exc_mut;

/*Creamos las variables necesarias*/


unsigned char** Original; //matriz de valores char entre 0 y 255 para la img original
unsigned char** Salida; //para la img resultante despues de aplicar el filtro(nucleo)
int **nucleo; //filtro que aplicaremos a la img Original
unsigned char** Final;

//Creación de estructuras datos


typedef struct{
int ini_col;
int fin_col;
int ini_rows;
int fin_rows;
int alto_;
int dim_nucleo;
}datos;

/*Función hebrada donde generamos la convolución de la imagen aplicando el filtro


*a la estructura que le pasemos por parametro, en este caso la img original cuya
* estructura viene dada por ini_col (inicio de la columna), fin_col (final de la
columna)
* , ini_rows (inicio de la fila), fin_rows (final de la fila), el alto_ (altura de la
img),
* y por último dim_nucleo (dimensión del nucleo), estos 2 últimos no cambian*/
void *convolucion(void *estructura) {

3
int y, suma, i, j, x, k = 0;
datos *p = (datos*)estructura;
int ini = p->ini_col;
int fin = p->fin_col;
int ini_alto = p->ini_rows;
int fin_alto = p->fin_rows;
int alto = p->alto_;
int dim = p->dim_nucleo;
/*Hallamos el valor de k a través del nucleo generado*/
for (i = 0; i < dim; i++)
for (j = 0; j < dim; j++)
k = k + nucleo[i][j]; //para un nucleo de 3x3 k = -7, para 5x5 k = -22...
/*Ahora calculamos el inicio y fin de los ejes "x" e "y" dependiendo de la
dimensión del nucleo
* para determinar el recorrido donde aplicaremos el filtro*/
if(ini == 0){ ini += (dim-2);}
if(fin == alto){ fin -= (dim-2);}
if(ini_alto == 0){ ini_alto += (dim-2);}
if(fin_alto == alto){ fin_alto -= (dim-2);}

/*Recorremos la estructura de la img según los valores calculados anteriormente */


for (x = ini; x<fin; x++){
for (y = ini_alto; y <fin_alto; y++){
suma = 0;
/*Guardamos en suma el valor del resultado que obtenemos al aplicar el
nucleo*/
for (i = 0; i < dim; i++){
for (j = 0; j < dim; j++){
suma = suma + Original[(x-1)+i][(y-1)+j] * nucleo[i][j];
}
}
/*Guardamos en Salida el valor del resultado anterior/k,
* de esta manera hacemos que el pixel pierda nitidez y veamos
* una imagen más borrosa al final*/
if(k==0){
Salida[x][y] = suma;
}else{
Salida[x][y] = suma/k;
}
}
}
/* Guardamos en la variable Final[x][y](variable global) los valores
* obtenidos en Salida[x][y], para ello tenemos que hacer
* exclusión mutua sobre el acceso a la variable Salida[x][y]*/
pthread_mutex_lock(&exc_mut);
for (x = ini; x<fin; x++){
for (y = ini_alto; y <fin_alto; y++){
Final[x][y]=Salida[x][y];
}
}
pthread_mutex_unlock(&exc_mut);
/*finalizamos el hilo*/
pthread_exit(NULL);
}

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

// Variables para almacenar los instantes de tiempo


double start, end;
// primer instante de tiempo, start
start = get_wall_time();

int ancho_img, alto_img;


int i, j, numeroHilos, nucleo_size;
char nombre[100];
pthread_t *threads;

4
pthread_attr_t attr;

/*comprobamos los parametros de entrada*/


if (argc != 4)
{
printf ("\nError, debe introducir el nombre de la img y 2 números, ejemplo:");
printf ("\n./conbordes nombre_img numero_procesos nucleo_size");
exit (1);
}

/*guardo el parametro de entrada en variable*/


sscanf (argv[1], "%s", &nombre[0]);
sscanf (argv[2], "%d", &numeroHilos);
sscanf (argv[3], "%d", &nucleo_size);

if(nucleo_size%2==0 || nucleo_size<3){
printf ("\nError, debe introducir un tamaño de nucleo con valor impar y mayor o
igual a 3:");
printf ("\n./conbordes numero_procesos nucleo_size\n");
exit (1);
}

/*Inicializamos las variales globales definidas anteriormente*/


Original = pgmread(nombre, &ancho_img, &alto_img); //img original
Salida = (unsigned char**)GetMem2D(ancho_img, alto_img, sizeof(unsigned char));
//img que vamos a definir aplicando el filtro
Final = (unsigned char**)GetMem2D(ancho_img, alto_img, sizeof(unsigned char));
//img que escribiremos a partir de Salida
nucleo = (int**) GetMem2D(nucleo_size, nucleo_size, sizeof(int)); //nucleo o filtro

/*Inicializamos el nucleo con valores -1 excepto la posición central con valor 1,


* dicho filtro se aplicara para crear el desenfoque que queremos obtener, a mayor
* dimensión del filtro mayor será el desenfoque*/
for (i = 0; i < nucleo_size; i++)
for (j = 0; j < nucleo_size; j++)
nucleo[i][j] = -1;
nucleo[nucleo_size/2][nucleo_size/2] = 1;

/*Reservamos un espacio de memoria para los hilos generados*/


threads = (pthread_t *) malloc(numeroHilos*sizeof(pthread_t));

/*inicializo el semaforo de exclusión mutua*/


pthread_mutex_init(&exc_mut, NULL);

/*inicializo la variable attr para hacer los hilos joinables*/


pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

datos *datosArray;
/*Reservamos un espacio de memoria para el array de la estructura,
* en la cual pasaremos los datos a cada hilo*/
datosArray = (datos *)malloc(numeroHilos*sizeof(datos));

int rc;
int inicio = 0;
int fin = 0;
int cociente = ancho_img/numeroHilos;
int resto = ancho_img%numeroHilos;
int t;

/*para cada uno de los hilos*/


for(t = 0; t < numeroHilos; t++){

/*Determinamos el inicio y fin de cada columna depeniendo el número de hilos*/


inicio = (t * cociente + (t < resto? t : resto));
fin = (inicio + cociente) + (t < resto);

5
datosArray[t].ini_col = inicio;
datosArray[t].ini_rows = 0;
datosArray[t].fin_col = fin;
datosArray[t].fin_rows = alto_img;
datosArray[t].alto_ = alto_img;
datosArray[t].dim_nucleo = nucleo_size;

//printf("hilo[%d] --> ini_x: %d, fin_x %d, ini_y: %d, fin_y %d\n", t, inicio,
fin, 0, alto_img);

/*creamos el hilo y comprobamos si hay errores*/


rc = pthread_create(&threads[t], &attr, convolucion, (void *)&datosArray[t]);
if (rc)
{
printf ("ERROR; return code from pthread_create() is %d\n", rc);
exit (-1);
}
}

/*Para continuar será necesario esperar a que acaben todos los hilos*/
for(t = 0; t < numeroHilos; t++) {
rc = pthread_join(threads[t], NULL);
}
/*Generamos el archivo "lena_procesada1.pgm" a traves de Salida*/
pgmwrite(Final, "lena_procesada_pthread&mutex.pgm", ancho_img, alto_img);

// Segundo instante de tiempo, end


end = get_wall_time();

// Se calcula la diferencia y se muestra por pantalla.


printf("time: %.5fs\n", end-start);

/*liberamos la memoria*/
Free2D((void**) nucleo, nucleo_size);
Free2D((void**) Original, ancho_img);
Free2D((void**) Salida, ancho_img);
Free2D((void**) Final, ancho_img);
/*limpiamos y finalizamos los hilos*/
pthread_attr_destroy(&attr);
pthread_mutex_destroy(&exc_mut);
pthread_exit(NULL);
}

Más información

Como hemos mencionado anteriormente si probamos ejecutar el código anterior comprobamos que necesitamos
introducir el nombre de la imagen + un numero de procesos + un número para la dimensión del núcleo cuyo valor
deberá ser mayor o igual a 3 (en este caso el valor será para un núcleo de 3x3) como vemos en la siguiente imagen:

6
Comprobamos realizarlo para un núcleo de 5x5 y con un solo hilo:

La diferencia con el núcleo anterior de 3x3 es la siguiente:

Si vemos las imágenes que tenemos a continuación comprobamos como para aplicar un núcleo de 3x3 recorremos
toda la parte blanca dejando un borde proporcional al filtro aplicado en este caso 1px, en el caso de un núcleo de
5x5 podemos observar como el borde de la imagen aumentaría a 2px y así sucesivamente, esto lo contemplamos
para que el recorrido del núcleo no acabe solapándose con el de la imagen original.

IMAGEN DE 12X12 CON UN FILTRO DE 3X3 IMAGEN DE 12X12 CON UN FILTRO DE 5X5

7
A medida que vamos aumentando el filtro comprobamos como la imagen acaba siendo más borrosa y el borde de la
imagen acaba siendo más grueso como hemos explicado anteriormente.

Nucleo de 3x3 Nucleo de 5x5 Nucleo d 7x7 Nucleo de 11x11

Pruebas de eficiencia

Ahora vamos a realizar una


prueba de rendimiento para
varias resoluciones de la
imagen con distinto número de
procesos y un núcleo de 23x23,
en la siguiente tabla
mostramos el tiempo de
ejecución que ha tardado en
procesarse dichas imágenes
con 1,2,4 y 8 hilos cada una:

La dimensión del núcleo ha sido fijada en 23x23 para hallar estos valores y no ha sido cambiada para valorar el
rendimiento en el uso de los hilos, pero a medida que este valor lo aumentemos los tiempos también serán mayores
ya que son más iteraciones que deberíamos recorrer.

Valores obtenidos

RESOLUCIÓN (px) 1 HILO (seg) 2 HILOS (seg) 4 HILOS (seg) 8 HILOS (seg)
256x256 0,112 0,096 0,075 0,072
512x512 0,468 0,318 0,305 0,283
1024x1024 1,971 1,343 1,181 1,165

Mostramos la gráfica resultante de los valores obtenidos anteriormente:

8
Tiempo de ejecución
2,5

Tiempo(segundos)
2

1,5

0,5

0
0 1 2 3 4 5 6 7 8 9
Número de procesos

Img256x256 Img512x512 Img1024x1024

Como vemos en la gráfica anterior para una imagen de 256x256 no se aprecia la diferencia de tiempo, pero si
usamos una resolución de 1024x1024 (línea gris) comprobamos como ha disminuido considerablemente de 2
segundos con 1 hilo a casi la mitad en el uso de 4 hilos y su rendimiento ya es constante.

Comprobaciones en el terminal
Si comprobamos el rendimiento que supone generar la imagen de 512x512px con distintos valores en los hilos
aplicando un núcleo de 23x23 obtenemos los resultados vistos anteriormente:

Ahora probamos la misma imagen, pero reducida a un tamaño de 256x256px

9
Y por último calculamos los tiempos obtenidos para la imagen con una resolución de 1024x1024:

Comprobamos como a medida que aumentamos el número de hilos, se puede apreciar como el tiempo disminuye,
con lo cual empleando más hilos hacemos que todo el proceso sea más eficiente.

VERSIÓN - MPI
Para realizar la versión MPI, hemos seguido el mismo patrón anterior, dividiendo el “ancho” de la imagen entre el
“número de procesadores en este caso” -1, ya que el procesador 0 “master” se encargará solamente de recibir los
datos de los de más procesadores y guardar la imagen, los parámetros que recogeremos para procesar la imagen
“Final” son el nombre de la imagen, el número de procesadores y el tamaño del núcleo o filtro.

En esta versión hemos creado dos métodos auxiliares los cuales nos permitan pasar una matriz a un vector y
viceversa, de esta manera nos resulta más fácil a la hora de trabajar con un vector para enviar y recibir los datos a
través de MPI, pero también necesitamos una matriz para aplicar el núcleo, es por eso el uso de estos dos métodos.

El resultado que obtenemos será una imagen con la misma resolución que la original, pero dependiendo del tamaño
del núcleo aplicado, esta imagen será más borrosa o menos como hemos visto anteriormente:

10
mpi.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pgm.h"
#include "mpi.h"
#define TAG 17

// gettimeofday
#include <sys/time.h>

// funcion para simplicar el proceso de obtener el instante de tiempo


double get_wall_time(){
struct timeval time;
if (gettimeofday(&time,NULL)){
// Handle error
return 0;
}
return (double)time.tv_sec + (double)time.tv_usec * .000001;
}

/*Definimos el tipo de dato para guardar la información sobre las operaciones de


recepción de mensajes*/
MPI_Status status;
/*Creamos las variables necesarias*/
unsigned char** Original;
unsigned char** Salida;
int **nucleo;
unsigned char** Auxiliar;
unsigned char* vector;

/*Método para pasar la matriz a un vector, este método lo usaremos para poder enviar
* y recibir los datos que tenemos en MPI a través de un vector ya que el uso de una
matriz nos da fallos de memoria y de esta manera nos resulta más facil*/
void pasoMatriz (unsigned char** m, unsigned char *v, int Largo, int Alto)
{
int i,j;
int p = 0;
for (i=0; i<Largo; i++)
{
for (j=0; j<Alto; j++)
{
v[p] = m[i][j];
p++;
}
}
}
/*Método para pasar el vector a una matriz, necesitamos los datos en una matriz para
realizar la convolución y de esta manera aplicar el nucleo*/
void pasovector (unsigned char** m, unsigned char *v, int Largo, int Alto)
{
int i,j;
int p = 0;
for (i=0; i<Largo; i++)
{
for (j=0; j<Alto; j++)
{
m[i][j] = v[p];
p++;
}
}
}

11
/*Función hebrada donde generamos la convolución de la imagen donde le pasamos por
parametro
* la imagen Original, el nucleo, la imagen Salida, la posición donde comienza y
termina tanto las columnas
* como las filas, ademas de la dimensión del nucleo */
void convolucion(unsigned char** Original, int** nucleo, unsigned char** Salida, int
ini, int fin, int ini_rows, int fin_rows, int alto,int dim) {
int x, y;
int suma;
int k = 0;
int i, j;

/*Ahora calculamos el inicio y fin de los ejes "x" e "y" dependiendo de la


dimensión del nucleo
* para determinar el recorrido donde aplicaremos el filtro*/
if (ini==0){
ini+=(dim-2);
}
if(fin==alto){
fin-=(dim-2);
}
if(ini_rows==0){
ini_rows+=(dim-2);
}
if(fin_rows==alto){
fin_rows-=(dim-2);
}

/*Hallamos el valor de k a través del nucleo generado*/


for (i = 0; i < dim; i++)
for (j = 0; j < dim; j++)
k = k + nucleo[i][j];

/*Recorremos la estructura de la img según los valores calculados anteriormente */


for (x = ini; x <fin; x++){
for (y = ini_rows; y <fin_rows; y++){
suma = 0;
/*Guardamos en suma el valor del resultado que obtenemos al aplicar el nucleo*/
for (i = 0; i < dim; i++){
for (j = 0; j < dim; j++){
suma = suma + Original[(x-1)+i][(y-1)+j] * nucleo[i][j];
}
}
/*Guardamos en Salida el valor del resultado anterior/k,
* de esta manera hacemos que el pixel pierda nitidez y veamos
* una imagen más borrosa al final*/
if(k==0)
Salida[x][y] = suma;
else
Salida[x][y] = suma/k;
}
}
}
int main(int argc, char *argv[]){

// Variables para almacenar los instantes de tiempo


double start, end;
// primer instante de tiempo, start
start = get_wall_time();

/*Declaramos las variables necesarias*/


int Largo, Alto;
int i, j,y,x;
char nombre[100];
int rank,size;
int inicio,fin;

12
int nucleo_size;

/*guardo los parametros de entrada en variables*/


sscanf (argv[1], "%s", &nombre[0]); //nombre img
sscanf (argv[2], "%d", &nucleo_size); //tamaño del nucleo

if(nucleo_size%2==0 || nucleo_size<3){
printf ("\nError, debe introducir un nommbre de imagen y un tamaño de nucleo
con valor impar y mayor o igual a 3:");
printf ("\nmpiexec -n número_procesadores mpi nombre_img nucleo_size\n");
exit(1);
}

/*Inicializamos la estructura MPI*/


MPI_Init( &argc, &argv );
/*Determinamos el tamaño del número de procesos*/
MPI_Comm_size( MPI_COMM_WORLD, &size);
/*Determinamos el rango (identificador) del proceso que lo llama*/
MPI_Comm_rank( MPI_COMM_WORLD, &rank);

/*Inicializamos las variales globales definidas anteriormente */


Original = pgmread(nombre, &Largo, &Alto);
/*Reservamos la memoria necesaria para cada matriz y vector*/
Salida = (unsigned char**)GetMem2D(Largo, Alto, sizeof(unsigned char));
Auxiliar = (unsigned char**)GetMem2D(Largo, Alto, sizeof(unsigned char));
vector = (unsigned char *) malloc (sizeof(unsigned char) * Largo * Alto);
nucleo = (int**) GetMem2D(nucleo_size, nucleo_size, sizeof(int));

/*Inicializamos el nucleo con valores -1 excepto la posición central con valor 1,


* dicho filtro se aplicara para crear el desenfoque que queremos obtener
* (a mayor dimensión del filtro mayor será el desenfoque)*/
for (i = 0; i < nucleo_size; i++)
for (j = 0; j < nucleo_size; j++)
nucleo[i][j] = -1;
nucleo[nucleo_size/2][nucleo_size/2] = 1;

int ini_rows = 0;
int fin_rows = Alto;
int aux=size-1; //número de procesadores
int resto=Largo%aux;
int cociente=Largo/aux;

/*Definimos el rango de procesos que podemos ejecutar [0 - master]*/


if(rank==0){
for(i=1;i<size;i++){
MPI_Recv (vector, Largo*Alto, MPI_CHAR, i, TAG, MPI_COMM_WORLD, &status);
//printf("Proceso %d ha recibido %d\n", 0, vector);
/*pasamos el vector recibido a una matriz que guardamos en Auxiliar*/
pasovector (Auxiliar, vector, Largo, Alto);
MPI_Recv (&inicio, 1, MPI_INT, i, TAG, MPI_COMM_WORLD, &status);
//printf("Proceso %d ha recibido %d\n", 0, inicio);
MPI_Recv (&fin, 1, MPI_INT, i, TAG, MPI_COMM_WORLD, &status);
//printf("Proceso %d ha recibido %d\n", 0, fin);

/* Guardamos en la variable Salida[x][y] los valores


* obtenidos anteriormente en Auxiliar[x][y]*/
for (x = inicio; x < fin; x++){
for (y = ini_rows; y <fin_rows; y++){
Salida[x][y] = Auxiliar[x][y];
}
}
}
/*Generamos el archivo "lena_procesadaMPI.pgm" a traves de Salida*/
pgmwrite(Salida, "lena_procesadaMPI.pgm", Largo, Alto);
}else{
/*Determinamos el inicio y fin de cada columna depeniendo el número de procesadores*/

13
int aux1=rank-1;
inicio=aux1*(Largo/aux)+(aux1<resto?aux1:resto);
fin=inicio+(Largo/aux)+(aux1<resto);
/*Creamos la convolución*/
convolucion (Original, nucleo, Salida, inicio, fin, ini_rows, fin_rows,
Alto,nucleo_size);
/*pasamos la matriz Salida calculada a un vector para poder enviarlo al proceso
maestro "0"*/
pasoMatriz (Salida, vector, Largo, Alto);
//printf("Proceso %d envia el vector: %d al proceso %d\n", rank, vector, 0);
MPI_Send (vector, Largo*Alto, MPI_CHAR, 0, TAG, MPI_COMM_WORLD);
//printf("Proceso %d envia el inicio: %d al proceso %d\n", rank, inicio, 0);
MPI_Send (&inicio, 1, MPI_INT, 0, TAG, MPI_COMM_WORLD);
//printf("Proceso %d envia el fin: %d al proceso %d\n", rank, fin, 0);
MPI_Send (&fin, 1, MPI_INT, 0, TAG, MPI_COMM_WORLD);
}

// Segundo instante de tiempo, end


end = get_wall_time();

// Se calcula la diferencia y se muestra por pantalla.


printf("time: %.5fs\n", end-start);

/*liberamos la memoria*/
Free2D((void**) nucleo, nucleo_size);
Free2D((void**) Original, Largo);
Free2D((void**) Salida, Largo);
Free2D((void**) Auxiliar, Largo);
free(vector);

/*Finalizamos MPI*/
MPI_Finalize();
return 0;
}

Más información
En este caso hacemos el mismo proceso para aplicar un núcleo de distintas dimensiones como hemos realizado para
los hilos, si, por ejemplo, tenemos una imagen de 12x12 y usamos 3 procesadores, el procesador0 se encarga de recibir
los datos y guardar la imagen, por lo tanto, esta imagen sería dividida en 2 partes, una parte para el procesador1 (color
naranja) y otra para el procesador2 (color blanco). En este caso se enviaría el inicio y fin de donde empezaría y
terminaría cada procesador y el resultado de aplicar la convolución sería enviado al procesador 0.

14
Pruebas de eficiencia
Ahora vamos a realizar una prueba de rendimiento con las resoluciones de 512x512 y 1024x1024 vistas anteriormente
con distinto número de procesadores y un núcleo fijo de 23x23, en la siguiente tabla mostramos el tiempo de ejecución
medio que ha tardado en procesarse dichas imágenes con 2,3 y 4 procesadores cada una:

Cuando hacemos uso de 2 procesadores, como el proceso 0 se encarga solo de recibir los datos y guardar la imagen
solo haría la función de aplicar la convolución un procesador, el 1, por lo tanto, aunque describamos 2 procesadores
solo trabajaría 1 la convolución y así para los otros.

Valores obtenidos

RESOLUCIÓN (px) 2 PRO (seg) 3 PRO (seg) 4 PRO (seg)


512x512 0,606 0,451 0,425
1024x1024 2,272 1,766 1,546

Mostramos la gráfica resultante de los valores obtenidos anteriormente:

Tiempo de ejecución
2,5 2,272
Tiempo(segundos)

2 1,766
1,546
1,5

1 0,606
0,451 0,425
0,5

0
0 1 2 3 4 5

Número de procesadores
Img512x512 Img1024x1024

Comprobamos en la gráfica anterior la similitud con el uso de hilos, y es que cuanta más resolución tenga la imagen,
más claro se verá la diferencia en el uso de los procesadores. Si comparamos los tiempos obtenidos en el uso de
hilos y ahora con MPI comprobamos que los tiempos son muy parecidos, incluso un poco más lento en MPI debido a
que hacemos uso de más bucles anidados, pero aún así, los tiempos son muy similares.

A falta de tiempo podíamos haber hecho uso de MPI_Reduce, MPI_Scatter o MPI_Gather para mejorar su
eficiencia y comprobar como los tiempos de ejecución con MPI son menores respecto al uso de hilos.

Comprobaciones en el terminal
Si comprobamos el rendimiento que supone generar la imagen de 1024x1024px con distintos valores en los
procesadores empleados aplicando un núcleo de 23x23 obtenemos los resultados vistos anteriormente, también
podemos ver los tiempos que se han empleado en cada procesador, en la convolución y el total después de guardar
la imagen el procesador 0.

15
Probamos realizarlo con la imagen de 512x512 y los tiempos obtenidos son los siguientes:

16
VERSIÓN - HÍBRIDA
Para realizar la versión Híbrida, hemos aplicado las versiones anteriores, en este caso también dividimos el “ancho”
de la imagen entre el “número de procesadores -1, ya que el procesador 0 “master” se encargará solamente de
recibir los datos de los de más procesadores y guardar la imagen, una vez dividida la imagen por ejemplo haciendo
uso de 3 procesadores, ya sabemos que la imagen sería dividida en 2 partes y ahora cada una de estas partes será
dividida por un número de hilos que empleamos, los parámetros que recogeremos para procesar la imagen “Final”
son el nombre de la imagen, el número de procesadores, el tamaño del núcleo o filtro y por último el número de
hilos.

Como hemos mencionado anteriormente: También hemos creado dos métodos auxiliares los cuales nos permitan
pasar una matriz a un vector y viceversa, de esta manera nos resulta más fácil a la hora de trabajar con un vector
para enviar y recibir los datos a través de MPI, pero también necesitamos una matriz para aplicar el núcleo, es por
eso el uso de estos dos métodos.

El resultado que obtenemos será una imagen con la misma resolución que la original, pero dependiendo del tamaño
del núcleo aplicado, esta imagen será más borrosa o menos como hemos visto anteriormente:

hibrido.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pgm.h"
#include "mpi.h"
#define TAG 17
#include <pthread.h>

// gettimeofday
#include <sys/time.h>

// funcion para simplicar el proceso de obtener el instante de tiempo


double get_wall_time(){
struct timeval time;
if (gettimeofday(&time,NULL)){
// Handle error
return 0;
}
return (double)time.tv_sec + (double)time.tv_usec * .000001;
}

//Creación de la estructura de datos


typedef struct{
int ini_col;
int fin_col;
int dim_nucleo;
int ini_rows;
int fin_rows;
int alto;
}datos;

/*Definimos el tipo de dato para guardar la información sobre las operaciones de


recepción de mensajes*/
MPI_Status status;
/*Creamos las variables necesarias*/
unsigned char** Original;
unsigned char** Salida;
int **nucleo;
unsigned char** Auxiliar;
unsigned char* vector;
/*Método para pasar la matriz a un vector, este método lo usaremos para poder enviar

17
* y recibir los datos que tenemos en MPI a través de un vector ya que el uso de una
matriz
* nos da fallos de memoria y de esta manera nos resulta más facil*/
void pasoMatriz (unsigned char** m, unsigned char *v, int Largo, int Alto)
{
int i,j;
int p = 0;
for (i=0; i<Largo; i++)
{
for (j=0; j<Alto; j++)
{
v[p] = m[i][j];
p++;
}
}
}
/*Método para pasar el vector a una matriz, necesitamos los datos en una matriz para
realizar
* la convolución y de esta manera aplicar el nucleo*/
void pasovector (unsigned char** m, unsigned char *v, int Largo, int Alto)
{
int i,j;
int p = 0;
for (i=0; i<Largo; i++)
{
for (j=0; j<Alto; j++)
{
m[i][j] = v[p];
p++;
}
}
}
/*Función principal donde generamos la convolución de la imagen aplicando el filtro
*a la estructura que le pasemos por parametro, en este caso la img original cuya
* estructura viene dada por ini_col (inicio de la columna), fin_col (final de la
columna)
* , ini_rows (inicio de la fila), fin_rows (final de la fila), el alto_ (altura de la
img),
* y por último dim_nucleo (dimensión del nucleo), estos 2 últimos no cambian*/
void *convolucion(void *estructura) {
datos *p = (datos*)estructura;
int ini = p->ini_col;
int fin = p->fin_col;
int dim = p->dim_nucleo;
int ini_rows=p->ini_rows;
int fin_rows=p->fin_rows;
int alto=p->alto;

int y;
int suma;
int k = 0;
int i, j;
int x;

/*Inicializamos el nucleo o filtro que aplicaremos a la img*/


for (i = 0; i < dim; i++)
for (j = 0; j < dim; j++)
k = k + nucleo[i][j];

/*Ahora calculamos el inicio y fin de los ejes "x" e "y" dependiendo de la


dimensión del nucleo
* para determinar el recorrido donde aplicaremos el filtro*/
if(ini==0){ini+=(dim-2);}
if(fin==alto){fin-=(dim-2);}
if(ini_rows==0){ini_rows+=(dim-2);}
if(fin_rows==alto){fin_rows-=(dim-2);}

18
/*Recorremos la estructura de la img según los valores calculados anteriormente */
for (x = ini; x<fin; x++){
for (y = ini_rows; y<fin_rows ; y++){
suma = 0;
/*Guardamos en suma el valor del resultado que obtenemos al aplicar el
nucleo*/
for (i = 0; i < dim; i++){
for (j = 0; j < dim; j++){
suma = suma + Original[(x-1)+i][(y-1)+j] * nucleo[i][j];
}
}
/*Guardamos en Salida el valor del resultado anterior/k,
* de esta manera hacemos que el pixel pierda nitidez y veamos
* una imagen más borrosa al final*/
if(k==0){
Salida[x][y] = suma;
}else{
Salida[x][y] = suma/k;
}
}
}
/*finalizamos el hilo*/
pthread_exit(NULL);
}

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

// Variables para almacenar los instantes de tiempo


double start, end;
// primer instante de tiempo, start
start = get_wall_time();

/*Declaramos las variables necesarias*/


int Largo, Alto;
int i, j,y,x;
char nombre[100];
int rank,size,numeroHilos;
int inicio,fin;
int nucleo_size;
/*guardo los parametros de entrada en variables*/
sscanf (argv[1], "%s", &nombre[0]);
sscanf (argv[2], "%d", &nucleo_size);
sscanf (argv[3], "%d", &numeroHilos);

if(nucleo_size%2==0 || nucleo_size<3){
printf ("\nError, debe introducir un nommbre de imagen y un tamaño de nucleo
con valor impar y mayor o igual a 3:");
printf ("\nmpiexec -n número_procesadores mpi nombre_img nucleo_size\n");
exit(1);
}

/*Inicializamos la estructura MPI*/


MPI_Init( &argc, &argv );
/*Determinamos el tamaño del número de procesos*/
MPI_Comm_size( MPI_COMM_WORLD, &size);
/*Determinamos el rango (identificador) del proceso que lo llama*/
MPI_Comm_rank( MPI_COMM_WORLD, &rank);

/*Inicializamos las variales globales definidas anteriormente */


Original = pgmread(nombre, &Largo, &Alto);
/*Reservamos la memoria necesaria para cada matriz y vector*/
Salida = (unsigned char**)GetMem2D(Largo, Alto, sizeof(unsigned char));
Auxiliar = (unsigned char**)GetMem2D(Largo, Alto, sizeof(unsigned char));
vector = (unsigned char *) malloc (sizeof(unsigned char) * Largo * Alto);

19
nucleo = (int**) GetMem2D(nucleo_size, nucleo_size, sizeof(int));

/*Inicializamos el nucleo con valores -1 excepto la posición central con valor 1,


* dicho filtro se aplicara para crear el desenfoque que queremos obtener
* (a mayor dimensión del filtro mayor será el desenfoque)*/
for (i = 0; i < nucleo_size; i++)
for (j = 0; j < nucleo_size; j++)
nucleo[i][j] = -1;
nucleo[nucleo_size/2][nucleo_size/2] = 1;

int ini_rows = 0;
int fin_rows = Alto;
int aux=size-1; //número de procesadores
int resto=Largo%aux;
int cociente=Largo/aux;
pthread_t *threads;
/*Reservamos un espacio de memoria para los hilos generados*/
threads = (pthread_t *) malloc(numeroHilos*sizeof(pthread_t));
pthread_attr_t attr;

/*inicializo la variable attr para hacer los hilos joinables*/


pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

datos *datosArray;
/*Reservamos un espacio de memoria para el array de la estructura,
* en la cual pasaremos los datos a cada hilo*/
datosArray = (datos *)malloc(numeroHilos*sizeof(datos));

/*Definimos el rango de procesos que podemos ejecutar [0 - master]*/


if(rank==0){
for(i=1;i<size;i++){

MPI_Recv (vector, Largo*Alto, MPI_CHAR, i, TAG, MPI_COMM_WORLD, &status);


//printf("Proceso %d ha recibido %d\n", 0, vector);

/*pasamos el vector recibido a una matriz que guardamos en Auxiliar*/


pasovector (Auxiliar, vector, Largo, Alto);

MPI_Recv (&inicio, 1, MPI_INT, i, TAG, MPI_COMM_WORLD, &status);


//printf("Proceso %d ha recibido %d\n", 0, inicio);
MPI_Recv (&fin, 1, MPI_INT, i, TAG, MPI_COMM_WORLD, &status);
//printf("Proceso %d ha recibido %d\n", 0, fin);

/* Guardamos en la variable Salida[x][y] los valores


* obtenidos anteriormente en Auxiliar[x][y]*/
for (x = inicio; x < fin; x++){
for (y = ini_rows; y <fin_rows; y++){
Salida[x][y] = Auxiliar[x][y];
}
}
}
/*Generamos el archivo "lena_procesadaMPI.pgm" a traves de Salida*/
pgmwrite(Salida, "lena_procesadaHIBRIDA.pgm", Largo, Alto);

}else{
/*Determinamos el inicio y fin de cada columna depeniendo el número de procesadores*/
int aux1=rank-1;
inicio=aux1*(Largo/aux)+(aux1<resto?aux1:resto);
fin=inicio+(Largo/aux)+(aux1<resto);
/*También será necesario calcular los datos que abarcan cada hilo/s en cada procesador*/
int rc;
int cocienteHilo = (fin-inicio)/numeroHilos;
int restoHilo = (fin-inicio)%numeroHilos;
int t,final;
int inicioHilo=0,finHilo=0;

20
/*para cada uno de los hilos...*/
for(t = 0; t < numeroHilos; t++){

/*Determinamos el inicio y fin de cada columna depeniendo el número de hilos*/


inicioHilo=inicio+t*cocienteHilo+(t < resto? t : resto);
finHilo=inicioHilo+cocienteHilo+(t<restoHilo);

datosArray[t].ini_col = inicioHilo;
datosArray[t].fin_col = finHilo;
datosArray[t].dim_nucleo=nucleo_size;
datosArray[t].ini_rows=0;
datosArray[t].fin_rows=Alto;
datosArray[t].alto=Alto;

/*creamos el hilo y comprobamos si hay errores*/


rc = pthread_create(&threads[t], &attr, convolucion, (void
*)&datosArray[t]);
if (rc)
{
printf ("ERROR; return code from pthread_create() is %d\n", rc);
exit (-1);
}
}

/*Para continuar será necesario esperar a que acaben todos los hilos*/
for(t = 0; t < numeroHilos; t++) {
rc = pthread_join(threads[t], NULL);
}

/*pasamos la matriz Salida calculada a un vector para poder enviarlo al proceso


maestro "0"*/
pasoMatriz (Salida, vector, Largo, Alto);
//printf("Proceso %d envia el vector: %d al proceso %d\n", rank, vector, 0);
MPI_Send (vector, Largo*Alto, MPI_CHAR, 0, TAG, MPI_COMM_WORLD);
//printf("Proceso %d envia el inicio: %d al proceso %d\n", rank, inicio, 0);
MPI_Send (&inicio, 1, MPI_INT, 0, TAG, MPI_COMM_WORLD);
//printf("Proceso %d envia el fin: %d al proceso %d\n", rank, fin, 0);
MPI_Send (&fin, 1, MPI_INT, 0, TAG, MPI_COMM_WORLD);
}

// Segundo instante de tiempo, end


end = get_wall_time();

// Se calcula la diferencia y se muestra por pantalla.


printf("time: %.5fs\n", end-start);

/*liberamos la memoria*/
Free2D((void**) nucleo, nucleo_size);
Free2D((void**) Original, Largo);
Free2D((void**) Salida, Largo);
Free2D((void**) Auxiliar, Largo);
free(vector);

/*Finalizamos MPI*/
MPI_Finalize();
return 0;
}

Más información
En este caso como hemos explicado anteriormente si tenemos una imagen con una resolución de 12x12 y queremos
ejecutarla con 3 procesadores (0 – “master”) , este ejecutaría la convolución en 2 de ellos, por lo tanto tenemos la
imagen dividida en 2 partes como vemos en la siguiente figura (procesador1 color naranja y procesador2 color

21
blanco) ahora si queremos ejecutar solamente 2 hilos, el procesador1 su parte la dividiría en otras 2 partes (siempre
dividimos en columnas ya que el acceso a memoria es más eficiente que realizar las divisiones por filas) como vemos
en el procesador1 el hilo0 subrayado de rojo y el hilo1 subrayado de azul, este mismo proceso haría el procesador2.

Pruebas de eficiencia
Ahora vamos a realizar una prueba de rendimiento con una resolución de 2048x2048 (lena_original4.pgm) con
distinto número de procesadores e hilos y un núcleo fijo de 23x23, las comprobaciones se han realizado en la parte
hibrida con 1,4 y 8 hilos. En la siguiente tabla mostramos el tiempo de ejecución que ha tardado en procesarse dichas
imágenes con 2,3 y 4 procesadores cada una:

Valores obtenidos

RESOLUCIÓN (px) 2 PRO (seg) 3 PRO (seg) 4 PRO (seg)


MPI 9,615 7,096 6,283
HIBRIDO con 1 hilo 10,166 6,923 6,379
HIBRIDO con 4 hilos 10,026 5,464 5,496
HIBRIDO con 8 hilos 10,031 5,376 5,293

Mostramos la gráfica resultante de los valores obtenidos anteriormente:

22
Tiempo de ejecución
12

Tiempo(segundos)
10

0
0 1 2 3 4 5

Número de procesadores
MPI HIBRIDO(1hilo) HIBRIDO(4hilos) HIBRIDO(8hilos)

Como vemos en la gráfica anterior tanto el proceso MPI e HIBRIDO con 1 hilo están en los mismos intervalos de
tiempo, pero si usamos la parte HIBRIDA con 4 y 8 hilos vemos como se hace más eficiente y en el caso de usar un
número mayor de procesadores llegaríamos a un intervalo de tiempo lineal como vemos en la gráfica.

Comprobaciones en el terminal
Si comprobamos el rendimiento que supone generar la imagen de 2048x2048px con distintos valores en los
procesadores empleados aplicando un núcleo de 23x23 obtenemos los resultados vistos anteriormente, también
podemos ver los tiempos que se han empleado en cada procesador, en la convolución y el total después de guardar
la imagen el procesador 0.

Comprobaciones con MPI con 2,3 y 4 procesadores

23
Si ejecutamos htop podemos comprobar el uso de procesadores empleados en cada proceso

Comprobaciones HÍBRIDO con 2,3 y 4 procesadores y con 1 hilo


Estos tiempos deberían ser muy similares a los de MPI ya que hacemos uso de 1 solo hilo

Comprobaciones HÍBRIDO con 2,3 y 4 procesadores y con 4 hilos


En este caso vemos como los tiempos han disminuido respecto a 1 hilo usando el mismo número de procesadores

24
Comprobaciones HÍBRIDO con 2,3 y 4 procesadores y con 8 hilos
Y por último vemos como los tiempos son algo más bajos que con 4 hilos pero la diferencia es mínima ya que nos
acercamos a un punto lineal como hemos mencionado anteriormente por el uso de los procesadores.

25

Вам также может понравиться