22
ETSISI - UPM Procesamiento Paralelo Curso 20/21 6 de Octubre 2020 Práctica 2 Método de Monte Carlo y Superaceleración

Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

  • Upload
    others

  • View
    3

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

6 de Octubre 2020

Práctica 2

Método de Monte Carlo y Superaceleración

Page 2: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 1

ÍNDICE

1 Objetivos ......................................................................................................................................................... 2

2 El método de Monte Carlo aplicado al cálculo de PI ...................................................................................... 2 2.1 El método ................................................................................................................................................ 2 2.2 Tres versiones secuenciales ..................................................................................................................... 3 2.3 Un modelo paralelo ................................................................................................................................. 6 2.4 Tablas de tiempos .................................................................................................................................... 9

3 Divide y vencerás con superaceleración .........................................................................................................10 3.1 Problemática de medición de tiempos en sistemas multinúcleo ............................................................ 10 3.2 Comprobando los efectos del optimizador de código ............................................................................ 12 3.3 Comprobando los efectos de las técnicas de “prefetch” ........................................................................ 12 3.4 Análisis del sistema de caches y el conjunto de trabajo de los procesos ............................................... 16 3.5 Ejemplo del uso de las primitivas scatter y reduce ................................................................................ 17

4 ANEXOS........................................................................................................................................................19 4.1 Fichero Makefile ................................................................................................................................... 19 4.2 Fichero cuentaPar2.c ............................................................................................................................. 20

Page 3: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 2

1 Objetivos

• Comprender el modelo de Monte Carlo aplicado al cálculo del número PI

• Problemática de paralelizar basándose en generación de números aleatorios

• Primeros problemas para determinar la condición de terminación

• Problemática de la medición de tiempos: optimizador de código, técnicas de prefetch y planificación de procesos en sistemas multinúcleo

• Ejemplo del modelo divide y vencerás que evidencia superaceleración

• Uso de primitivas de comunicación colectiva: scatter y reduce

2 El método de Monte Carlo aplicado al cálculo de PI

2.1 El método

De los dos métodos explicados en las clases de teoría, vamos a seguir el primero, cuya representación gráfica es la siguiente:

Dado que el área del círculo es Π y el área del cuadrado que lo circunscribe es 4, la relación entre ambas áreas es Π/4.

Esta relación de áreas es la misma en el cuadrante superior derecha, donde podemos imaginar unos ejes cartesianos con origen en el centro del círculo y amplitud 1 en cada eje.

El método consiste en elegir al azar puntos del cuadrante (con coordenadas x,y comprendidas entre 0 y 1), de tal forma que contabilizaremos los puntos que caen dentro del semicírculo.

Si el método de elegir números al azar es bueno y probamos suficientes veces, sea M, la relación entre puntos que nos han caído dentro del semicírculo y puntos totales escogidos, debe ser una aproximación a Π/4.

El esbozo de programa secuencial que calcularía PI de esta forma es el siguiente:

enCirculo = 0; for (i=1; i<=M; i++) { x = aleatorio(0.0, 1.0); y = aleatorio(0.0, 1.0); if ((x*x + y*y)<=1.0) enCirculo++; } PI = (4.0 * enCirculo) / (double) M;

2

2 1

Page 4: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 3

2.2 Tres versiones secuenciales

En este apartado se va a probar la ejecución del algoritmo básico contado en el apartado anterior, pero con tres variantes que tienen que ver con la condición de terminación del cálculo.

Antes de nada, al igual que en la primera práctica, crear el directorio p2 para trabajar en esta segunda y traerse desde el servidor todos los ficheros que residan en el correspondiente directorio del usuario “propar00”, entre los que se encuentran piUno.c, piDos.c y piTres.c que tienen el código completo de las tres versiones que se van a probar

La primera versión del programa es como el esbozo presentado en la sección anterior. El cómputo termina tras un bucle que se ejecuta un cierto número de veces, cuyo valor se fija con un parámetro en la línea de comandos al invocar el ejecutable. El código es el siguiente:

// PCM. Procesamiento Paralelo Curso 05/06 EUI 10/03/06 | // | // piUno.c: Programa que calcula el numero PI mediante el metodo | // de Monte Carlo basado en circulo de radio 1 inscrito en | // cuadrado de lado 2. | // Terminacion controlada por: Numero de iteraciones | #include <assert.h> #include <math.h> #include <stdlib.h> #include <stdio.h> #include <sys/time.h> //-------------------------------------------------------------------- int main( int argc, char *argv[] ) { int i, iteraciones, enCirculo=0; double x, y, pi; struct timeval t0, t1, t; // Control parametro => Numero de iteraciones if (argc != 2) { printf ("Uso: piUno numIteraciones \n"); exit(0); } iteraciones = atoi(argv[1]); assert (gettimeofday (&t0, NULL) == 0); for (i=1; i<=iteraciones; i++) { x = (double) random() / (double) RAND_MAX; y = (double) random() / (double) RAND_MAX; if ((x*x + y*y) <= 1.0) enCirculo++; } assert (gettimeofday (&t1, NULL) == 0); timersub(&t1, &t0, &t); pi = (4.0 * enCirculo) / (double) i; printf ("Valor de PI = %.6lf\n", pi); printf ("Error = %.6lf\n", fabs(pi-M_PI)); printf ("Tiempo = %ld:%ld(seg:mseg)\n", t.tv_sec, t.tv_usec/1000); return 0; }

