Práctica 3 Generación de números primos Septiembre-Diciembre 2007
1 Algoritmo secuencial (primosec) En esta práctica vamos a trabajar con la generación de números primos basado en el método conocido como la criba de Eratóstenes. En nuestro ejemplo, se maneja una tabla con los primeros 480 números primos (del 3 al 3.433 incluidos, ya que excluimos el 1 y el 2) que nos permitirá calcular primos por debajo de 11.799.225 que es el cuadrado de 3.435 (dos más del último primo conocido y del que no se puede asegurar que sea o no primo, aunque en este caso sea obvio que no lo es). El programa desarrollado admite un parámetro de línea de comando que permite fijar con cuántos primos conocidos vamos a trabajar, para así poder hacer varias pruebas sin necesidad de recompilar. Gráficamente, este algoritmo puede expresarse de la forma siguiente: 11.799.223,...,3437,3435 primosec [3..3433] 480 11.799.223,...,3457,3449 Tabla de primeros primos conocidos En este apartado se va a probar la ejecución del algoritmo secuencial primosec.c cuyo código se suministra totalmente escrito y se muestra a continuación: #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #define TRUE 1 #define FALSE 0 Secuencia de números por los que probar (impares < 3435 2 ) #define TOT_PRIMOS_CONOCIDOS 480 Secuencia de números primos calculados static int primosconocidos[tot_primos_conocidos] = 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, Práctica 3: Generación de números primos Página - 1
; 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997,1009,1013,1019, 1021,1031,1033,1039,1049,1051,1061,1063,1069,1087, 1091,1093,1097,1103,1109,1117,1123,1129,1151,1153, 1163,1171,1181,1187,1193,1201,1213,1217,1223,1229, 1231,1237,1249,1259,1277,1279,1283,1289,1291,1297, 1301,1303,1307,1319,1321,1327,1361,1367,1373,1381, 1399,1409,1423,1427,1429,1433,1439,1447,1451,1453, 1459,1471,1481,1483,1487,1489,1493,1499,1511,1523, 1531,1543,1549,1553,1559,1567,1571,1579,1583,1597, 1601,1607,1609,1613,1619,1621,1627,1637,1657,1663, 1667,1669,1693,1697,1699,1709,1721,1723,1733,1741, 1747,1753,1759,1777,1783,1787,1789,1801,1811,1823, 1831,1847,1861,1867,1871,1873,1877,1879,1889,1901, 1907,1913,1931,1933,1949,1951,1973,1979,1987,1993, 1997,1999,2003,2011,2017,2027,2029,2039,2053,2063, 2069,2081,2083,2087,2089,2099,2111,2113,2129,2131, 2137,2141,2143,2153,2161,2179,2203,2207,2213,2221, 2237,2239,2243,2251,2267,2269,2273,2281,2287,2293, 2297,2309,2311,2333,2339,2341,2347,2351,2357,2371, 2377,2381,2383,2389,2393,2399,2411,2417,2423,2437, 2441,2447,2459,2467,2473,2477,2503,2521,2531,2539, 2543,2549,2551,2557,2579,2591,2593,2609,2617,2621, 2633,2647,2657,2659,2663,2671,2677,2683,2687,2689, 2693,2699,2707,2711,2713,2719,2729,2731,2741,2749, 2753,2767,2777,2789,2791,2797,2801,2803,2819,2833, 2837,2843,2851,2857,2861,2879,2887,2897,2903,2909, 2917,2927,2939,2953,2957,2963,2969,2971,2999,3001, 3011,3019,3023,3037,3041,3049,3061,3067,3079,3083, 3089,3109,3119,3121,3137,3163,3167,3169,3181,3187, 3191,3203,3209,3217,3221,3229,3251,3253,3257,3259, 3271,3299,3301,3307,3313,3319,3323,3329,3331,3343, 3347,3359,3361,3371,3373,3389,3391,3407,3413,3433 int main (int argc, char **argv) struct timeval t0, t1, t; int numprimosconocidos; int total = 0; int siguiente; // Siguiente numero de la serie a cribar int cotaserie; // Cota superior hasta donde van a determinarse // numeros primos con los ya conocidos int esprimo (int numero) int i; for (i=0; i<numprimosconocidos; i++) if ((numero % primosconocidos[i]) == 0) return FALSE; return TRUE; // Control del parametro de linea de comandos if (argc!= 2) printf ("Uso: primosec numprimosconocidos \n"); return 0; Práctica 3: Generación de números primos Página - 2
numprimosconocidos = atoi(argv[1]); assert (numprimosconocidos > 0); assert (numprimosconocidos <= TOT_PRIMOS_CONOCIDOS); assert (gettimeofday (&t0, NULL) == 0); siguiente = primosconocidos[numprimosconocidos-1]+2; cotaserie = siguiente * siguiente; while (siguiente < cotaserie) if (esprimo(siguiente)) // Comentar los dos printf del if en la version sin E/S printf ("%9i", siguiente); // Comentar total++; if ((total%10) == 0) printf ("\n"); // Comentar siguiente = siguiente + 2; assert (gettimeofday (&t1, NULL) == 0); timersub(&t1, &t0, &t); printf ("\nprimos calculados => %6i\n", total); printf ("Tiempo => %ld:%ld(seg:mseg)\n", t.tv_sec, t.tv_usec/1000); return 0; Generar el ejecutable de primosec.c Ejecutar varias veces este comando con distintos valores de número de primos conocidos y rellenar lo que corresponda de la Tabla-1 de tiempos. Volver a generar el ejecutable de primosec, pero anulando la escritura de los números primos y volver a tomar tiempos rellenando las casillas correspondientes de la Tabla- 1. Observar que el tiempo gastado en imprimir por pantalla es elevado. 2 Un modelo paralelo (primopar1) Si disponemos de dos procesadores, podemos hacer que cada uno de ellos (dispuestos en pipeline ) ejecute una variante del programa secuencial de la sección anterior. El primer filtro de la cadena aplicará el test de divisibilidad con la mitad de los primeros números primos conocidos (del 3 al 1.523 inclusive). Los números que no hayan sido divididos por estos primeros 240 primos, se los pasará al siguiente filtro, que realizará el test de divisibilidad con la otra mitad de primos conocidos (del 1.531 al 3.433 inclusive). Este modelo, sin retoques, es generalizable para 4, 6, 8 10, 12 y 16 procesadores ya que la tabla de primos conocidos admite particiones exactas al ser 480 múltiplo del número de procesadores. Gráficamente el modelo es el siguiente: Práctica 3: Generación de números primos Página - 3
11.799.223,...,3437,3435 Filtro0 Filtro1 11.799.223,...,3457,3449 [3..1523] 240 [1531..3433] 240 Secuencia de números por los que probar (impares < 3435 2 ) Secuencia de números primos calculados Este programa paralelo no tendrá parámetros de línea de comando, ya que el número de primos conocidos con los que se va a trabajar se fija en 240. Al arrancar n procesos MPI, todos ellos sabrán quiénes son (0, 1, 2,, n-1) y cuántos procesos hay (n), pudiendo determinar con qué trozo de la tabla de primos conocidos debe trabajar cada uno de los procesos. Si nos fijamos más adelante en el código, veremos que podríamos haber hecho una optimización en el sentido de no enviar del Filtro0 al Filtro1 los números menores que 2.325.625 (1.525 2 ) y que no hayan sido divididos por Filtro0, ya que sabemos con certeza que son números primos. No obstante, esta optimización no está hecha en el programa primosec (con el objetivo de simplificar el código), por lo que tampoco la haremos en la versión paralela. El código definitivo del programa primopar1.c es el siguiente: #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/time.h> #include "mpi.h" #define TRUE 1 #define FALSE 0 #define MAX_PRIMOS_CONOCIDOS 480 // Maximo de la tabla #define NUM_PRIMOS_CONOCIDOS 240 // Primos con los que probar #define MAX_PROCESOS 16 static int primosconocidos[max_primos_conocidos] = 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, Práctica 3: Generación de números primos Página - 4
; 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997,1009,1013,1019, 1021,1031,1033,1039,1049,1051,1061,1063,1069,1087, 1091,1093,1097,1103,1109,1117,1123,1129,1151,1153, 1163,1171,1181,1187,1193,1201,1213,1217,1223,1229, 1231,1237,1249,1259,1277,1279,1283,1289,1291,1297, 1301,1303,1307,1319,1321,1327,1361,1367,1373,1381, 1399,1409,1423,1427,1429,1433,1439,1447,1451,1453, 1459,1471,1481,1483,1487,1489,1493,1499,1511,1523, 1531,1543,1549,1553,1559,1567,1571,1579,1583,1597, 1601,1607,1609,1613,1619,1621,1627,1637,1657,1663, 1667,1669,1693,1697,1699,1709,1721,1723,1733,1741, 1747,1753,1759,1777,1783,1787,1789,1801,1811,1823, 1831,1847,1861,1867,1871,1873,1877,1879,1889,1901, 1907,1913,1931,1933,1949,1951,1973,1979,1987,1993, 1997,1999,2003,2011,2017,2027,2029,2039,2053,2063, 2069,2081,2083,2087,2089,2099,2111,2113,2129,2131, 2137,2141,2143,2153,2161,2179,2203,2207,2213,2221, 2237,2239,2243,2251,2267,2269,2273,2281,2287,2293, 2297,2309,2311,2333,2339,2341,2347,2351,2357,2371, 2377,2381,2383,2389,2393,2399,2411,2417,2423,2437, 2441,2447,2459,2467,2473,2477,2503,2521,2531,2539, 2543,2549,2551,2557,2579,2591,2593,2609,2617,2621, 2633,2647,2657,2659,2663,2671,2677,2683,2687,2689, 2693,2699,2707,2711,2713,2719,2729,2731,2741,2749, 2753,2767,2777,2789,2791,2797,2801,2803,2819,2833, 2837,2843,2851,2857,2861,2879,2887,2897,2903,2909, 2917,2927,2939,2953,2957,2963,2969,2971,2999,3001, 3011,3019,3023,3037,3041,3049,3061,3067,3079,3083, 3089,3109,3119,3121,3137,3163,3167,3169,3181,3187, 3191,3203,3209,3217,3221,3229,3251,3253,3257,3259, 3271,3299,3301,3307,3313,3319,3323,3329,3331,3343, 3347,3359,3361,3371,3373,3389,3391,3407,3413,3433 static int iprimoinicial, iprimofinal; int divisible (int numero) int i; for (i=iprimoinicial; i<=iprimofinal; i++) if ((numero % primosconocidos[i]) == 0) return TRUE; return FALSE; void enviar (int proceso, int numero) MPI_Send (&numero, 1, MPI_INT, proceso, 1, MPI_COMM_WORLD); void recibir (int proceso, int *numero) MPI_Status estado; MPI_Recv (numero, 1, MPI_INT, proceso, 1, MPI_COMM_WORLD, &estado); Práctica 3: Generación de números primos Página - 5
void filtrocero (int numprocesos) struct timeval t0, t1, t; int numero, cotaserie; int miderecha = 1, numprimosporproceso; assert (gettimeofday (&t0, NULL) == 0); numprimosporproceso = NUM_PRIMOS_CONOCIDOS / numprocesos; iprimoinicial = 0; iprimofinal = numprimosporproceso-1; numero = primosconocidos[num_primos_conocidos-1]+2; cotaserie = numero * numero; while (numero < cotaserie) if (!divisible(numero)) enviar(miderecha, numero); numero = numero + 2; enviar(miderecha, 0); recibir(numprocesos-1, &numero); // Indicacion de terminacion assert (gettimeofday (&t1, NULL) == 0); timersub(&t1, &t0, &t); printf ("Tiempo => %ld:%ld(seg:mseg)\n", t.tv_sec, t.tv_usec/1000); void filtronocero (int numprocesos, int yo) int numero, miizquierda, miderecha=0, soyelultimo; int numprimosporproceso, totalprimos=0; miizquierda = yo-1; soyelultimo = (yo == (numprocesos-1)); if (! soyelultimo) miderecha = yo+1; numprimosporproceso = NUM_PRIMOS_CONOCIDOS / numprocesos; iprimoinicial = yo * numprimosporproceso; iprimofinal = iprimoinicial + numprimosporproceso - 1; recibir(miizquierda, &numero); while (numero!= 0) if (!divisible(numero)) if (soyelultimo) //descomentar los dos printf del if si se desea ver la salida //printf("%8i", numero); // Descomentar totalprimos++; //if ((totalprimos%10) == 0) printf("\n"); // Descomentar else enviar(miderecha, numero); recibir(miizquierda, &numero); if (soyelultimo) printf ("\nprimos calculados => %6i\n", totalprimos); enviar(0, 0); else enviar(miderecha, numero); // Propagar indicacion terminacion Práctica 3: Generación de números primos Página - 6
int main (int argc, char **argv) int numprocesos, yo; char nombrepc[100]; setbuf (stdout, NULL); MPI_Init (&argc, &argv); MPI_Comm_rank (MPI_COMM_WORLD, &yo); MPI_Comm_size (MPI_COMM_WORLD, &numprocesos); gethostname (nombrepc, 100); printf ( Proceso %d ejecutandose en %s\n, yo, nombrepc); if (yo == 0) assert ((numprocesos > 1) && (numprocesos <= MAX_PROCESOS)); assert (numprocesos!= 14); assert ((numprocesos % 2) == 0); filtrocero(numprocesos); else filtronocero(numprocesos, yo); MPI_Finalize(); return 0; Generar el ejecutable de primopar1.c Probar la ejecución de primopar1 variando el número de procesos. Observar cómo se comporta en comparación con la versión secuencial. Para comprender todavía más el funcionamiento de esta versión puede modificarse el programa para que informe la cantidad de números que se pasan del filtro 0 al 1, del filtro 1 al 2 y del filtro 2 al 3. También podemos hacer esta misma prueba para cuando ejecutamos con tan sólo dos filtros y ver el volumen de información que se pasa de un filtro al otro. Los datos serán muy reveladores. 3 Un modelo paralelo eficiente (primopar2) La ineficiencia del modelo anterior viene del elevado número de comunicaciones pequeñas que se producen en comparación con los cálculos que se realizan. Una forma de suavizar este problema es hacer buffer de los números que se van transfiriendo los filtros, de forma que las comunicaciones reales entre dichos filtros sean de mensajes de 100 números. El código definitivo del programa primopar2.c es el siguiente: #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include "mpi.h" #define TRUE 1 Práctica 3: Generación de números primos Página - 7
#define FALSE 0 #define MAX_PRIMOS_CONOCIDOS 480 // Maximo de la tabla #define NUM_PRIMOS_CONOCIDOS 480 // Primos con los que trabajar #define MAX_PROCESOS 16 #define MAX_MAQUINAS 8 #define LONG_BUFFER 100 typedef struct buffer int numeros[long_buffer]; int cuantos; int actual; // Para buffer de entrada buffer_t; static int primosconocidos[max_primos_conocidos] = 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997,1009,1013,1019, 1021,1031,1033,1039,1049,1051,1061,1063,1069,1087, 1091,1093,1097,1103,1109,1117,1123,1129,1151,1153, 1163,1171,1181,1187,1193,1201,1213,1217,1223,1229, 1231,1237,1249,1259,1277,1279,1283,1289,1291,1297, 1301,1303,1307,1319,1321,1327,1361,1367,1373,1381, 1399,1409,1423,1427,1429,1433,1439,1447,1451,1453, 1459,1471,1481,1483,1487,1489,1493,1499,1511,1523, 1531,1543,1549,1553,1559,1567,1571,1579,1583,1597, 1601,1607,1609,1613,1619,1621,1627,1637,1657,1663, 1667,1669,1693,1697,1699,1709,1721,1723,1733,1741, 1747,1753,1759,1777,1783,1787,1789,1801,1811,1823, 1831,1847,1861,1867,1871,1873,1877,1879,1889,1901, 1907,1913,1931,1933,1949,1951,1973,1979,1987,1993, 1997,1999,2003,2011,2017,2027,2029,2039,2053,2063, 2069,2081,2083,2087,2089,2099,2111,2113,2129,2131, 2137,2141,2143,2153,2161,2179,2203,2207,2213,2221, 2237,2239,2243,2251,2267,2269,2273,2281,2287,2293, 2297,2309,2311,2333,2339,2341,2347,2351,2357,2371, 2377,2381,2383,2389,2393,2399,2411,2417,2423,2437, 2441,2447,2459,2467,2473,2477,2503,2521,2531,2539, 2543,2549,2551,2557,2579,2591,2593,2609,2617,2621, 2633,2647,2657,2659,2663,2671,2677,2683,2687,2689, 2693,2699,2707,2711,2713,2719,2729,2731,2741,2749, 2753,2767,2777,2789,2791,2797,2801,2803,2819,2833, 2837,2843,2851,2857,2861,2879,2887,2897,2903,2909, 2917,2927,2939,2953,2957,2963,2969,2971,2999,3001, 3011,3019,3023,3037,3041,3049,3061,3067,3079,3083, 3089,3109,3119,3121,3137,3163,3167,3169,3181,3187, 3191,3203,3209,3217,3221,3229,3251,3253,3257,3259, Práctica 3: Generación de números primos Página - 8
; 3271,3299,3301,3307,3313,3319,3323,3329,3331,3343, 3347,3359,3361,3371,3373,3389,3391,3407,3413,3433 static int iprimoinicial, iprimofinal; int divisible (int numero) int i; for (i=iprimoinicial; i<=iprimofinal; i++) if ((numero % primosconocidos[i]) == 0) return TRUE; return FALSE; void enviar (int proceso, int numero) MPI_Send (&numero, 1, MPI_INT, proceso, 1, MPI_COMM_WORLD); void recibir (int proceso, int *numero) MPI_Status estado; MPI_Recv (numero, 1, MPI_INT, proceso, 1, MPI_COMM_WORLD, &estado); void enviarbuffer (buffer_t *b, int proceso, int numero) b->numeros[b->cuantos] = numero; if (b->cuantos == (LONG_BUFFER-1)) MPI_Send (b->numeros,long_buffer, MPI_INT, proceso, 1,MPI_COMM_WORLD); b->cuantos = 0; else b->cuantos++; void vaciarbuffer (buffer_t *b, int proceso) if (b->cuantos > 0) MPI_Send (b->numeros, b->cuantos, MPI_INT, proceso, 1, MPI_COMM_WORLD); b->cuantos = 0; void recibirbuffer (buffer_t *b, int proceso, int *numero) MPI_Status estado; if (b->actual == b->cuantos) MPI_Recv (b->numeros, LONG_BUFFER, MPI_INT, proceso, 1, MPI_COMM_WORLD, &estado); MPI_Get_count (&estado, MPI_INT, &(b->cuantos)); b->actual = 0; *numero = b->numeros[b->actual++]; Práctica 3: Generación de números primos Página - 9
void filtrocero (int numprocesos, int yo) struct timeval t0, t1, t; int numero, cotaserie; int miderecha = 1, numprimosporproceso; buffer_t salida; assert (gettimeofday (&t0, NULL) == 0); numprimosporproceso = NUM_PRIMOS_CONOCIDOS / numprocesos; iprimoinicial = 0; iprimofinal = iprimoinicial + numprimosporproceso - 1; numero = primosconocidos[num_primos_conocidos-1]+2; cotaserie = numero * numero; salida.cuantos = 0; while (numero < cotaserie) if (!divisible(numero)) enviarbuffer (&salida, miderecha, numero); numero = numero + 2; enviarbuffer(&salida, miderecha, 0); vaciarbuffer(&salida, miderecha); recibir(mpi_any_source, &numero); // Indicacion de terminacion assert (gettimeofday (&t1, NULL) == 0); timersub(&t1, &t0, &t); printf ("Tiempo => %ld:%ld(seg:mseg)\n", t.tv_sec, t.tv_usec/1000); void filtronocero (int numprocesos, int yo) int numero, miizquierda, miderecha=0, soyelultimo; int numprimosporproceso, totalprimos=0; buffer_t entrada, salida; miizquierda = yo-1; soyelultimo = (yo == (numprocesos-1)); if (! soyelultimo) miderecha = yo+1; numprimosporproceso = NUM_PRIMOS_CONOCIDOS / numprocesos; iprimoinicial = yo * numprimosporproceso; iprimofinal = iprimoinicial + numprimosporproceso - 1; entrada.cuantos = 0; entrada.actual = 0; salida.cuantos = 0; recibirbuffer(&entrada, miizquierda, &numero); while (numero!= 0) if (!divisible(numero)) if (soyelultimo) //Descomentar los dos printf del if para ver la salida //printf("%8i", numero); //Descomentar totalprimos++; //if ((totalprimos%10) == 0) printf("\n"); //Descomentar else enviarbuffer(&salida, miderecha, numero); recibirbuffer(&entrada, miizquierda, &numero); if (soyelultimo) printf ("\nprimos calculados => %6i\n", totalprimos); enviar(0, 0); else enviarbuffer(&salida, miderecha, numero); // Propagar terminacion vaciarbuffer(&salida, miderecha); Práctica 3: Generación de números primos Página - 10
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); if (yo == 0) assert ((numprocesos > 1) && (numprocesos <= MAX_PROCESOS)); assert (numprocesos!= 14); assert ((numprocesos % 2) == 0); filtrocero(numprocesos, yo); else filtronocero(numprocesos, yo); MPI_Finalize(); return 0; Generar el ejecutable de primopar2.c Probar la ejecución de primopar2 rellenando la Tabla-3 4 Modelo paralelo alternativo a desarrollar (primopar3) En este apartado se trata de escribir una versión paralela que calcule primos con mayor eficiencia que el modelo de pipeline. La idea es que los filtros tengan, cada uno, la tabla completa de primos conocidos y trabajen con todos ellos, pero repartiéndose la serie de números impares que van a ser sometidos al test de divisibilidad. Gráficamente, para el caso de dos filtros, sería lo siguiente: Filtro0 [3..3433] 480 11.799.223,...,3437,3435 Secuencia de números primos calculados Filtro1 Secuencia de números por los que probar (impares < 3435 2 ) [3..3433] 480 Una forma de dividir la serie de entrada es que el filtro cero tome los números 3435, 3439, 3443 y así sucesivamente, mientras que el filtro uno tome el resto, es decir, los números 3437, 3441, 3445 etc. Práctica 3: Generación de números primos Página - 11
Con este modelo, la secuencia de números primos saldrá desordenada si cada filtro imprime sus primos según los va calculando. Opcionalmente, se puede intentar mejorar el modelo para que los números primos salgan ordenados, tal y como sucedía en el caso de las versiones secuencial y paralela de las secciones anteriores ojo, es más difícil-. Se puede partir del programa primopar1.c como esqueleto. Se sugiere fundir filtrocero y filtronocero en un único filtro (ejecutado por todos los procesos) que reciba los parámetros necesarios para que pueda determinar con qué parte de la serie de números de entrada va a trabajar. Además, todos los procesos filtros del 1 en adelante, informarán al proceso maestro (el 0) del número de primos que han calculado. El proceso maestro concluirá indicando el tiempo empleado y el número total de primos que han calculado entre todos los filtros (incluido él mismo). Una vez escrito, compilado y depurado el programa, rellenar la Tabla-4 (columnas de tiempos y eficiencia) y comparar los resultados obtenidos respecto de los de la Tabla-3 (programa primopar2). Una mínima comprobación de que el programa es correcto puede consistir en constatar que esta versión informa del mismo número total de primos que en las versiones anteriores. Si llega a observar algo extraño con la eficiencia para el caso de 6, 10 y 12 procesos, modifique el programa para que informe de la cantidad de divisiones que realiza cada proceso y anotar las columnas de MínDivisiones y MáxDivisiones de la Tabla-4. Práctica 3: Generación de números primos Página - 12
Tabla-1. Tiempos de la versión primosec primosconocidos primoscalculados Tiempo (con E/S) Tiempo (sin E/S) 120 240 480 Tabla-2. Tiempos de la versión primopar1 (con 240 primosconocidos) numprocesos Tiempo (seg:mseg) 2 4 8 Tabla-3. Tiempos de la versión primopar2 (con 480 primosconocidos) numprocesos Tiempo (seg:mseg) Eficiencia 2 4 6 8 10 12 16 Tabla-4. Tiempos de la versión primopar3 (con 480 primosconocidos) numprocesos Tiempo (seg:mseg) Eficiencia MínDivisiones MáxDivisiones 2 4 6 8 10 12 16 Práctica 3: Generación de números primos Página - 13