Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Ejercicios Resueltos

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 31

Coleccin de ejercicios

Computacin Paralela Grado en Ingeniera Informtica (ETSInf)

ndice
1. Memoria Compartida y OpenMP 1
1.1. Paralelismo de bucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2. Regiones paralelas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3. Sincronizacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4. Tareas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

2. Paso de Mensajes y MPI 7


2.1. Comunicacin punto a punto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2. Comunicacin colectiva . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.3. Tipos de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.4. Comunicadores y topologas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

1. Memoria Compartida y OpenMP


1.1. Paralelismo de bucles
Cuestin 1.11
La infinito-norma de una matriz A Rnn se define como el mximo de las sumas de los valores absolutos
de los elementos de cada fila:
n1
X
kAk = max |ai,j |
i=0,...,n1
j=0

El siguiente cdigo secuencial implementa dicha operacin para el caso de una matriz cuadrada.
#include <math.h>
#define DIMN 100

double infNorm(double A[DIMN][DIMN], int n)


{
int i,j;
double s,norm=0;

for (i=0; i<n; i++) {


s = 0;
for (j=0; j<n; j++)
s += fabs(A[i][j]);
if (s>norm)
norm = s;
}
return norm;
}
(a) Realiza una implementacin paralela mediante OpenMP de dicho algoritmo. Justifica la razn por
la que introduces cada cambio.
Solucin:

#include <math.h>
#define DIMN 100

double infNorm(double A[DIMN][DIMN], int n)


{
int i,j;
double s,norm=0;

#pragma omp parallel for private(j,s)


for (i=0; i<n; i++) {
s = 0;
for (j=0; j<n; j++)
s += fabs(A[i][j]);
if (s>norm)
#pragma omp critical
if (s>norm)
norm = s;
}
return norm;
}

La paralelizacin la situamos en el bucle ms externo para una mayor granularidad y menor


tiempo de sincronizacin. Teniendo en cuenta que todas las iteraciones tienen el mismo coste
computacional, se puede realizar una distribucin esttica de las iteraciones (planificacin por
defecto).
La paralelizacin genera una condicin de carrera en la actualizacin del mximo de las sumas por
filas. Para evitar errores en las actualizaciones simultneas se utiliza una seccin crtica que evita la
modificacin concurrente de norm. Para evitar una excesiva secuencializacin se incluye la seccin
crtica dentro de la comprobacin del mximo, repitindose la comprobacin dentro de la seccin
crtica para asegurar que slo se modifica el valor de norm si es un valor superior al acumulado.

(b) Calcula el coste computacional (en flops) de la versin original secuencial y de la versin paralela
desarrollada.
Nota: Se puede asumir que la dimensin de la matriz n es un mltiplo exacto del nmero de hilos
p.
Nota 2: Se puede asumir que el coste de la funcin fabs es de 1 Flop.
n
n1 p 1
X n1
X X n1
X n2
Solucin: t(n) = 2 = 2n2 flops t(n, p) = 2=2 flops
i=0 j=0 i=0 j=0
p
No se incluye el coste derivado de las operaciones de sincronizacin porque dependen de los valores
de la matriz y las condiciones de ejecucin.

(c) Calcula la mxima aceleracin (speedup) posible que puede obtenerse con el cdigo paralelo. Calcula
tambin el valor de la eficiencia.
t(n) S(n, p)
Solucin: S(n, p) = =p E(n, p) = =1
t(n, p) p
La aceleracin es la mxima esperable y la eficiencia es del 100 %, lo que indica un aprovechamiento
mximo de los procesadores.

1.2. Regiones paralelas


Cuestin 1.21
Dada la siguiente funcin, que busca un valor en un vector, paralelzala usando OpenMP. Al igual que
la funcin de partida, la funcin paralela deber terminar la bsqueda tan pronto como se encuentre el
elemento buscado.
int busqueda(int x[], int n, int valor)
{
int encontrado=0, i=0;
while (!encontrado && i<n) {
if (x[i]==valor) encontrado=1;
i++;
}
return encontrado;
}

Solucin:

int busqueda(int x[], int n, int valor)


{
int encontrado=0, i, salto;
#pragma omp parallel private(i)
{
i = omp_get_thread_num();
salto = omp_get_num_threads();
while (!encontrado && i<n) {
if (x[i]==valor) encontrado=1;
i += salto;
}
}
return encontrado;
}

Cuestin 1.22
Dado un vector v de n elementos, la siguiente funcin calcula su 2-norma kvk, definida como:
v
u n
uX
kvk = t v2 i
i=1

double norma(double v[], int n)


{
int i;
double r=0;
for (i=0; i<n; i++)
r += v[i]*v[i];
return sqrt(r);
}
(a) Paralelizar la funcin anterior mediante OpenMP, siguiendo el siguiente esquema:
En una primera fase, se quiere que cada hilo calcule la suma de cuadrados de un bloque de n/p
elementos del vector v (siendo p el nmero de hilos). Cada hilo dejar el resultado en la posicin
correspondiente de un vector sumas de p elementos. Se puede asumir que el vector sumas ya ha
sido creado (aunque no inicializado).
En una segunda fase, uno de los hilos calcular la norma del vector, a partir de las sumas
parciales almacenadas en el vector sumas.
Solucin:

double norma(double v[], int n)


{
int i, i_hilo, p;
double r=0;

/* Fase 1 */
#pragma omp parallel private(i_hilo)
{
p = omp_get_num_threads();
i_hilo = omp_get_thread_num();
sumas[i_hilo]=0;
#pragma omp for schedule(static)
for (i=0; i<n; i++)
sumas[i_hilo] += v[i]*v[i];
}

/* Fase 2 */
for (i=0; i<p; i++)
r += sumas[i];
return sqrt(r);
}
(b) Paralelizar la funcin de partida mediante OpenMP, usando otra aproximacin distinta de la del
apartado anterior.
Solucin:

double norma(double v[], int n)


{
int i;
double r=0;
#pragma omp parallel for reduction(+:r)
for (i=0; i<n; i++)
r += v[i]*v[i];
return sqrt(r);
}
(c) Calcular el coste a priori del algoritmo secuencial de partida. Razonar cul sera el coste del algoritmo
paralelo del apartado a, y el speedup obtenido.
Solucin: Coste del algoritmo secuencial (el coste de la raz cuadrada es despreciable frente al
coste del bucle i):
n1
X
t(n) = 2 2n flops
i=0
Coste del algoritmo paralelo: como cada iteracin del bucle i cuesta 2 flops y cada hilo realiza n/p
iteraciones, el coste es de t(n, p) = 2n/p flops. El speedup es de S(n, p) = 2n/(2n/p) = p.

Cuestin 1.23
Dada la siguiente funcin:

double funcion(int n, double u[], double v[], double w[], double z[])
{
int i;
double sv,sw,res;

calcula_v(n,v); /* tarea 1 */
calcula_w(n,w); /* tarea 2 */
calcula_z(n,z); /* tarea 3 */
calcula_u(n,u,v,w,z); /* tarea 4 */
sv = 0;
for (i=0; i<n; i++) sv = sv + v[i]; /* tarea 5 */
sw = 0;
for (i=0; i<n; i++) sw = sw + w[i]; /* tarea 6 */
res = sv+sw;
for (i=0; i<n; i++) u[i] = res*u[i]; /* tarea 7 */
return res;
}

Las funciones calcula_X tienen como entrada los vectores que reciben como argumentos y con ellos
modifican el vector X indicado. Cada funcin nicamente modifica el vector que aparece en su nombre.
Por ejemplo, la funcin calcula_u utiliza los vectores v, w y z para realizar unos clculos que guarda en
el vector u, pero no modifica ni v, ni w, ni z.
Esto implica, por ejemplo, que las funciones calcula_v, calcula_w y calcula_z son independientes y
podran realizarse simultneamente. Sin embargo, la funcin calcula_u necesita que hayan terminado
las otras, porque usa los vectores que ellas rellenan (v,w,z).
(a) Dibuja el grafo de dependencias de las diferentes tareas.
Solucin: El grafo de dependencias se muestra a continuacin:
T1 T3 T2