Page 5: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 4

Generar el ejecutable de piUno.c tecleando: make piUno. Este comando ejecuta la compilación tal y como está definida en el fichero Makefile

Ejecutar varias veces este comando con distintos valores de iteraciones y rellenar la Tabla-1 de tiempos

Reflexionar sobre las ganancias o pérdidas de precisión del cálculo de PI según hemos ido aumentando el número de iteraciones

Ahora vamos a suponer que conocemos el valor real del número PI (constante M_PI definida en la biblioteca matemática math.h). Podemos cambiar el modelo de nuestro programa para que haga iteraciones hasta que compute un valor de PI con un error respecto del PI real, menor que un parámetro determinado.

Precisamente esto es lo que hace el programa piDos.c cuyo código es el siguiente:

// PCM. Procesamiento Paralelo Curso 05/06 EUI 10/03/06 | // | // piDos.c: Programa que calcula el numero PI mediante el metodo | // de Monte Carlo basado en circulo de radio 1 inscrito en | // cuadrado de lado 2. | // Terminacion controlada por: Error respecto de M_PI | #include <assert.h> #include <math.h> #include <stdlib.h> #include <stdio.h> #include <sys/time.h> #define MAX_ITER 1000000000 int main( int argc, char *argv[] ) { int i, enCirculo=0; double x, y, pi, cotaError; struct timeval t0, t1, t; // Control parametro => Cota del error if (argc != 2) { printf ("Uso: piDos cotaError \n"); exit(0); } cotaError = atof(argv[1]); assert (gettimeofday (&t0, NULL) == 0); for (i=1; i<=MAX_ITER; i++) { x = (double) random() / (double) RAND_MAX; y = (double) random() / (double) RAND_MAX; if ((x*x + y*y) <= 1.0) enCirculo++; pi = (4.0 * enCirculo) / (double) i; if (fabs(pi - M_PI) < cotaError) break; } assert (gettimeofday (&t1, NULL) == 0); timersub(&t1, &t0, &t); printf ("Valor de PI = %.10lf en iteracion = %d\n", pi, i); printf ("Tiempo = %ld:%ld(seg:mseg)\n", t.tv_sec, t.tv_usec/1000); return 0; }

Page 6: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 5

Generar el ejecutable de piDos.c tecleando: make piDos

Ejecutar varias veces este comando con distintos valores de cota del error y rellenar la Tabla-2 de tiempos

Comparar los resultados obtenidos en esta prueba (piDos) respecto de la anterior (piUno)

Finalmente, vamos a adoptar otro modelo de terminación que persigue acotar el error, pero sin conocer el valor real de PI. Una forma de hacerlo es iterar hasta que se consigan dos valores consecutivos de PI que se diferencien en menos de una cota de error fijada como parámetro. Esto es lo que hace el programa piTres.c cuyo código es el siguiente:

// PCM. Procesamiento Paralelo Curso 05/06 EUI 10/03/06 | // | // piTres.c: Programa que calcula el numero PI mediante el metodo | // de Monte Carlo basado en circulo de radio 1 inscrito en | // cuadrado de lado 2. | // Terminacion controlada por: Error respecto piAnterior | #include <assert.h> #include <math.h> #include <stdlib.h> #include <stdio.h> #include <sys/time.h> #define MAX_ITER 1000000000 //-------------------------------------------------------------------- int main( int argc, char *argv[] ) { int i, enCirculo=0; double x, y, piActual, piAnterior=3.0, cotaError, elError; struct timeval t0, t1, t; // Control parametro => Error respecto piAnterior if (argc != 2) { printf ("Uso: piTres cotaError \n"); exit(0); } cotaError = atof(argv[1]); assert (gettimeofday (&t0, NULL) == 0); for (i=1; i<=MAX_ITER; i++) { x = (double) random() / (double) RAND_MAX; y = (double) random() / (double) RAND_MAX; if ((x*x + y*y) <= 1.0) enCirculo++; piActual = (4.0 * enCirculo) / (double) i; elError = fabs(piActual - piAnterior); if ((elError > 0.0) && (elError < cotaError)) break; piAnterior = piActual; } assert (gettimeofday (&t1, NULL) == 0); timersub(&t1, &t0, &t); printf ("Valor de PI = %.6lf en iteracion = %d\n", piActual, i); printf ("Error = %.6lf\n", fabs(piActual - M_PI)); printf ("Tiempo = %ld:%ld(seg:mseg)\n", t.tv_sec, t.tv_usec/1000); return 0; }

Page 7: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 6

