Ejercicios Resueltos
Ejercicios Resueltos
Ejercicios Resueltos
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
El siguiente cdigo secuencial implementa dicha operacin para el caso de una matriz cuadrada.
#include <math.h>
#define DIMN 100
#include <math.h>
#define DIMN 100
(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.
Solucin:
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
/* 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:
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
double funcion(int n, double u[], double v[], double w[], double z[])
{
int i;
double sv,sw,res;
0 1 1 2 3 5 8 13 21 34 55 ...
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;
}
}
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 .
Cuestin 2.12
Dada la siguiente funcin:
double funcion()
{
int i,n,j;
double *v,*w,*z,sv,sw,x,res;
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
n1 n1 n1
! n1
X X X X
2+ 1 = (2n + n) = 3n2
j=0 i=0 i=0 j=0
(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);
calcula_v(n,v); /* tarea 1 */
MPI_Recv(w, n, MPI_DOUBLE, 1, 0, MPI_COMM_WORLD, &status);
calcula_w(n,w); /* tarea 2 */
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) */
...
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
Tiempo de ejecucin paralelo: tiempo de comunicaciones. Los mensajes que se transmiten son:
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).
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:
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.
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
(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.
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);
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;
}
}
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:
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);
if (p) a = malloc(nb*sizeof(int));
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:
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.
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);
}
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);
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];
};
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:
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;
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:
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_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;
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:
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));
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);