T5 T4 T6

T7

(b) Paraleliza la funcin de forma eficiente.


Solucin: La paralelizacin se puede realizar con secciones y las dependencias se pueden imple-
mentar con barreras implcitas. Todas las variables son compartidas excepto el contador de los
bucles. El cdigo paralelo es:

double funcion(int n, double u[], double v[], double w[], double z[])
{
int i;
double sv,sw,res;

#pragma omp parallel private(i)


{
#pragma omp sections
{
#pragma omp section
calcula_v(n,v); /* tarea 1 */
#pragma omp section
calcula_w(n,w); /* tarea 2 */
#pragma omp section
calcula_z(n,z); /* tarea 3 */
}
#pragma omp sections
{
#pragma omp section
calcula_u(n,u,v,w,z); /* tarea 4 */
#pragma omp section
{
sv = 0;
for (i=0; i<n; i++) sv = sv + v[i]; /* tarea 5 */
}
#pragma omp section
{
sw = 0;
for (i=0; i<n; i++) sw = sw + w[i]; /* tarea 6 */
}
}
#pragma omp single
{
res = sv+sw;
for (i=0; i<n; i++) u[i] = res*u[i]; /* tarea 7 */
}
}
return res;
}
(c) Si suponemos que el coste de todas las funciones calcula_X es el mismo y que el coste de los bucles
posteriores es despreciable, cul sera el speedup mximo posible?
Solucin: Suponiendo que el coste de cada tarea calcula_X es 1, el coste total del algoritmo
secuencial sera 4. A la vista del grafo de dependencias se deduce que el grado de concurrencia es
3 (el mximo nmero de tareas que se pueden ejecutar en paralelo es 3). Las tres primeras tareas
calcula_X se pueden ejecutar en paralelo, pero la ltima no. Por tanto, el coste del algoritmo
paralelo ser 2 en el caso de utilizar 3 o ms procesadores. El speedup mximo en ese caso ser
4/2=2.
1.3. Sincronizacin
1.4. Tareas
Cuestin 1.41
Una sucesin de Fibonacci se define como la siguiente sucesin infinita:

0 1 1 2 3 5 8 13 21 34 55 ...

calculndose cada elemento de la serie as fn = fn1 + fn2 , n > 1, f0 = 0 y f1 = 1.


Una forma burda pero vlida para obtener el elemento fn es utilizar la siguiente funcin:
int fibonacci(int n) {
int i,j;
if (n<2) {
return n;
} else {
i = fibonacci(n-1);
j = fibonacci(n-2);
return i+j;
}
}
Implementar una versin en OpenMP que permita su clculo en paralelo en memoria compartida mediante
tareas OpenMP (task).

Solucin: La paralelizacin en OpenMP de la rutina anterior se realizara como sigue:

int fibonacci(int n) {
int i,j;
if (n<2) {
return n;
} else {
#pragma omp task shared(i) if(n<10)
i = fibonacci(n-1);
#pragma omp task shared(j) if(n<10)
j = fibonacci(n-2);
#pragma omp taskwait
return i+j;
}
}

y la llamada a la rutina en el programa principal se hara as:

#pragma omp parallel


#pragma omp single
m = fibonacci(n);

Dado que el coste de la generacin de tareas es considerable y esto puede ralentizar el proceso en lugar
de optimizarlo, se corta la generacin de tareas si el valor de n es menor de cierta cantidad. Este es un
caso en el que es necesario sincronizar las dos tareas generadas para poder utilizar el resultado devuelto
y regresar as hacia arriba en el rbol de llamadas con el dato correcto.
2. Paso de Mensajes y MPI
2.1. Comunicacin punto a punto
Cuestin 2.11
Dado el siguiente cdigo secuencial:
int calculo(int i,int j,int k)
{
double a,b,c,d;
a = T1(i);
b = T2(j);
c = T3(k);
d = T4(a+b+c);
x = T5(a/d);
y = T6(b/d);
z = T7(c/d);
return x+y+z;
}
(a) Dibuja el grafo de dependencias de las diferentes tareas, incluyendo el clculo del resultado de la
funcin.
Solucin: El grafo de dependencias es el siguiente:
T1 T2 T3

T4

T5 T6 T7

La ltima tarea representa la suma necesaria en la instruccin return. Existen tres dependencias
(T1 T5 , T2 T6 y T3 T7 ) que no se han representado, ya que se satisfacen indirectamente a
travs de las dependencias con T4 .

(b) Realiza una implementacin paralela en MPI suponiendo que el nmero de procesos es igual a 3.
Solucin: En la primera y tercera fase del algoritmo los tres procesos estn activos, mientras que
en la segunda fase slo uno de ellos (por ejemplo P0 ) ejecuta tarea. Antes de T4 , P0 tiene que recibir
b y c de los otros procesos, y al finalizar T4 se debe enviar el resultado d a P1 y P2 . Finalmente,
hay que sumar las variables x, y, z, por ejemplo en el proceso P0 .

int calculo(int i,int j,int k)