Generar el ejecutable de piTres.c tecleando: make piTres

Ejecutar varias veces este comando con distintos valores para la cota del error y rellenar la Tabla-3 de tiempos

Comparar los resultados obtenidos en esta prueba (piTres) respecto de las anteriores (piUno y piDos)

2.3 Un modelo paralelo

El problema más serio a la hora de paralelizar el cálculo de PI a partir de los ejemplos secuenciales, es la gestión de números aleatorios. Hacer que un maestro reparta números aleatorios a los esclavos es muy ineficiente en nuestro tipo de máquina, así que optaremos por una solución consistente en que cada esclavo inicialice su serie de números aleatorios (función srandom) a partir de su identificador de proceso.

En cuanto al modelo de control de terminación del cómputo, descartaremos la versión de piDos, ya que no parece muy realista conocer previamente el valor que se está computando, así que optaremos por adaptar la versión de piTres ya que parece ofrecer cierta consistencia en el sentido de obtener mejores precisiones según aumentamos la precisión del error entre cálculos consecutivos de PI.

El programa será tipo SPMD y se denominará piPar.c, de hecho, se suministra un esqueleto del mismo. La idea es que todos los procesos que se creen –nosotros probaremos sólo con dos y tres-, realizarán los cálculos según el modelo de piTres, es decir, hasta conseguir una diferencia, entre dos cómputos seguidos, menor de una cota de error fijada como parámetro de la línea de comando.

De todos los procesos, uno de ellos –el 0- se comportará como maestro y el resto como esclavos. El maestro recibirá los resultados de los esclavos (iteraciones totales y cuántas veces cayó en círculo) y los compondrá –con los resultados que él obtuvo- para dar el resultado definitivo de PI y el error cometido.

El esqueleto del programa piPar.c es el siguiente:

// PCM. Procesamiento Paralelo Curso 05/06 EUI 10/03/06 | // | // piPar.c: Programa que calcula el numero PI mediante el metodo | // de Monte Carlo basado en circulo de radio 1 inscrito en | // cuadrado de lado 2. Version paralela. (ESQUELETO) | #include <assert.h> #include <math.h> #include <stdlib.h> #include <stdio.h> #include <sys/time.h> #include "mpi.h" #define MAX_PROCESOS 16 #define MAX_ITER 1000000000

// Continua en la página siguiente

Page 8: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 7

//-------------------------------------------------------------------- void computar(int yo, double cotaError, int *numVeces, int *dentro) { int i, enCirculo=0; double x, y, elError, piAnterior=3.0, piActual; // Evitar misma secuencia de numeros aleatorios srandom((unsigned int) yo+1); for (i=1; i<=MAX_ITER; i++) { x = (double) random() / (double) RAND_MAX; y = (double) random() / (double) RAND_MAX; if ((x*x + y*y) <= 1.0) enCirculo++; piActual = (double) (4.0 * enCirculo) / (double) i; elError = fabs(piActual - piAnterior); if ((elError > 0.0) && (elError < cotaError)) break; piAnterior = piActual; } printf ("%d muestras tomadas por %d\n", i, yo); *numVeces = i; *dentro = enCirculo; } //-------------------------------------------------------------------- void esclavo(int yo, double cotaError) { // Incluir el codigo del esclavo } //-------------------------------------------------------------------- void maestro(double cotaError, int numEsclavos) { int muestras; double pi; struct timeval t0, t1, t; assert (gettimeofday (&t0, NULL) == 0); // Incluir el codigo del maestro, que tambien realizara computos assert (gettimeofday (&t1, NULL) == 0); timersub(&t1, &t0, &t); printf ("Valor de PI = %.6lf en %d muestras\n", pi, muestras); printf ("Error real = %.6lf\n", M_PI - pi); printf ("Tiempo = %ld:%ld(seg:mseg)\n", t.tv_sec, t.tv_usec/1000); } //-------------------------------------------------------------------- int main(int argc, char *argv[]) { int yo, numProcesos; double cotaError; setbuf (stdout, NULL); MPI_Init (&argc, &argv); MPI_Comm_rank (MPI_COMM_WORLD, &yo); MPI_Comm_size (MPI_COMM_WORLD, &numProcesos);

Page 9: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 8

// Control del numero de procesos if ((numProcesos < 2) || (numProcesos > MAX_PROCESOS)) { if (yo == 0) printf ("Numero de procesos incorrecto\n"); MPI_Finalize(); exit(0); } // Control del numero de parametros if (argc != 2) { if (yo == 0) printf ("Uso: piPar cotaError \n"); MPI_Finalize(); exit(0); } cotaError = atof(argv[1]); if (yo == 0) maestro(cotaError, numProcesos-1); else esclavo(yo, cotaError); MPI_Finalize(); return 0; } Completar el código de piPar.c para que realice lo que se está pidiendo

Generar el ejecutable de piPar.c tecleando: make piPar

