Práctica 3. Generación de números primos

Documentos relacionados
1 Primitivas básicas de OpenMP

MPI Introducción Procesos Envío y recepción simple Envío y recepción no tan simple Comunicación colectiva. Herramientas de depuración y evaluación

ETSISI - UPM Procesamiento Paralelo Curso 18/19. Práctica 2. Método de Monte Carlo y Superaceleración

Message Passing Interface (MPI)

Toma de contacto con MPI

Paradigma de paso de mensajes

Enunciado 2 Descifrar claves cifradas con crypt

Práctica 1. Primera experiencia con MPI

Master SIA. Programación de Sistemas Paralelos. MPI: Ejercicios prácticos. Ejercicio 1. Facultad de Informática

Procesamiento Paralelo

SISTEMAS PARALELOS Y DISTRIBUIDOS. 3º GIC. PRÁCTICA 9 Departamento de Arquitectura y Tecnología de Computadores Universidad de Sevilla

ETSISI-UPM Arquitecturas Avanzadas Paralelas Curso 17/18. Toma de contacto con la placa Parallella Epiphany-16 Lab4401 y Lab4405

Programación en Entornos Paralelos: MPI

MPI es un estándar de programación en paralelo mediante paso de mensajes que permite crear programas portables y eficientes.

SESIÓN DE EJERCICIOS E1

Programación I Funciones

INTRODUCCIÓN A LA PROGRAMACIÓN DE COMPUTADORES DE MEMORIA DISTRIBUIDA USANDO MPI SISTEMAS PARALELOS Y DISTRIBUIDOS

Introducción a Sistemas Operativos: Ficheros

Repaso Lenguaje C Área de Servicios Programación (Ing. Elect. y Prof. Tec.), Programación I (TUG y TUR) y Electrónica programable (TUE)

Realizar el ejercicio anterior utilizando Punteros

UNIVERSIDAD CARLOS III DE MADRID DEPARTAMENTO DE INFORMÁTICA INGENIERÍA EN INFORMÁTICA. ARQUITECTURA DE COMPUTADORES II 19 de junio de 2007

Tema 3. Estructuras de control

Multiprocesamiento en lenguaje C Introducción a MPI

Ejercicios sobre tuberías

Procesamiento Paralelo

SESIÓN DE EJERCICIOS E1

Estructura de Datos L I S T A

Codificación en C. Como pasar de Pseudocódigo a C (con pequeños trucos de C++)

EJERCICIOS CON FUNCIONES EN C. EJEMPLO CALCULAR SERIES NUMÉRICAS. REFACTORIZAR. (CU00552F)

Lenguaje C. República Bolivariana de Venezuela Fundación Misión Sucre Aldea Fray Pedro de Agreda Introducción a la Programación III

1. Presentación del lenguaje C Creado en 1972 por D. Ritchie Lenguaje de propósito general Portátil o transportable (generalmente) Inicialmente de niv

Biblioteca de sistema

for(i = 0; i <= 45; i+=5) { x = (i*3.1416)/180; printf( seno(%d) = %f\n,i,seno(x));

Estructuras de Decisión Simples y Dobles

Introducción general al Lenguaje C (2010/2011)

1000+(4/100)*1000 =1000 * (1+4/100) =1000 * 1.04 = Al finalizar el segundo año, el 4% se aplica sobre los 1040, y obtenemos:

Tema ADQUISICIÓN Y TRATAMIENTO DE DATOS. Departamento de Ciencias de la Computación e IA. Subprogramas en C

ASIGNATURA: SISTEMAS INFORMÁTICOS INDUSTRIALES. CURSO 4º GRUPO Octubre 2015

TEMA 5. CONTROL DE FLUJO DEL PROGRAMA. Sentencia Instrucción Expresión Operadores + Operandos Sintaxis: Sentencia ;

Master SIA 2012/13. Programación de Sistemas Paralelos. OpenMP: Ejercicios prácticos. Facultad de Informática. Ejercicio 1.A. pi.c

Estructuras de Repetición: Repita Mientras.

TEMA 5: PARALELISMO A NIVEL DE HILOS, TAREAS Y PETICIONES (TLP, RLP) (segunda parte)

GUIÓN DE PRÁCTICAS 3: ESTRUCTURAS CONDICIONALES

Fundamentos de programación

Operadores aritméticos

Punteros. Programación en C 1

Ejercicios Tema 6. Funciones

Actividad Algoritmos, Estructura y Programación I. FOR, DO-WHILE

8. Vectores (arrays)

Los prototipos de las funciones de MPI se detallan en el document: MPI_funciones.pdf

Funciones en lenguaje C

Estructura de un programa en Java. Tipos de datos básicos. class miprimerprograma{ // comentario, no es parte del programa

GUÍA DE LABORATORIO #3 ESTRUCTURAS ALGORÍTMICAS CONDICIONALES SIMPLES, DOBLES Y MÚLTIPLES

INTRODUCCIÓN A LA PROGRAMACIÓN. 1º Bachillerato

Sintaxis de los aspectos generales de un lenguaje de programación

Práctico 2: Funciones y Punteros en C La teoría general para este práctico puede consultarse en los Capítulos 4 y 5 Notas de Clase

PROBLEMA 1. Rellena el hueco 1 (línea 23). Realiza la reserva de memoria para almacenar los n puntos.

Práctica 1: Intérprete de mandatos. Sistemas Operativos Área de Arquitectura y Tecnología de Computadores

Introducción a Sistemas Operativos: Comunicación entre Procesos

Diagrama de transiciones del autómata. Tabla de transiciones

Quick Tutorial de C++ y CLion

Autor: Ing. Nahuel González INTRODUCCIÓN A C. Clase 1

Ejercicios de la sesión 4 de C resueltos

Sistemas Operativos: Programación de Sistemas. Curso Oscar Déniz Suárez Alexis Quesada Arencibia Francisco J.

Laboratorio de Paralelismo Prácticas MPI

Lección 3 Sentencias de control

Estructuras de Decisión Simples y Dobles

Operadores de comparación

8- LEX-Expresiones regulares

Capítulo 13 INSTRUCCIONES DE CONTROL REPETITIVAS. Presentación resumen del libro: "EMPEZAR DE CERO A PROGRAMAR EN lenguaje C"

Tema 4. Control de flujo. Programación Programación - Tema 4: Control de Flujo

Lenguaje de Programación: C++ Repaso de Material C++

Transcripción:

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