{
double a,b,c,d;
int p,rank;
MPI_Comm_size(MPI_COMM_WORLD,&p);
MPI_Comm_rank(MPI_COMM_WORLD,&rank);
/* primera fase */
if (rank==0) {
a = T1(i);
MPI_Recv(&b, 1, MPI_DOUBLE, 1, 111, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
MPI_Recv(&c, 1, MPI_DOUBLE, 2, 111, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
} else if (rank==1) {
b = T2(j);
MPI_Send(&b, 1, MPI_DOUBLE, 0, 111, MPI_COMM_WORLD);
} else { /* rank==2 */
c = T3(k);
MPI_Send(&c, 1, MPI_DOUBLE, 0, 111, MPI_COMM_WORLD);
}
/* segunda fase */
if (rank==0) {
d = T4(a+b+c);
}
/* tercera fase */
if (rank==0) {
MPI_Send(&d, 1, MPI_DOUBLE, 1, 112, MPI_COMM_WORLD);
MPI_Send(&d, 1, MPI_DOUBLE, 2, 112, MPI_COMM_WORLD);
x = T5(a/d);
} else if (rank==1) {
MPI_Recv(&d, 1, MPI_DOUBLE, 0, 112, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
y = T6(b/d);
} else { /* rank==2 */
MPI_Recv(&d, 1, MPI_DOUBLE, 0, 112, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
z = T7(c/d);
}
/* suma final */
if (rank==0) {
MPI_Recv(&y, 1, MPI_DOUBLE, 1, 113, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
MPI_Recv(&z, 1, MPI_DOUBLE, 2, 113, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
} else if (rank==1) {
MPI_Send(&y, 1, MPI_DOUBLE, 0, 113, MPI_COMM_WORLD);
} else { /* rank==2 */
MPI_Send(&z, 1, MPI_DOUBLE, 0, 113, MPI_COMM_WORLD);
}
return x+y+z;
}
(c) Implementa una versin modificada de forma que se realice la ejecucin replicada de la tarea T4 ,
utilizando operaciones de comunicacin colectiva.
Solucin: La ejecucin replicada de T4 permite evitar la comunicacin de la tercera fase. Para
ello, al final de la primera fase hay que hacer que a, b, c estn disponibles en todos los procesos, o
mejor an que se haga una reduccin-a-todos de dichas variables.
int calculo(int i,int j,int k)
{
double a,b,c,d,*e,f;
int p,rank;
MPI_Comm_size(MPI_COMM_WORLD,&p);
MPI_Comm_rank(MPI_COMM_WORLD,&rank);
/* primera fase */
if (rank==0) {
a = T1(i);
e = &a;
} else if (rank==1) {
b = T2(j);
e = &b;
} else { /* rank==2 */
c = T3(k);
e = &c;
}
MPI_Allreduce(e, &f, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
/* segunda fase */
d = T4(f);
/* tercera fase */
if (rank==0) {
x = T5(a/d);
} else if (rank==1) {
y = T6(b/d);
} else { /* rank==2 */
z = T7(c/d);
}
/* suma final */
if (rank==0) {
MPI_Recv(&y, 1, MPI_DOUBLE, 1, 113, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
MPI_Recv(&z, 1, MPI_DOUBLE, 2, 113, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
} else if (rank==1) {
MPI_Send(&y, 1, MPI_DOUBLE, 0, 113, MPI_COMM_WORLD);
} else { /* rank==2 */
MPI_Send(&z, 1, MPI_DOUBLE, 0, 113, MPI_COMM_WORLD);
}
return x+y+z;
}

Cuestin 2.12
Dada la siguiente funcin:
double funcion()
{
int i,n,j;
double *v,*w,*z,sv,sw,x,res;

/* Leer los vectores v, w, z, de dimension n */


leer(&n, &v, &w, &z);

calcula_v(n,v); /* tarea 1 */
calcula_w(n,w); /* tarea 2 */
calcula_z(n,z); /* tarea 3 */

/* tarea 4 */
for (j=0; j<n; j++) {
sv = 0;
for (i=0; i<n; i++) sv = sv + v[i]*w[i];
for (i=0; i<n; i++) v[i]=sv*v[i];
}

/* tarea 5 */
for (j=0; j<n; j++) {
sw = 0;
for (i=0; i<n; i++) sw = sw + w[i]*z[i];
for (i=0; i<n; i++) w[i]=sw*w[i];
}

/* tarea 6 */
x = sv+sw;
for (i=0; i<n; i++) res = res+x*z[i];

return res;
}
Las funciones calcula_X tienen como entrada los datos que reciben como argumentos y con ellos modi-
fican el vector X indicado. Por ejemplo, calcula_v(n,v) toma como datos de entrada los valores de n y
v y modifica el vector v.
(a) Dibuja el grafo de dependencias de las diferentes tareas, incluyendo en el mismo el coste de cada
una de las tareas y de cada una de las comunicaciones. Suponer que las funciones calcula_X tienen
un coste de 2n2 .
Solucin: Los costes de comunicaciones aparecen en las aristas del grafo.

leer
1

n+
n+

n+1
1

T1 T2 T3

n n n n

T4 T5

n
1 1

T6

El coste (tiempo de ejecucin) de la tarea 4 es:

n1 n1 n1
! n1
X X X X
2+ 1 = (2n + n) = 3n2
j=0 i=0 i=0 j=0

El coste de T5 es igual al de T4, y el coste de T6 es 2n.

(b) Paralelzalo usando MPI, de forma que los procesos MPI disponibles ejecutan las diferentes tareas
(sin dividirlas en subtareas). Se puede suponer que hay al menos 3 procesos.
Solucin: Hay distintas posibilidades de hacer la asignacin. Hay que tener en cuenta que slo
uno de los procesos debe realizar la lectura. Las tareas 1, 2 y 3 son independientes y por tanto se
pueden asignar a 3 procesos distintos. Lo mismo ocurre con las 4 y 5.
Por ejemplo, el proceso 0 se puede encargar de leer, y hacer las tareas 1 y 4. El proceso 1 puede
hacer la tarea 2, y el proceso 3 las tareas 3, 4 y 5.
double funcion()
{
int i,n,j;
double *v,*w,*z,sv,sw,x,res;
int p,rank;
MPI_Status status;

MPI_Comm_size(MPI_COMM_WORLD,&p);
MPI_Comm_rank(MPI_COMM_WORLD,&rank);

if (rank==0) {
/* T0: Leer los vectores v, w, z, de dimension n */
leer(&n, &v, &w, &z);

MPI_Send(&n, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);


MPI_Send(&n, 1, MPI_INT, 2, 0, MPI_COMM_WORLD);
MPI_Send(w, n, MPI_DOUBLE, 1, 0, MPI_COMM_WORLD);
MPI_Send(z, n, MPI_DOUBLE, 2, 0, MPI_COMM_WORLD);

calcula_v(n,v); /* tarea 1 */
MPI_Recv(w, n, MPI_DOUBLE, 1, 0, MPI_COMM_WORLD, &status);

/* tarea 4 (mismo codigo del caso secuencial) */


...

MPI_Send(&sv, 1, MPI_DOUBLE, 2, 0, MPI_COMM_WORLD);

MPI_Recv(&res, 1, MPI_DOUBLE, 2, 0, MPI_COMM_WORLD, &status);


}
else if (rank==1) {
MPI_Recv(&n, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
MPI_Recv(w, n, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, &status);

calcula_w(n,w); /* tarea 2 */

MPI_Send(w, n, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);


MPI_Send(w, n, MPI_DOUBLE, 2, 0, MPI_COMM_WORLD);

MPI_Recv(&res, 1, MPI_DOUBLE, 2, 0, MPI_COMM_WORLD, &status);


}
else if (rank==2) {
MPI_Recv(&n, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
MPI_Recv(z, n, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, &status);

calcula_z(n,z); /* tarea 3 */
MPI_Recv(w, n, MPI_DOUBLE, 1, 0, MPI_COMM_WORLD, &status);
/* tarea 5 (mismo codigo del caso secuencial) */
...

MPI_Recv(&sv, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, &status);

/* tarea 6 (mismo codigo del caso secuencial) */


...

/* Enviar el resultado de la tarea 6 a los demas procesos */


MPI_Send(&res, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);
MPI_Send(&res, 1, MPI_DOUBLE, 1, 0, MPI_COMM_WORLD);
}
return res;
}
(c) Indica el tiempo de ejecucin del algoritmo secuencial, el del algoritmo paralelo, y el speedup que
se obtendra. Ignorar el coste de la lectura de los vectores.
Solucin: Teniendo en cuenta que el tiempo de ejecucin de cada una de las tareas 1, 2 y 3 es de
2n2 , mientras que el de las tareas 4 y 5 es de 3n2 , y el de la tarea 6 es de 2n, el tiempo de ejecucin
secuencial ser la suma de esos tiempos:

t(n) = 3 2n2 + 2 3n2 + 2n 12n2

Tiempo de ejecucin paralelo: tiempo aritmtico. Ser igual al tiempo aritmtico del proceso que
ms operaciones realice, que en este caso es el proceso 2, que realiza las tareas 3, 5 y 6. Por tanto

ta (n, p) = 2n2 + 3n2 + 2n 5n2

Tiempo de ejecucin paralelo: tiempo de comunicaciones. Los mensajes que se transmiten son:

2 mensajes, del proceso 0 a los dems, con el valor de n. Coste de 2(ts + tw ).


2 mensajes, uno del proceso 0 al 1 con el vector w y otro del 0 al 2 con el vector z. Coste de
2(ts + ntw ).
2 mensajes, del proceso 1 a los dems, con el vector w. Coste de 2(ts + ntw ).
1 mensaje, del proceso 0 al 2, con el valor de sv. Coste de (ts + tw )
2 mensajes, del proceso 2 a los dems, con el valor de res. Coste de 2(ts + tw )

Por lo tanto, el coste de comunicaciones ser:

tc (n, p) = 5(ts + tw ) + 4(ts + ntw ) = 9ts + (5 + 4n)tw 9ts + 4ntw

Tiempo de ejecucin paralelo total:

t(n, p) = ta (n, p) + tc (n, p) = 5n2 + 9ts + 4ntw

Speedup:
12n2
S(n, p) =
5n2 + 9ts + 4ntw
Cuestin 2.13
Implementa una funcin que, a partir de un vector de dimensin n, distribuido entre p procesos de forma
cclica por bloques, realice las comunicaciones necesarias para que todos los procesadores acaben con una
copia del vector completo. Nota: utiliza nicamente comunicacin punto a punto.
La cabecera de la funcin ser:
void comunica_vector(double vloc[], int n, int b, int p, double w[])
/* vloc: parte local del vector v inicial
n: dimension global del vector v
b: tamao de bloque empleado en la distribucion del vector v
p: numero de procesos
w: vector de longitud n, donde debe guardarse una copia del vector v completo
*/

Solucin: Asumimos que n es mltiplo del tamao de bloque b (o sea, todos los bloques tienen tamao
b).

void comunica_vector(double vloc[], int n, int b, int p, double w[])


/* vloc: parte local del vector v inicial
n: dimension global del vector v
b: tamao de bloque empleado en la distribucion del vector v
p: numero de procesos
w: vector de longitud n, donde debe guardarse una copia del vector v completo
*/
{
int i, rank, rank_pb, rank2;
int ib, ib_loc; /* Indice de bloque */
int num_blq=n/b; /* Numero de bloques */
MPI_Status status;

MPI_Comm_rank(MPI_COMM_WORLD, &rank);
for (ib=0; ib<num_blq; ib++) {
rank_pb = ib%p; /* propietario del bloque */
if (rank==rank_pb) {
ib_loc = ib/p; /* indice local del bloque */
/* Enviar bloque ib a todos los procesos menos a mi mismo */
for (rank2=0; rank2<p; rank2++) {
if (rank!=rank2) {
MPI_Send(&vloc[ib_loc*b], b, MPI_DOUBLE, rank2, 0, MPI_COMM_WORLD);
}
}
/* Copiar bloque ib en mi propio vector local */
for (i=0; i<b; i++) {
w[ib*b+i]=vloc[ib_loc*b+i];
}
}
else {
MPI_Recv(&w[ib*b], b, MPI_DOUBLE, rank_pb, 0, MPI_COMM_WORLD, &status);
}
}
}
Cuestin 2.14
Se desea aplicar un conjunto de T tareas sobre un vector de nmeros reales de tamao n. Estas tareas
han de aplicarse secuencialmente y en orden. La funcin que las representa tiene la siguiente cabecera:

void tarea(int tipo_tarea, int n, double *v);


donde tipo_tarea identifica el nmero de tarea de 1 hasta T . Sin embargo, estas tareas sern aplicadas
a m vectores. Estos vectores estn almacenados en una matriz A en el proceso maestro donde cada fila
representa uno de esos m vectores.
(a) Implementar un programa paralelo en MPI en forma de Pipeline donde cada proceso (P1 . . . Pp1 )
ejecutar una de las T tareas (T = p 1). El proceso maestro (P0 ) se limitar a alimentar el pipeline
y recoger cada uno de los vectores (y almacenarlos de nuevo en la matriz A) una vez hayan pasado
por toda la tubera. Utilizad un mensaje vacio identificado mediante una etiqueta para terminar el
programa (supngase que los esclavos desconocen el nmero m de vectores).
Solucin: La parte de cdigo que puede servir para implementar el pipeline propuesto podra ser
la siguiente:

#define TAREA_TAG 123


#define FIN_TAG 1
int continuar,num;
MPI_Status stat;
if (!rank) {
for (i=0;i<m;i++) {
MPI_Send(&A[i*n], n, MPI_DOUBLE, 1, TAREA_TAG, MPI_COMM_WORLD);
}
MPI_Send(0, 0, MPI_DOUBLE, 1, FIN_TAG, MPI_COMM_WORLD);
for (i=0;i<m;i++) {
MPI_Recv(&A[i*n], n, MPI_DOUBLE, p-1, TAREA_TAG, MPI_COMM_WORLD, &stat);
}
MPI_Recv(0, 0, MPI_DOUBLE, p-1, FIN_TAG, MPI_COMM_WORLD, &stat);
} else {
continuar = 1;
while (continuar) {
MPI_Recv(A, n, MPI_DOUBLE, rank-1, MPI_ANY_TAG, MPI_COMM_WORLD, &stat);
MPI_Get_count(&stat, MPI_DOUBLE, &num);
if (stat.MPI_TAG == TAREA_TAG) {
tarea(rank, n, A);
} else {
continuar = 0;
}
MPI_Send(A, num, MPI_DOUBLE, (rank+1)%p, stat.MPI_TAG, MPI_COMM_WORLD);
}
}
(b) Suponiendo que cada tarea tiene un coste de 2n2 flops, calcular el coste secuencial y paralelo del
algoritmo para m vectores y T tareas suponiendo que todos los nodos estn interconectados entre
s y que el coste del mensaje testigo de terminacin es despreciable.
Solucin: El coste secuencial del algoritmo es de 2n2 mT flops.
Para calcular el coste paralelo obtenemos los costes aritmtico (ta ) y de comunicaciones (tc ) por
separado.
La manera ms intuitiva de calcular el coste paralelo de un pipeline consiste en analizar:
El coste de clculo del primer elemento a procesar, en este caso, el primer vector. Este coste
es el coste por tarea (2n2 ) multiplicado por el nmero de tareas T , 2n2 T .

Una vez haya salido el primer elemento del cauce, se obtiene un nuevo elemento cada 2n2
flops que, multiplicado por el nmero de elementos (m) menos 1 dara el coste aritmtico
asociado a los m 1 elementos restantes.

El coste aritmtico, por tanto, es

ta = 2n2 T + 2n2 (m 1) = 2n2 (T + m 1) .

Para obtener el coste de comunicaciones se sigue un razonamiento similar. El primer mensaje tiene
que dar p saltos desde que sale del proceso maestro hasta que vuelve a l. Por lo tanto, este coste
es de
p (ts + n tw ) .
A partir de entonces, el proceso maestro recibe m1 mensajes de tamao n. Se ha supuesto que los
nodos estn todos interconectados entre s, por lo que se asume que pueden darse comunicaciones
concurrentes. El coste de comunicaciones total en funcin del nmero de tareas (T ) es

tc = (T + 1) (ts + n tw ) + (m 1) (ts + n tw ) = (T + m) (ts + n tw ) .

El coste total del algoritmo paralelo sera:

ta + tc = (T + m 1)2n2 + (T + m)(ts + ntw ) .

(c) Obtener el Speedup del algoritmo paralelo. Analizar dicho Speedup cuando el nmero de datos (m)
se hace grande. En tal caso, analizad tambin cul sera el Speedup cuando el tamao del vector (n)
se hace grande. Por ltimo, para m y n suficientemente grandes indicad la eficiencia.
Solucin: El Speedup se calcula as
2n2 mT
S= .
(T + m 1)2n2+ (T + m)(ts + ntw )
Para analizar el Speedup cuando m se hace grande vemos qu pasa al tender m a infinito:
2n2 T 2n2 T T
lim S = lim 1
= = ts +ntw .
m m ( Tm 2 T
+ 1)2n + ( m + 1)(ts + ntw ) 2
2n + (ts + ntw ) 1 + 2n2
La expresin anterior dice que, para un nmero suficientemente grande de vectores a procesar, el
Speedup es proporcional al nmero de tareas del pipeline, es decir, tendremos ms incremento de
velocidad cuanto ms tareas haya en el pipeline.
Sin embargo, el Speedup sigue limitado por el coste de las comunicaciones. Si utilizamos vectores
suficientemente grandes, es decir, cuando n tiende a infinito, obtendremos lo siguiente:
T
lim S = ts +ntw =T ,
m,n 1+ 2n2
lo que viene a indicar que se tendr ms incremento de velocidad cuanto mayor sea tanto el nmero
de vectores a procesar como el tamao de los mismos y que ste ser proporcional al nmero de
tareas.
Para obtener la eficiencia en el caso de que tanto m como n sean grandes (infinito) utilizaremos la
ltima expresin del Speedup:
T p1
E= = .
p p
Esta eficiencia se aproxima al 100 % con el nmero de procesos. Para obtener una eficiencia mxima
del 100 % podramos, por ejemplo, asignar una tarea a P0 o bien simplemente mapear P0 y P1 en
el mismo procesador, ya que P0 no hace trabajo efectivo.

Cuestin 2.15
En un programa paralelo ejecutado en p procesos, se tiene un vector x de dimensin n distribuido por
bloques, y un vector y replicado en todos los procesos. Implementar la siguiente funcin, la cual debe
sumar la parte local del vector x con la parte correspondiente del vector y, dejando el resultado en un
vector local z.

void suma(double xloc[], double y[], double z[], int n, int p, int pr)
/* pr es el indice del proceso local */

Solucin:

void suma(double xloc[], double y[], double z[], int n, int p, int pr)
/* pr es el indice del proceso local */
{
int i, iloc, mb;

mb = ceil(((double) n)/p);
for (i=pr*mb; i<MIN((pr+1)*mb,n); i++) {
iloc=i%mb;
z[iloc]=xloc[iloc]+y[i];
}
}

Cuestin 2.16
La distancia de Levenshtein proporciona una medida de similitud entre dos cadenas. El siguiente cdigo
secuencial utiliza dicha distancia para calcular la posicin en la que una subcadena es ms similar a otra
cadena, asumiendo que las cadenas se leen desde un fichero de texto.
Ejemplo: si la cadena ref contiene "aafsdluqhqwBANANAqewrqerBANAfqrqerqrABANArqwrBAANANqwe" y
la cadena str contiene "BANAN", el programa mostrar que la cadena "BANAN" se encuentra en la menor
diferencia en la posicin 11.

int mindist, pos, dist, i, ls, lr;


FILE *f1, *f2;
char ref[500], str[100];

f1 = fopen("ref.txt","r");
fgets(ref,500,f1);
lr = strlen(ref);
printf("Ref: %s (%d)\n", ref, lr);
fclose(f1);

f2 = fopen("lines.txt","r");
while (fgets(str,100,f2)!=NULL) {
ls = strlen(str);
printf("Str: %s (%d)\n", str, ls);
mindist = levenshtein(str, ref);
pos = 0;
for (i=1;i<lr-ls;i++) {
dist = levenshtein(str, &ref[i]);
if (dist < mindist) {
mindist = dist;
pos = i;
}
}
printf("Distancia %d para %s en %d\n", mindist, str, pos);
}
fclose(f2);
(a) Realiza una implementacin siguiendo el modelo maestro-esclavo de dicho algoritmo utilizando MPI.
Solucin:

int mindist, pos, dist, i, ls, lr, count, rank, size, rc, org;
FILE *f1, *f2;
char ref[500], str[100], c;
MPI_Status status;

MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);

if (rank ==0) { /* master */


f1 = fopen("ref.txt","r");
fgets(ref,500,f1);
lr = strlen(ref);
ref[lr-1]=0;
lr--;
MPI_Bcast(ref, lr+1, MPI_CHAR, 0, MPI_COMM_WORLD);
printf("Ref: %s (%d)\n", ref, lr);
fclose(f1);

f2 = fopen("lines.txt","r");
count = 1;
while ( (fgets(str,100,f2)!=NULL) && (count<size) ) {
ls = strlen(str);
str[ls-1] = 0;
ls--;
MPI_Send(str, ls+1, MPI_CHAR, count, TAG_WORK, MPI_COMM_WORLD);
count++;
}

do {
printf("%d procesos activos\n", count);
MPI_Recv(&mindist, 1, MPI_INT, MPI_ANY_SOURCE, TAG_RESULT,
MPI_COMM_WORLD, &status);
org = status.MPI_SOURCE;
MPI_Recv(&pos, 1, MPI_INT, org, TAG_POS, MPI_COMM_WORLD, &status);
MPI_Recv(str, 100, MPI_CHAR, org, TAG_STR, MPI_COMM_WORLD, &status);
ls = strlen(str);
printf("De [%d]: Distancia %d para %s en %d\n", org, mindist, str, pos);
count--;
rc = (fgets(str,100,f2)!=NULL);
if (rc) {
ls = strlen(str);
str[ls-1] = 0;
ls--;
MPI_Send(str, ls+1, MPI_CHAR, org, TAG_WORK, MPI_COMM_WORLD);
count++;
} else {
printf("Enviando mensaje de terminacin a %d\n", status.MPI_SOURCE);
MPI_Send(&c, 1, MPI_CHAR, org, TAG_END, MPI_COMM_WORLD);
}
} while (count>1);

fclose(f2);
} else { /* worker */
MPI_Bcast(ref, 500, MPI_CHAR, 0, MPI_COMM_WORLD);
lr = strlen(ref);
printf("[%d], Ref: %s\n", rank, ref);
rc = 0;
do {
MPI_Recv(str, 100, MPI_CHAR, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
ls = strlen(str);
if (status.MPI_TAG == TAG_WORK) {
printf("[%d] Mensaje recibido (%s)\n", rank, str);
mindist = levenshtein(str, ref);
pos = 0;
for (i=1;i<lr-ls;i++) {
dist = levenshtein(str, &ref[i]);
if (dist < mindist) {
mindist = dist;
pos = i;
}
}

printf("[%d] enva: %d, %d, y %s a 0\n", rank, mindist, pos, str);


MPI_Send(&mindist, 1, MPI_INT, 0, TAG_RESULT, MPI_COMM_WORLD);
MPI_Send(&pos, 1, MPI_INT, 0, TAG_POS, MPI_COMM_WORLD);
MPI_Send(str, ls+1, MPI_CHAR, 0, TAG_STR, MPI_COMM_WORLD);
} else {
printf("[%d] recibe mensaje con etiqueta %d\n", rank, status.MPI_TAG);
rc = 1;
}
} while (!rc);
}
(b) Calcula el coste de comunicaciones de la versin paralela desarrollada dependiendo del tamao del
problema n y del nmero de procesos p.
Solucin: En la versin propuesta, el coste de comunicacin se debe a cuatro conceptos principales:
Difusin de la referencia (lr + 1 bytes): (ts + tw (lr + 1)) (p 1)
Mensaje individual por cada secuencia (lsi + 1 bytes): (ts + tw (lsi + 1)) n
Tres mensajes para la respuesta de cada secuencia (dos enteros ms lsi + 1 bytes):
(ts + tw (lsi + 1)) n + 2 n (ts + 4 tw )

Mensaje de terminacin (1 byte): (ts + tw ) (p 1)

Por tanto, el coste total se puede aproximar por 2 n ts + tw (9 n + m).

2.2. Comunicacin colectiva


Cuestin 2.21
El siguiente fragmento de cdigo permite calcular el producto de una matriz cuadrada por un vector,
ambos de la misma dimensin N:
int i, j;
int A[N][N], v[N], x[N];
leer(A,v);
for (i=0;i<N;i++) {
x[i]=0;
for (j=0;j<N;j++) x[i] += A[i][j]*v[j];
}
escribir(x);

Escribe un programa MPI que realice el producto en paralelo, teniendo en cuenta que el proceso P0 obtiene
inicialmente la matriz A y el vector v, realiza una distribucin de A por bloques de filas consecutivas sobre
todos los procesos y enva v a todos. Asimismo, al final P0 debe obtener el resultado.
Nota: Para simplificar, se puede asumir que N es divisible por el nmero de procesos.

Solucin: Definimos una matriz auxiliar B y un vector auxiliar y, que contendrn las porciones locales
de A y x en cada proceso. Tanto B como y tienen k=N/p filas, pero para simplificar se han dimensionado
a N filas ya que el valor de k es desconocido en tiempo de compilacin (una solucin eficiente en trminos
de memoria reservara estas variables con malloc).

int i, j, k, rank, p;
int A[N][N], B[N][N], v[N], x[N], y[N];

MPI_Comm_size(MPI_COMM_WORLD,&p);
MPI_Comm_rank(MPI_COMM_WORLD,&rank);
if (rank == 0) leer(A,v);
k = N/p;
MPI_Scatter(A, k*N, MPI_INT, B, k*N, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Bcast(v, N, MPI_INT, 0, MPI_COMM_WORLD);
for (i=0;i<k;i++) {
y[i]=0;
for (j=0;j<N;j++) y[i] += B[i][j]*v[j];
}
MPI_Gather(y, k, MPI_INT, x, k, MPI_INT, 0, MPI_COMM_WORLD);
if (rank == 0) escribir(x);

Cuestin 2.22
El siguiente fragmento de cdigo calcula la norma de Frobenius de una matriz cuadrada obtenida a partir
de la funcin leermat.
int i, j;
double s, norm, A[N][N];
leermat(A);
s = 0.0;
for (i=0;i<N;i++) {
for (j=0;j<N;j++) s += A[i][j]*A[i][j];
}
norm = sqrt(s);
printf("norm=%f\n",norm);
Implementa un programa paralelo usando MPI que calcule la norma de Frobenius, de manera que el
proceso P0 lea la matriz A, la reparta segn una distribucin cclica de filas, y finalmente obtenga el
resultado s y lo imprima en la pantalla.
Nota: Para simplificar, se puede asumir que N es divisible por el nmero de procesos.

Solucin: Utilizamos una matriz auxiliar B para que cada proceso almacene su parte local de A (slo
se utilizan las k primeras filas). Para la distribucin de la matriz, se hacen k operaciones de reparto,
una por cada bloque de p filas.

int i, j, k, rank, p;
double s, norm, A[N][N], B[N][N];

MPI_Comm_size(MPI_COMM_WORLD, &p);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
k = N/p;
if (rank == 0) leermat(A);
for (i=0;i<k;i++) {
MPI_Scatter(&A[i*p][0],N, MPI_DOUBLE, &B[i][0], N, MPI_DOUBLE, 0,
MPI_COMM_WORLD);
}
s=0;
for (i=0;i<k;i++) {
for (j=0;j<N;j++) s += B[i][j]*B[i][j];
}
MPI_Reduce(&s, &norm, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
if (rank == 0) {
norm = sqrt(norm);
printf("norm=%f\n",norm);
}

Cuestin 2.23
Se quiere paralelizar el siguiente programa usando MPI.
double *lee_datos(char *nombre, int *n) {
... /* lectura desde fichero de los datos */
/* devuelve un puntero a los datos y el nmero de datos en *n */
}

double procesa(double x) {
... /* funcin costosa que hace un clculo dependiente de x */
}
int main() {
int i,n;
double *a,res;

a = lee_datos("datos.txt",&n);
res = 0.0;
for (i=0; i<n; i++)
res += procesa(a[i]);

printf("Resultado: %.2f\n",res);
free(a);
return 0;
}
Cosas a tener en cuenta:

Slo el proceso 0 debe llamar a lee_datos (slo l leer del fichero).


Slo el proceso 0 debe mostrar el resultado.
Hay que repartir los n clculos entre los procesos disponibles usando un reparto por bloques. Habr
que enviar a cada proceso su parte de a y recoger su aportacin al resultado res. Se puede suponer
que n es divisible por el nmero de procesos.
(a) Realiza una versin con comunicacin punto a punto.
Solucin:

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


{
int i,n,p,np,nb;
double *a,res,aux;

MPI_Init(&argc,&argv);
MPI_Comm_rank(MPI_COMM_WORLD,&p);
MPI_Comm_size(MPI_COMM_WORLD,&np);

if (!p) a = lee_datos("datos.txt",&n);

/* Difundir el tamao del problema (1) */


if (!p) {
for (i=1; i<np; i++)
MPI_Send(&n, 1, MPI_INT, i, 5, MPI_COMM_WORLD);
} else {
MPI_Recv(&n, 1, MPI_INT, 0, 5, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
}
nb = n/np; /* Asumimos que n es mltiplo de np */

if (p) a = malloc(nb*sizeof(int));

/* Repartir la a entre todos los procesos (2) */


if (!p) {
for (i=1; i<np; i++)
MPI_Send(&a[i*nb], nb, MPI_INT, i, 25, MPI_COMM_WORLD);
} else {
MPI_Recv(a, nb, MPI_INT, 0, 25, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
}
res = 0.0;
for (i=0; i<nb; i++)
res += procesa(a[i]);

/* Recogida de resultados (3) */


if (!p) {
for (i=1; i<np; i++)
MPI_Recv(&aux, 1, MPI_DOUBLE, i, 52, MPI_COMM_WORLD,MPI_STATUS_IGNORE);
res += aux;
}
} else {
MPI_Send(&res, 1, MPI_DOUBLE, 0, 52, MPI_COMM_WORLD);
}
if (!p) printf("Resultado: %.2f\n",res);
free(a);

MPI_Finalize();
return 0;
}
(b) Realiza una versin utilizando primitivas de comunicacin colectiva.
Solucin: Slo hay que cambiar en el cdigo anterior las zonas marcadas con (1), (2) y (3) por:

/* Difundir el tamao del problema (1) */


MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);

/* Repartir la a entre todos los procesos (2) */


MPI_Scatter(a, nb, MPI_DOUBLE, b, nb, MPI_DOUBLE, 0, MPI_COMM_WORLD);

/* Recogida de resultados (3) */


aux = res;
MPI_Reduce(&aux, &res, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);

En el scatter se ha utilizado una variable auxiliar b, ya que no est permitido usar el mismo buffer
para envo y recepcin. Faltara cambiar a por b en las llamadas a malloc, free y procesa.

Cuestin 2.24
Desarrolla un programa usando MPI que juegue al siguiente juego:
1. Cada proceso se inventa un nmero y se lo comunica al resto.
2. Si todos los procesos han pensado el mismo nmero, se acaba el juego.
3. Si no, se repite el proceso (se vuelve a 1). Si ya ha habido 1000 repeticiones, se finaliza con un error.
4. Al final hay que indicar por pantalla (una sola vez) cuntas veces se ha tenido que repetir el proceso
para que todos pensaran el mismo nmero.
Se dispone de la siguiente funcin para inventar los nmeros:
int piensa_un_numero(); /* devuelve un nmero aleatorio */
Utiliza operaciones de comunicacin colectiva de MPI para todas las comunicaciones necesarias.
Solucin:

int p,np;
int num,*vnum,cont,iguales,i;

MPI_Comm_rank(MPI_COMM_WORLD,&p);
MPI_Comm_size(MPI_COMM_WORLD,&np);
vnum = malloc(np*sizeof(int));
cont = 0;
do {
cont++;
num = piensa_un_numero();
MPI_Allgather(&num, 1, MPI_INT, vnum, 1, MPI_INT, MPI_COMM_WORLD);
iguales = 0;
for (i=0; i<np; i++) {
if (vnum[i]==num) iguales++;
}
} while (iguales!=np && cont<1000);

if (!p) {
if (iguales==np)
printf("Han pensado el mismo nmero en la vez %d.\n",cont);
else
printf("ERROR: Tras 1000 veces, no coinciden los nmeros.\n");
}
free(vnum);

Cuestin 2.25
Se pretende implementar un generador de nmeros aleatorios paralelo. Dados p procesos MPI, el programa
funcionar de la siguiente forma: todos los procesos van generando una secuencia de nmeros hasta que
P0 les indica que paren. En ese momento, cada proceso enviar a P0 su ltimo nmero generado y P0
combinar todos esos nmeros con el nmero que ha generado l. En pseudocdigo sera algo as:
si id=0
n = inicial
para i=0 hasta 100
n = siguiente(n)
fpara
envia mensaje de aviso a procesos 1..np-1
recibe m[k] de procesos 1..np-1
n = combina(n,m[k]) para cada k=1..np-1
si no
n = inicial
mientras no recibo mensaje de 0
n = siguiente(n)
fmientras
enva n a 0
fsi

Implementar en MPI un esquema de comunicacin asncrona para este algoritmo, utilizando MPI_Irecv
y MPI_Test. La recogida de resultados puede realizarse con una operacin colectiva.
Solucin:

MPI_Comm_rank(MPI_COMM_WORLD, &id);
MPI_Comm_size(MPI_COMM_WORLD, &np);
n = inicial(id);
if (id==0) {
for (i=0;i<100;i++) n = siguiente(n);
for (k=1;k<np;k++) {
MPI_Send(0, 0, MPI_INT, k, TAG_AVISO, MPI_COMM_WORLD);
}
} else {
MPI_Irecv(0, 0, MPI_INT, 0, TAG_AVISO, MPI_COMM_WORLD, &req);
do {
n = siguiente(n);
MPI_Test(&req, &flag, MPI_STATUS_IGNORE);
} while (!flag);
}
MPI_Gather(&n, 1, MPI_DOUBLE, m, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
for (k=1;k<np;k++) n = combina(n,m[k]);

Cuestin 2.26
Dado el siguiente fragmento de programa que calcula el valor del nmero :
double rx, ry, computed_pi;
long int i, points, hits;
unsigned int seed = 1234;

hits = 0;
for (i=0; i<points; i++) {
rx = (double)(rand_r(&seed))/((double)RAND_MAX);
ry = (double)(rand_r(&seed))/((double)RAND_MAX);
if (((rx-0.5)*(rx-0.5)+(ry-0.5)*(ry-0.5))<0.25) hits++;
}
computed_pi = 4.0*(double)hits/((double)points);
printf("Computed PI = %16.10lf\n", computed_pi);
Implementar una versin en MPI que permita su clculo en paralelo.

Solucin: La paralelizacin es sencilla en este caso dado que el programa es muy paralelizable. Consiste
en la utilizacin correcta de la rutina MPI_Reduce. Dado que todos los procesos reciben el valor de los
argumentos de entrada, cada proceso solo tiene que calcular la cantidad de nmeros aleatorios que debe
generar.

double rx, ry, computed_pi;


long int i, points_per_proc, points, hits_per_proc, hits;
int myproc, nprocs;

MPI_Comm_rank(MPI_COMM_WORLD, &myproc);
MPI_Comm_size(MPI_COMM_WORLD, &nprocs);

seed = myproc*1234;
points_per_proc = points/nprocs;
hits_per_proc = 0;
for (i=0; i<points_per_proc; i++) {
rx = (double)(rand_r(&seed))/((double)RAND_MAX);
ry = (double)(rand_r(&seed))/((double)RAND_MAX);
if (((rx-0.5)*(rx-0.5)+(ry-0.5)*(ry-0.5))<0.25) hits_per_proc++;
}

hits = 0;
MPI_Reduce(&hits_per_proc, &hits, 1, MPI_LONG, MPI_SUM, 0, MPI_COMM_WORLD);

if (!myproc) {
computed_pi = 4.0*(double)hits/((double)points);
printf("Computed PI = %16.10lf\n", computed_pi);
}

2.3. Tipos de datos


Cuestin 2.31
Supongamos definida una matriz de enteros A[N][N]. Define un tipo derivado MPI y realiza las corres-
pondientes llamadas para el envo desde P0 y la recepcin en P1 de un dato de ese tipo, en los siguientes
casos:
(a) Envo de la tercera fila de la matriz A.
Solucin: En el lenguaje C, los arrays bidimensionales se almacenan por filas, con lo que la
separacin entre elementos de la misma fila es 1. Con tipo derivado vector de MPI sera as:

int A[N][N];
MPI_Status status;
MPI_Datatype newtype;

MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Type_vector(N, 1, 1, MPI_INT, &newtype);
MPI_Type_commit(&newtype);
if (rank==0) {
MPI_Send(&A[2][0], 1, newtype, 1, 0, MPI_COMM_WORLD);
} else if (rank==1) {
MPI_Recv(&A[2][0], 1, newtype, 0, 0, MPI_COMM_WORLD, &status);
}
MPI_Type_free(&newtype);

Una solucin equivalente se obtendra con MPI_Type_contiguous. En este caso, no es realmente


necesario crear un tipo MPI, por estar los elementos contiguos en memoria:

if (rank==0) {
MPI_Send(&A[2][0], N, MPI_INT, 1, 0, MPI_COMM_WORLD);
} else if (rank==1) {
MPI_Recv(&A[2][0], N, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
}
(b) Envo de la tercera columna de la matriz A.
Solucin: La separacin entre elementos de la misma columna es N.

int A[N][N];
MPI_Status status;
MPI_Datatype newtype;

MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Type_vector(N, 1, N, MPI_INT, &newtype);
MPI_Type_commit(&newtype);
if (rank==0) {
MPI_Send(&A[0][2], 1, newtype, 1, 0, MPI_COMM_WORLD);
} else if (rank==1) {
MPI_Recv(&A[0][2], 1, newtype, 0, 0, MPI_COMM_WORLD, &status);
}
MPI_Type_free(&newtype);

Cuestin 2.32
Dado el siguiente fragmento de un programa MPI:
struct Tdatos {
int x;
int y[N];
double a[N];
};

void distribuye_datos(struct Tdatos *datos, int n, MPI_Comm comm) {


int p, pr, pr2;
MPI_Status status;

MPI_Comm_size(comm, &p);
MPI_Comm_rank(comm, &pr);
if (pr==0) {
for (pr2=1; pr2<p; pr2++) {
MPI_Send(&(datos->x), 1, MPI_INT, pr2, 0, comm);
MPI_Send(&(datos->y[0]), n, MPI_INT, pr2, 0, comm);
MPI_Send(&(datos->a[0]), n, MPI_DOUBLE, pr2, 0, comm);
}
} else {
MPI_Recv(&(datos->x), 1, MPI_INT, 0, 0, comm, &status);
MPI_Recv(&(datos->y[0]), n, MPI_INT, 0, 0, comm, &status);
MPI_Recv(&(datos->a[0]), n, MPI_DOUBLE, 0, 0, comm, &status);
}
}
Modificar la funcin distribuye_datos para optimizar las comunicaciones.
(a) Realiza una versin que utilice tipos de datos derivados de MPI, de forma que se realice un envo
en lugar de tres.
Solucin:

void distribuye_datos(struct Tdatos *datos, int n, MPI_Comm comm) {


int p, pr, pr2;
MPI_Status status;
MPI_Comm_size(comm, &p);
MPI_Comm_rank(comm, &pr);

MPI_Datatype Tnuevo;
int longitudes[]={1,n,n};
MPI_Datatype tipos[]={MPI_INT, MPI_INT, MPI_DOUBLE};
MPI_Aint despls[3];
MPI_Aint dir1, dirx, diry, dira;

/* Calculo de los desplazamientos de cada componente */


dir1=MPI_Get_address(datos, &dir1);
dirx=MPI_Get_address(&(datos->x), &dirx);
diry=MPI_Get_address(&(datos->y[0]), &diry);
dira=MPI_Get_address(&(datos->a[0]), &dira);
despls[0]=dirx-dir1;
despls[1]=diry-dir1;
despls[2]=dira-dir1;

MPI_Type_struct(3, longitudes, despls, tipos, &Tnuevo);


MPI_Type_commit(&Tnuevo);

if (pr==0) {
for (pr2=1; pr2<p; pr2++) {
MPI_Send(datos, 1, Tnuevo, pr2, 0, comm);
}
}
else {
MPI_Recv(datos, 1, Tnuevo, 0, 0, comm, &status);
}
}
(b) Realiza una modificacin de la anterior para que utilice primitivas de comunicacin colectiva.
Solucin: Sera idntica a la anterior, excepto el ltimo if, que se cambiara por la siguiente
instruccion:

MPI_Bcast(datos, 1, Tnuevo, 0, comm);

Cuestin 2.33
Supngase que se desea implementar la versin esttica del problema del Sudoku. El proceso 0 genera
una matriz A de tamao n 81, siendo n el nmero de tableros que hay en la matriz.
(a) Escribir el cdigo que distribuye toda la matriz desde el proceso 0 hasta el resto de procesos de
manera que cada proceso reciba un tablero (suponiendo n = p, donde p es el nmero de procesos).
Solucin:

MPI_Scatter(A, 81, MPI_INT, tablero, 81, MPI_INT, 0, MPI_COMM_WORLD);


(b) Supongamos que para implementar el algoritmo en MPI creamos la siguiente estructura en C:
struct tarea {
int tablero[81];
int inicial[81];
int es_solucion;
};
typedef struct tarea Tarea;
Crear un tipo de dato MPI_TAREA que represente la estructura anterior.
Solucin:

MPI_Datatype MPI_TAREA;
int blocklen[3] = { 81, 81, 1 };
MPI_Aint ad1, ad2, ad3, ad4, disp[3];
MPI_Get_address(&t, &ad1);
MPI_Get_address(&t.tablero[0], &ad2);
MPI_Get_address(&t.inicial[0], &ad3);
MPI_Get_address(&t.es_solucion, &ad4);
disp[0] = ad2 - ad1;
disp[1] = ad3 - ad1;
disp[2] = ad4 - ad1;
MPI_Datatype types[3] = { MPI_INT, MPI_INT, MPI_INT };
MPI_Type_create_struct(3, blocklen, disp, types, &MPI_TAREA);
MPI_Type_commit(&MPI_TAREA);

Cuestin 2.34
El siguiente fragmento de cdigo implementa el clculo del cuadrado de una matriz triangular superior
con almacenamiento compacto (se evita almacenar los ceros). La matriz del ejemplo sera:

1 2 3 4
0 5 6 7

0 0 8 9
0 0 0 10
#define DIMN 4
#define triang(i,j,n) ( (i)*(n) + ((i)*((i)-1))/2 + (j) - (i) )

double A[(DIMN*DIMN+DIMN)/2] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
double A2[(DIMN*DIMN+DIMN)/2] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int i, j, k, n = DIMN;

for (i=0; i<n; i++)


for (j=i; j<n; j++)
for (k=i; k<=j; k++)
A2[triang(i,j,n)] += A[triang(i,k,n)]*A[triang(k,j,n)];
(a) Escribe el cdigo MPI para distribuir la matriz triangular superior (asumir que la matriz es cuadra-
da), mediante comunicacin punto a punto y MPI_Pack. Justifica si sera ms apropiado utilizar una
distribucin de bloques de filas o una distribucin cclica por filas (se puede justificar calculando el
coste para cada una de las distribuciones en el caso de ejemplo propuesto 4x4 con por ejemplo 2
procesos).
Solucin: La distribucin cclica por filas es mejor que la distribucin por bloques de filas. Esto
se puede ver tomando el ejemplo dado:

1. Distribucin por bloques de filas. P0 obtiene dos filas con 4 y 3 elementos (total 7) y P1
obtiene dos filas con 2 y 1 elementos (total 3). La media sera 5, y la desviacin estndar es
2.
2. Distribucin cclica por filas. P0 obtiene dos filas con 4 y 2 elementos (total 6) y P1 obtiene
dos filas con 3 y 1 elementos (total 4). La media sera 5, y la desviacin estndar es 1.

if (rank==0) {
for (i=1;i<p;i++) {
pos = 0;
for (j=0;j<n/p;j++) {
MPI_Pack(&A[triang(j*p+i,j*p+i,n)], n-(j*p+i),
MPI_DOUBLE, buffer, bufsize, &pos, MPI_COMM_WORLD);
}
MPI_Send(buffer, pos, MPI_PACKED, i, 0, MPI_COMM_WORLD);
}
} else {
MPI_Recv(buffer, bufsize, MPI_PACKED, 0, 0, MPI_COMM_WORLD, &status);
pos = 0;
for (j=0;j<n/p;j++) {
MPI_Unpack(buffer, bufsize, &pos, &A[triang(j*p+rank,j*p+rank,n)],
n-(j*p+rank), MPI_DOUBLE, MPI_COMM_WORLD);
}
}
(b) Cul sera el tiempo de comunicaciones de dicho programa?
Solucin: En total hay p 1 mensajes, con tiempo de establecimiento (p 1)ts , que enviarn la
matriz completa ((n2 + n)/2 elementos), excepto la parte asociada a P0 (n + n p + n 2 p + ... =
P np 1 n2
i=0 (n i p) = 2p ). La distribucin cclica garantiza que el nmero de elementos recibidos por
cada proceso ser ms o menos el mismo, aproximadamente igual a n2 /(2 p). Por tanto, el coste
de comunicacin ser:
n2
 
tc (n, p) = (p 1) ts + tw
2p

(c) Implementar utilizando MPI la recogida de los resultados para que la matriz final est en P0 .
Solucin:

if (rank==0) {
for (i=1;i<p;i++) {
MPI_Recv(buffer, bufsize, MPI_PACKED, i, 0, MPI_COMM_WORLD, &status);
pos = 0;
for (j=0;j<n/p;j++) {
MPI_Unpack(buffer, bufsize, &pos, &A2[triang(j*p+i,j*p+i,n)],
n-(j*p+i), MPI_DOUBLE, MPI_COMM_WORLD);
}
}
} else {
pos = 0;
for (j=0;j<n/p;j++) {
MPI_Pack(&A2[triang(j*p+rank,j*p+rank,n)], n-(j*p+rank),
MPI_DOUBLE, buffer, bufsize, &pos, MPI_COMM_WORLD);
}
MPI_Send(buffer, pos, MPI_PACKED, 0, 0, MPI_COMM_WORLD);
}
2.4. Comunicadores y topologas
Cuestin 2.41
En un programa MPI, se pretende definir dos comunicadores de manera que en cada comunicador se
realice un procesamiento distinto. Si p es el nmero de procesos, define los comunicadores de manera que
cada uno de ellos contenga la mitad de los procesos (si fuese impar p, el primer comunicador tendra uno
ms).

Solucin:

int p, p1, p2, i;


int *ranks1, *ranks2;

MPI_Status status;
MPI_Comm new_comm1, new_comm2;
MPI_Group group_world, new_group1, new_group2;

MPI_Comm_size(MPI_COMM_WORLD,&p);
p1 = p/2;
p2 = p-p1;
ranks1 = (int*)malloc(p1*sizeof(int));
ranks2 = (int*)malloc(p2*sizeof(int));

for (i=0; i<p1; i++) ranks1[i] = i;


for (i=p1; i<p; i++) ranks2[i-p1] = i;

MPI_Comm_group(MPI_COMM_WORLD, &group_world);
MPI_Group_incl(group_world, p1, ranks1, &new_group1);
MPI_Comm_create(MPI_COMM_WORLD, new_group1, &new_comm1);
MPI_Group_incl(group_world, p2, ranks2, &new_group2);
MPI_Comm_create(MPI_COMM_WORLD, new_group2, &new_comm2);

También podría gustarte