Ejecutar varias veces este comando con dos procesos –maestro y un esclavo- (utilizar la opción -n 2) y distintos valores para la cota del error y rellenar la Tabla-4 de tiempos. Se utilizará, por lo tanto, un único PC

Comprobar que el programa funciona correctamente si se utilizan tres procesos. Probar con una cota de error de 0,0000001

Page 10: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 9

2.4 Tablas de tiempos

Tabla-1. Tiempos de la versión piUno (#iteraciones)

# Iteraciones PI Error Tiempo (seg:mseg)

100.000 3,141 _ _ _ 0,000 _ _ _ :

1.000.000 3,141 _ _ _ 0,000 _ _ _ :

10.000.000 3,141 _ _ _ 0,000 _ _ _ :

100.000.000 3,141 _ _ _ 0,000 _ _ _ :

1.000.000.000 3,141 _ _ _ 0,000 _ _ _ :

Tabla-2. Tiempos de la versión piDos (error respecto de PI = 3.141592653590)

# Iteraciones PI Cota del Error Tiempo (seg:mseg)

3,14159 _ _ _ _ _ 0.000001 :

3,14159 _ _ _ _ _ 0.0000001 :

3,14159 _ _ _ _ _ 0.00000001 :

3,14159 _ _ _ _ _ 0.000000001 :

3,14159 _ _ _ _ _ 0.0000000001 :

Tabla-3. Tiempos de la versión piTres (error respecto de piAnterior)

# Iteraciones PI Error Cota del Error T. (seg:mseg)

3,138 _ _ _ 0,002 _ _ _ 0.00001 :

3,141 _ _ _ 0,000 _ _ _ 0.000001 :

3,141 _ _ _ 0,000 _ _ _ 0.0000001 :

3,141 _ _ _ 0,000 _ _ _ 0.00000001 :

3,141 _ _ _ 0,000 _ _ _ 0.000000001 :

Tabla-4. Tiempos de la versión piPar (Con 2 procesos y un único PC)

# Iteraciones PI Error Cota del Error T. (seg:mseg)

3,1_ _ _ _ _ 0,00_ _ _ _ 0.00001 :

3,14_ _ _ _ 0,00_ _ _ _ 0.000001 :

3,141 _ _ _ 0,000 _ _ _ 0.0000001 :

3,141 _ _ _ 0,000 _ _ _ 0.00000001 :

3,141 _ _ _ 0,000 _ _ _ 0.000000001 :

Page 11: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 10

3 Divide y vencerás con superaceleración

En este último apartado vamos a paralelizar un algoritmo secuencial que cuenta el número de apariciones de un número en un array muy grande.

Si hiciésemos una prueba con un array de 100 millones de enteros –cuya ocupación es de casi medio giga-, veríamos que nuestro equipo ejecutaría la versión secuencial en unos 150 milisegundos. Para obtener tiempos superiores al segundo, tendríamos que tener un array más grande que toda la memoria disponible -8GB-, así que nos vamos a conformar con un array de un tamaño máximo de 8.000.000 de elementos. Para simular una búsqueda en un array mayor –y así tener tiempos razonablemente altos-, lo que haremos es buscar muchas veces (NUM_VECTORES) en el mismo array.

Este último apartado va a depender bastante de la jerarquía de memoria de cada PC –en nuestro cluster todos los PC’s son iguales-, de forma que vamos a detallarla:

Figura: Jerarquía de cachés en un procesador Intel Core i3 8100

Todos los PC’s tienen, por cada núcleo, L1I y L1D de 32KB privada, así como una L2 privada de 256KB. Además, el procesador cuenta con una L3 de 6GB como último nivel de cache compartida entre los cuatro núcleos del procesador. Esta estructura tendrá su importancia en el apartado 3.4 “Análisis del sistema de caches y el conjunto de trabajo de los procesos”.

3.1 Problemática de medición de tiempos en sistemas multinúcleo

La primera versión del programa secuencial “cuentaSecSimple.c” es la siguiente:

#include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <assert.h> #define MAX_CARDINALIDAD 8000000 // Evita pasarse de limites #define NUM_VECTORES 10000 // Simula un vector todavia mayor #define MAX_ENTERO 1000 #define NUM_BUSCADO 5 int main (int argc, char *argv[]){ struct timeval t0, tf, t; int i, j, laCardinalidad, numVeces, *vector;

L3 6GB

Page 12: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 11

// Control parametro => Cardinalidad del vector if (argc != 2) { printf ("Uso: cuentaSecSimple cardinalidad\n"); return 0; } laCardinalidad = atoi(argv[1]); assert (laCardinalidad > 0); assert (laCardinalidad <= MAX_CARDINALIDAD); // Se pide memoria e inicializa el vector vector = malloc (laCardinalidad * 4); for (i=0; i<laCardinalidad; i++) vector[i] = random() % MAX_ENTERO; // Se contabilizan las apariciones del numero buscado assert (gettimeofday (&t0, NULL) == 0); numVeces = 0; for (i=0; i<NUM_VECTORES; i++) for (j=0; j<laCardinalidad; j++) if (vector[j] == NUM_BUSCADO) numVeces++; assert (gettimeofday (&tf, NULL) == 0); timersub (&tf, &t0, &t); printf ("Veces que aparece el %d = %d\n", NUM_BUSCADO, numVeces); printf ("Tiempo: %ld:%3ld(seg:mseg)\n", t.tv_sec, t.tv_usec/1000); return 0; }

Generar el ejecutable de cuentaSecSimple.c tecleando: make cuentaSecSimple

Ejecutar tres veces este programa con un parámetro de entrada de 1.000.000 anotando, en la Tabla-5, el mayor y el menor tiempo obtenido y calculando el % de variación entre ambos tiempos. Veremos que los tiempos no varían mucho: el procesador dispone de cuatro núcleos y tan sólo le estamos generando cómputo intensivo para un único núcleo

Vamos a repetir el experimento, pero lanzando dos procesos iguales con el objetivo de generar trabajo intensivo para dos cores. Para ello, teclear lo siguiente:

cuentaSecSimple 1000000 & cuentaSecSimple 1000000 Anotar, en la Tabla-5, los tiempos obtenidos. Puede observarse que ahora el tiempo de ejecución varía algo más

Volver a repetir el experimento, para tres y cuatro procesos iguales y anotar los datos en las casillas correspondientes de la Tabla-5. Rellenar la última fila de la Tabla-5 con el menor y el mayor de los tiempos y calcular el % de variación global

Puede observarse cómo aumentan los tiempos según se va cargando más al procesador. ¿Cuál puede ser la explicación?

Page 13: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 12

3.2 Comprobando los efectos del optimizador de código

Ahora vamos a comprobar el efecto de la optimización de código por parte del compilador. Para ello, vamos a generar este mismo ejecutable pero compilando con la opción –O3 del gcc que supone el mayor nivel de optimización. Hacer lo siguiente:

Generar el ejecutable de cuentaSecSimple.c tecleando: make cuentaSecSimpleO3. Fijarse que en la línea de compilación aparece la opción –O3

Ejecutar cuentaSecSimpleO3 con la misma cardinalidad de antes (1.000.000) y anotar en la Tabla-6 el nuevo tiempo de ejecución y la mejora obtenida (cuántas veces más rápido se ejecuta la nueva versión respecto del mínimo de la Tabla-5). La mejora tan sensible de tiempos se consigue en la optimización del doble bucle for cuyo cuerpo principal es un if

3.3 Comprobando los efectos de las técnicas de “prefetch”

Para poder mostrar el efecto de superaceleración, vamos a recorrer el array de una forma peculiar. En vez de recorrer desde el primer elemento hasta el último siguiendo un patrón puramente secuencial -previsible por los procesadores actuales que utilizan técnicas de prefetch avanzadas-, lo que haremos es recorrerlo un poco a trompicones. La idea es ir recorriendo el array en trozos de 1.000 elementos (LONG_PATRON), siguiendo un patrón de acceso aleatorio dentro de cada trozo.

Un ejemplo de este programa en versión secuencial es el siguiente:

// cuentaSec.c: Cuenta el numero de veces que aparece un numero en | // un vector muy grande. | #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <assert.h> #define MAX_CARDINALIDAD 8000000 // Evita pasarse de limites #define MAX_ENTERO 1000 #define NUM_VECTORES 10000 // Simula vector todavia mayor #define NUM_BUSCADO 5 #define LONG_PATRON 1000 // Patron de acceso a memoria // para anular tecnica prefetch int main (int argc, char *argv[]){ struct timeval t0, tf, t; int i, j, k, laCardinalidad, numVeces, *vector; int patronAcceso[LONG_PATRON], strideElegidos[LONG_PATRON]; // Control parametro => Cardinalidad del vector if (argc != 2) { printf ("Uso: cuentaSec cardinalidad\n"); return 0; } laCardinalidad = atoi(argv[1]); assert (laCardinalidad > 0); assert (laCardinalidad <= MAX_CARDINALIDAD); // Se forma el patron de acceso a memoria

Page 14: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 13

for (i=0; i<LONG_PATRON; i++) strideElegidos[i] = 0; for (i=0; i<LONG_PATRON; i++) { do j = random() % LONG_PATRON; while (strideElegidos[j] == 1); patronAcceso[i] = j; strideElegidos[j] = 1; } // Se inicializa el vector vector = malloc (laCardinalidad * 4); for (i=0; i<laCardinalidad; i++) vector[i] = random() % MAX_ENTERO; // Se contabilizan las apariciones del numero buscado assert (gettimeofday (&t0, NULL) == 0); numVeces = 0; for (i=0; i<NUM_VECTORES; i++) for (j=0; j<laCardinalidad; j+=LONG_PATRON) for (k=0; k<LONG_PATRON; k++) if (vector[j+patronAcceso[k]] == NUM_BUSCADO) numVeces++; assert (gettimeofday (&tf, NULL) == 0); timersub (&tf, &t0, &t); printf ("Veces que aparece el %d = %d\n", NUM_BUSCADO, numVeces); printf ("Tiempo: %ld:%3ld(seg:mseg)\n", t.tv_sec, t.tv_usec/1000); return 0; }

Si nos fijamos, el programa debe invocarse con un parámetro “cardinalidad” que determina el tamaño del array donde se busca y que está limitado precisamente a 8.000.000 tal y como se indica en la constante MAX_CARDINALIDAD. Si fijamos una cardinalidad de 200.000, tendremos un array de ese tamaño, donde se meten números al azar comprendidos entre 0 y 1.000 (MAX_ENTERO) y en él se busca (10.000 veces) el número indicado en la constante NUM_BUSCADO.

Antes de paralelizar este programa, vamos a comprobar cómo se comporta respecto del programa cuentaSecSimpleO3 y así evidenciar el efecto del prefetch. Para ello:

Generar el ejecutable de cuentaSec.c tecleando: make cuentaSec. Observar que también se compila con la opción O3 de optimización de código

Ejecutar cuentaSec para el tamaño indicado (1.000.000) y anotar el tiempo obtenido, así como la pérdida en eficiencia, en la Tabla-7. Podremos comprobar que hay mucha diferencia y el menor tiempo de la primera versión se debe, precisamente, a la técnica de prefetch que consigue ocultar bastante la latencia de los fallos de cache

Ahora se trata de generar una versión paralela sencilla de este programa (ya utilizando el mismo nivel de optimización “O3”), en la que el maestro reparta el trabajo entre sí mismo y el resto de procesos (sin usar scatter ni reduce). El reparto consistirá en dividir el array en tantos trozos como procesos.

Un esqueleto de este programa, al que denominaremos cuentaPar1.c, es el que aparece en la página siguiente.

Page 15: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 14

// cuentaPar1.c: Cuenta aparaciones de un numero en un vector muy | // grande. Version paralela simple (division entre | // esclavos de forma plana) del programa cuentaSec | // ESQUELETO | #include <assert.h> #include <sys/time.h> #include <stdlib.h> #include <stdio.h> #include "mpi.h" #define MAX_CARDINALIDAD 8000000 // Evita pasarse de limites #define MAX_ENTERO 1000 #define NUM_VECTORES 10000 // Simula vector todavia mayor #define NUM_BUSCADO 5 #define LONG_PATRON 1000 // Patron de acceso a memoria // para evitar tecnica prefetch // VARIABLES GLOBALES static int *vector; // Vector para el maestro y rodaja // para el esclavo static int longVector; // Longitud del vector static int longRodaja; // Longitud de las rodajas static int patronAcceso[LONG_PATRON]; // El patron de acceso //-------------------------------------------------------------------- // Inicializa de forma aleatoria el patron de acceso a las rodajas //-------------------------------------------------------------------- void formarPatronDeAcceso( void ) { int i, j, strideElegidos[LONG_PATRON]; for (i=0; i<LONG_PATRON; i++) strideElegidos[i] = 0; for (i=0; i<LONG_PATRON; i++) { do j = random() % LONG_PATRON; while (strideElegidos[j] == 1); patronAcceso[i] = j; strideElegidos[j] = 1; } } //-------------------------------------------------------------------- // Calcula el numero de veces que aparece "NUM_BUSCADO" en la rodaja // de tamanio "longRodaja" apuntada por "vector" //-------------------------------------------------------------------- int vecesQueAparece (void) { int i, j, k, numVeces; numVeces = 0; for (i=0; i<NUM_VECTORES; i++) for (j=0; j<longRodaja; j+=LONG_PATRON) for (k=0; k<LONG_PATRON; k++) if (vector[j+patronAcceso[k]] == NUM_BUSCADO) numVeces++; return numVeces; } //SIGUE EN LA PAGINA SIGUIENTE

Page 16: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 15

//-------------------------------------------------------------------- void esclavo (void) { // Formar el patron de acceso y pedir memoria para el vector formarPatronDeAcceso(); vector = malloc (longRodaja * 4); // Recibir mi trozo // Computar mi trozo // Enviar mi resultado } //-------------------------------------------------------------------- void maestro (int numProcesos) { int i, j, totalNumVeces, numVeces; struct timeval t0, tf, t; // Formar el patron de acceso e inicializar el vector formarPatronDeAcceso(); vector = malloc (longVector * 4); for (i=0; i<longVector; i++) vector[i] = random() % MAX_ENTERO; assert (gettimeofday (&t0, NULL) == 0); // Repartir trabajo // Computar mi trozo // Recoger resultados assert (gettimeofday (&tf, NULL) == 0); timersub(&tf, &t0, &t); printf ("Numero de veces que aparece el %d = %d\n", NUM_BUSCADO, totalNumVeces); printf ("tiempo = %ld:%3ld\n", t.tv_sec, t.tv_usec/1000); } //-------------------------------------------------------------------- int main( int argc, char *argv[] ) { int yo, numProcesos; setbuf (stdout, NULL); MPI_Init (&argc, &argv); MPI_Comm_rank (MPI_COMM_WORLD, &yo); MPI_Comm_size (MPI_COMM_WORLD, &numProcesos); // Control parametro => Cardinalidad del vector if (argc != 2) { if (yo == 0) printf ("Uso: cuentaPar1 cardinalidad\n"); MPI_Finalize(); return 0; } longVector = atoi(argv[1]); longRodaja = longVector / numProcesos; if (yo == 0) maestro(numProcesos); else esclavo(); MPI_Finalize(); return 0; }

Completar el código del programa cuentaPar1.c ¡ Sin utilizar scatter ni reduce !

Generar el ejecutable de cuentaPar1.c tecleando: make cuentaPar1

Page 17: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 16

Rellenar la primera parte de la ejecución secuencial de la Tabla-8. Para ello, trasladar el tiempo de la ejecución ya realizada para una cardinalidad de 1.000.000 “cuentaSec”

Ejecutar “cuentaSec 2000000” para rellenar el otro dato de ejecución secuencial de la Tabla-8

Ejecutar el programa paralelo (utilizar la opción -n 4) con una cardinalidad de 1.000.000, utilizando sólo nuestro PC, y anotar en la Tabla-8 el tiempo de ejecución. Ejecutarlo tres veces y tomar el menor de los tiempos. Calcular la aceleración y la eficiencia

Hacer lo mismo (lanzar una ejecución con 4 procesos y cardinalidad 1.000.000) pero haciendo que ahora se ejecuten dos procesos en nuestro PC y otros dos en el de al lado. Anotar el tiempo correspondiente y calcular la aceleración y la eficiencia.

¿Qué conclusiones pueden sacarse de esta primera prueba paralela?

Repetir ahora las ejecuciones paralelas para una cardinalidad de 2.000.000 y rellenar los campos que faltan de la Tabla-8.

¿Qué conclusiones pueden sacarse tras esta segunda prueba paralela, donde hemos duplicado la cardinalidad del vector?

3.4 Análisis del sistema de caches y el conjunto de trabajo de los procesos

Para acabar de entender qué es lo que pasa:

Ejecutar el programa cuentaSec con los valores indicados en la Tabla-9 y anotar los tiempos obtenidos

Calcular la columna de ratio de aumento de tiempo al aumentar el tamaño del problema al doble y traspasar estos datos a la Gráfica-1

Page 18: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 17

Gráfica-1: Efecto del sistema de caches en la ejecución de los procesos

Intentar explicar qué es lo que pasa de extraño en esta gráfica. Viene bien recordar que tenemos una caché compartida de nivel 3 de 6MBytes. Comentar con el profesor las conclusiones que se sacan:

3.5 Ejemplo del uso de las primitivas scatter y reduce

Se suministra otra versión de este mismo programa paralelo utilizando las primitivas de comunicación colectiva. El programa se da completamente programado y aparece en los anexos.

Si sobra tiempo, puede compilarse el programa cuentaPar2 y comparar los resultados obtenidos con los correspondientes a cuentaPar1.

Page 19: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 18

Tabla-5. Problemática para la medición de tiempos [cuentaSecSimple 1000000]

Número de procesos Tmin Tmax % variación 1

2

3

4

Variación global

Tabla-6. Efecto del optimizador de código

Comando Tiempo Mejora cuentaSecSimpleO3 1000000

Tabla-7. Efecto de la técnica de prefetch (Engañar al prefetch)

Comando Tiempo Pérdida cuentaSec 1000000

Tabla-8. Efecto de superaceleración (cuentaSec y cuentaPar1 en uno y dos PC’s)

1.000.000 2.000.000 PC’s Cores Tiempo Aceleración Eficiencia Tiempo Aceleración Eficiencia

1 1

1 4

2 4

Tabla-9. Análisis caches y conjunto de trabajo

Comando: cuentaSec N Tiempo Ratio Ti+1/Ti

125.000

250.000

500.000

1.000.000

2.000.000

4.000.000

8.000.000

Page 20: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 19

4 ANEXOS

4.1 Fichero Makefile

#------------------------------------------------------------------# # PCM. Procesamiento Paralelo Curso 19/20 ETSISI 23/09/19 # # # # Makefile para el desarrollo de las pruebas de los programas # # relacionados con la practica 2 de MPI. # #------------------------------------------------------------------# CC = mpicc todo: piUno piDos piTres piPar cuentaSecSimple cuentaSec \ cuentaSecSimpleO3 cuentaPar1 cuentaPar2 piUno: piUno.c gcc -Wall -ggdb piUno.c -o piUno piDos: piDos.c gcc -Wall -ggdb piDos.c -o piDos piTres: piTres.c gcc -Wall -ggdb piTres.c -o piTres piPar: piPar.c $(CC) -Wall -ggdb piPar.c -o piPar cuentaSecSimple: cuentaSecSimple.c gcc -Wall -ggdb cuentaSecSimple.c -o cuentaSecSimple cuentaSecSimpleO3: cuentaSecSimple.c gcc -Wall -ggdb -O3 cuentaSecSimple.c -o cuentaSecSimpleO3 cuentaSec: cuentaSec.c gcc -Wall -ggdb -O3 cuentaSec.c -o cuentaSec cuentaPar1: cuentaPar1.c $(CC) -Wall -ggdb -O3 cuentaPar1.c -o cuentaPar1 cuentaPar2: cuentaPar2.c $(CC) -Wall -ggdb -O3 cuentaPar2.c -o cuentaPar2 borrar: rm *.o piUno piDos piTres piPar cuentaSecSimple \ cuentaSecSimpleO3 cuentaSec cuentaPar1 cuentaPar2

Page 21: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 20

4.2 Fichero cuentaPar2.c

//------------------------------------------------------------------+ // PCM. Procesamiento Paralelo Curso 19/20 ETSISI 23/09/19 | // | // cuentaPar2.c: Cuenta aparaciones de un numero en un vector muy | // grande. Version paralela que utiliza las funciones | // scatter y reduce. | //------------------------------------------------------------------+ #include <assert.h> #include <sys/time.h> #include <stdlib.h> #include <stdio.h> #include "mpi.h" #define MAX_CARDINALIDAD 8000000 // Evita pasarse de limites #define MAX_ENTERO 1000 #define NUM_VECTORES 10000 // Simula vector todavia mayor #define NUM_BUSCADO 5 #define LONG_PATRON 1000 // Patron de acceso a memoria // para anular tecnica prefetch // VARIABLES GLOBALES static int *vector; // Vector para el maestro static int *rodaja; // rodaja para el maestro y esclavo static int longVector; static int longRodaja; static int patronAcceso[LONG_PATRON]; //-------------------------------------------------------------------- // Inicializa de forma aleatoria el patron de acceso a las rodajas //-------------------------------------------------------------------- void formarPatronDeAcceso( void ) { int i, j, strideElegidos[LONG_PATRON]; for (i=0; i<LONG_PATRON; i++) strideElegidos[i] = 0; for (i=0; i<LONG_PATRON; i++) { do j = random() % LONG_PATRON; while (strideElegidos[j] == 1); patronAcceso[i] = j; strideElegidos[j] = 1; } } //-------------------------------------------------------------------- // Calcula el numero de veces que aparece "NUM_BUSCADO" en la rodaja // de tamanio "longRodaja" apuntada por "vector" //-------------------------------------------------------------------- int vecesQueAparece (void) { int i, j, k, numVeces; numVeces = 0; for (i=0; i<NUM_VECTORES; i++) for (j=0; j<longRodaja; j+=LONG_PATRON) for (k=0; k<LONG_PATRON; k++) if (rodaja[j+patronAcceso[k]] == NUM_BUSCADO) numVeces++; return numVeces; }

Page 22: Práctica 2 Método de Monte Carlo y Superaceleración · 2020. 10. 6. · ETSISI - UPM Procesamiento Paralelo Curso 20/21 Práctica 2: Método de Monte Carlo y superaceleración

ETSISI - UPM Procesamiento Paralelo Curso 20/21

Práctica 2: Método de Monte Carlo y superaceleración Página - 21

//-------------------------------------------------------------------- int main( int argc, char *argv[] ) { int i, numVeces, yo, numProcesos, numVecesTotal; struct timeval t0, tf, t; setbuf(stdout, NULL); MPI_Init (&argc, &argv); MPI_Comm_rank (MPI_COMM_WORLD, &yo); MPI_Comm_size (MPI_COMM_WORLD, &numProcesos); // Control parametro => Cardinalidad del vector if (argc != 2) { if (yo == 0) printf ("Uso: cuentaPar2 cardinalidad\n"); MPI_Finalize(); return 0; } longVector = atoi(argv[1]); longRodaja = longVector / numProcesos; // Se forma el patron de acceso a memoria formarPatronDeAcceso(); // Se pide memoria e inicializa el vector if (yo == 0) { vector = malloc (longVector * 4); for (i=0; i<longVector; i++) vector[i] = random() % MAX_ENTERO; assert (gettimeofday (&t0, NULL) == 0); } rodaja = malloc (longRodaja * 4); // Se reparte, computa y recolectan resultados MPI_Scatter (vector, longRodaja, MPI_INT, rodaja, longRodaja, MPI_INT, 0, MPI_COMM_WORLD); numVeces = vecesQueAparece(); MPI_Reduce (&numVeces, &numVecesTotal, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); if (yo == 0) { assert (gettimeofday (&tf, NULL) == 0); timersub(&tf, &t0, &t); printf ("Numero de veces que aparece el %d = %d\n", NUM_BUSCADO, numVecesTotal); printf ("tiempo = %ld:%3ld\n", t.tv_sec, t.tv_usec/1000); } MPI_Finalize(); return 0; }