proPar Curso 18/19 5 3 2, 3, 2 2, 2 4 Computadores Paralelos Programación basada en paso de mensajes Técnicas básicas de programación paralela Compulsiva, Divide y Vencerás, Pipeline, Síncrona, Equilibrado de carga y Terminación Programación basada en memoria común Algoritmos y aplicaciones Ordenación, …
proPar Temario diviVence-2 Particionamiento y Divide y Vencerás Particionamiento Divide y Vencerás Ordenación mediante cubetas Integración numérica Problema de los N-cuerpos
proPar Particionamiento diviVence-3 Particionar: Dividir el problema en partes (Ej: Fractal Mandelbrot) datos cuentaM cuentaE1 cuentaEn funcional f1 f2 fn Mucho menos frecuente Huffman Quantizar IDCT P B YCbCr RGB MPEG Componer resultado final: scatter + reduce
proPar Divide y Vencerás diviVence-4 Divide y Vencerás: Particionar de forma recursiva, combinando resultados (Ej: QuickSort, SumarNúmeros) int sumar(int *s) { if (card(s) <= 2) return (n1 + n2); else { dividir (s, s1, s2); suma1 = sumar(s1); suma2 = sumar(s2); return (suma1 + suma2); } + ¿ Paralelización ?
proPar Divide y Vencerás (paralelización) diviVence-5 Recolectar sumas Dividir P7 P8 P9 P10 P11 P12 P13 P14 P3 P4 P5 P6 P1 P2 P0 No trabajan todos los procesos todo el tiempo
proPar Divide y Vencerás (paralelización) diviVence-6 Nivel 4 2 1 P0 P1 P2 P3 P4 P5 P6 P7 P0 P1 P2 P3 P4 P5 P6 P7 P0 => 4, 2, 1 P4 => 6, 5 P2 => 3 P6 => 7 ¿ Cómo programarlo ? Recibir_Mi_Trozo Distribuir_A_Descendientes Procesar_Mi_Trozo Recolectar_Sumas_De_Mis_Descendientes Enviar_Mi_Suma_Total_A_Mi_Ancestro ¿De quién? ¿A cuáles?
proPar Divide y Vencerás (paralelización) diviVence-7 //Recibir_Mi_Trozo if (yo == 0) nivel = numProcesos / 2; else recibir (-1, {&padre, &nivel, &miTrozo}); //Distribuir_A_Descendientes for (i=nivel; i>0; i=i/2) enviar(yo+i, {yo, i/2, trozo(i)}); //Procesar_Mi_Trozo //Recolectar_Sumas_De_Mis_Descendientes //Enviar_Mi_Suma_Total_A_Mi_Ancestro if (yo == 0) printf(“Total = %d\n”, Total); else enviar(padre, Total);
proPar Divide y Vencerás (paralelización) diviVence-8 //Recibir_Mi_Trozo if (yo == 0) { for (i=0; i<N; i++) v[i] = random(); nivel = numProcesos / 2; longRodaja = N; } else { MPI_Recv (v, N/2, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &estado); padre = estado.MPI_SOURCE; nivel = estado.MPI_TAG; MPI_Get_count (&estado, MPI_INT, longRodaja); } //Distribuir_A_Descendientes for (i=nivel; i>0; i=i/2) { longRodaja = longRodaja / 2; MPI_Send (&v[longRodaja], longRodaja, MPI_INT, yo+i, i/2, MPI_COMM_WORLD); ¿ scatter ?
proPar Divide y Vencerás (paralelización) diviVence-9 //Procesar_Mi_Trozo suma = 0; for (i=0; i<longRodaja; i++) suma += v[i]; //Recolectar_Sumas_De_Mis_Descendientes for (i=nivel; i>0; i=i/2) { MPI_Recv (&sumaBis, 1, MPI_INT, MPI_ANY_SOURCE, 1, MPI_COMM_WORLD, &estado); suma += sumaBis } //Enviar_Mi_Suma_Total_A_Mi_Ancestro if (yo>0) MPI_Send (&sumaBis, 1, MPI_INT, padre, 1, …); else printf (“Suma = %d\n”, suma); ¿ reduce?
proPar Divide y Vencerás (paralelización) diviVence-10 32.000.000 números int sumaSec => 0,033 *500 17,021 sumaParBis 0,161 0,234 1,006 1,385 1,632 Scatter + Reduce Procesos sumaPar 2 0,152 4 0,222 8 0,996 16 1,382 32 1,626 ? 2 11,388 4 10,279 8 6,320 16 3,985 32 2,675 0,75 0,41 0,34 0,27 0,20
proPar Ordenación mediante cubetas diviVence-11 ¿Ordenar exámenes? ¿Cuánto tardará en ordenar 600 exámenes? De uno en uno A B Z Un montón por letra ¡ Más rápido ! ¿ Paralelo ? ¡ Lentísimo ! ordenar juntar
proPar Ordenación mediante cubetas diviVence-12 DNI Edad (días) Sueldo anual ? M números (10.000.000) en un rango (sea: 0..99.999) Uniformemente distribuidos en N intervalos regulares (sean: 10) 1.000.000 [0..9.999], 1.000.000 [10.000..19.999], .............
proPar Ordenación mediante cubetas diviVence-13 DNI Edad (días) Sueldo anual ? M números (10.000.000) en un rango (sea: 0..99.999) Uniformemente distribuidos en N intervalos regulares (sean: 10) 1.000.000 [0..9.999], 1.000.000 [10.000..19.999], .............
proPar Ordenación mediante cubetas diviVence-14 DNI Edad (días) Sueldo anual ? M números (10.000.000) en un rango (sea: 0..99.999) Uniformemente distribuidos en N intervalos regulares (sean: 10) 1.000.000 [0..9.999], 1.000.000 [10.000..19.999], ............. 10.000 19.999 9.999 90.000 99.999 M-1 Distribuir en cubetas Ordenar cubetas ¡ No todas iguales !
proPar Ordenación mediante cubetas diviVence-15 int V[10.000.000] 10.000 19.999 9.999 90.000 99.999 M-1 Distribuir en cubetas 998.145 1.003.538 1.001.004 ¿Qué hacer? Cubetas de 1.000.000 + algo (1%) 1.010.000 int Cubetas [10][1.010.000] int Cubetas [10.100.000] ---------- ---------- Distribuir y ordenar cubetas ? Juntar cubetas 10.000.000 100.000
proPar Ordenación mediante cubetas diviVence-16 DNI Edad (días) Sueldo anual ? M números (10.000.000) en un rango (sea: 0..99.999) Uniformemente distribuidos en N intervalos regulares (sean: 10) 1.000.000 [0..9.999], 1.000.000 [10.000..19.999], ............. 10.000 19.999 9.999 90.000 99.999 M-1 Distribuir en cubetas Ordenar cubetas ¿Programa paralelo? ¡ No todas iguales ! Juntar
proPar Ordenación mediante cubetas diviVence-17 Versión 1: Asociar una cubeta por proceso 10.000 19.999 9.999 90.000 99.999 M-1 P0 P1 P9 Recolectar en mi cubeta Para todo Pi ¿Con pasoMsj? Ordenar mi cubeta ¿Mejor sort+mezclas? Todos los Pi necesitan el array completo
proPar Ordenación mediante cubetas diviVence-18 static int *vector, elemCubeta; vector = malloc (CARDINALIDAD * 4); enterosCubeta = MAX_ENTERO / numCubetas; if (yo == 0) // Inicializar vector MPI_Bcast (vector, CARDINALIDAD, MPI_INT, 0, MPI_COMM_WORLD); // Coger los de mi cubeta elemCubeta = 0; for (i=0; i<CARDINALIDAD; i++) if ((vector[i] / enterosCubeta) == yo) vector[elemCubeta++] = vector[i]; ordenarCubeta ( ); // Enviar y Recoger cubetas if (yo == 0) { j = elemCubeta; for (i=1; i<numCubetas; i++) { MPI_Recv (&vector[j], CARDINALIDAD, MPI_INT, i, 1, MPI_COMM_WORLD, &estado); MPI_Get_count (&estado, MPI_INT, &elemCubeta); j+=elemCubeta; } } else MPI_Send (vector, elemCubeta, MPI_INT, 0, 1, MPI_COMM_WORLD);
proPar Ordenación mediante cubetas diviVence-19 Versión 2: Asignar un trozo del array original a cada proceso M-1 P0 P1 P9 Recolectar en miniCubetas Para todo Pi ? Env/Rec miniCubetas 10.000 19.999 9.999 90.000 99.999 Ordenar mi cubeta
proPar Ordenación mediante cubetas diviVence-20 Pk int miTrozo [1.000.000] int miniCubetas [10][101.000] MPI_Alltoall int cubeta [1.010.000]
proPar Ordenación mediante cubetas diviVence-21 MPI_Alltoall (BufferEnvio, BufferRecepcion, MPI_Comm) buffer enviar buffer recibir P0 n-1 P1 Pn-1 Pn-1 n-1 P0 Pn-2 gather (receptorP0) gather (receptorPn-1) ¿Todas las minicubetas iguales?
proPar Tiempos ordenar Cubetas diviVence-22 480.000 números, 2..16 cubetas/Pi y Selección Directa 97,208 puro
proPar Integración numérica diviVence-23 a F(x) dx b ¿Mejor trapecios? a b a b = (b – a) / N I = F(x) x=a b- Precisión ¿Cómo puede ser el programa paralelo?
proPar Integración numérica diviVence-24 Particionamiento y Asignación Estática (4Pi) a b a b = (b – a) / N (N=1.000.000) = (b – a) / P (P=4) P0 P1 P2 P3 Cada Pi calculará su trozo: 250.000 rectángulos () ¿Esbozo del programa paralelo? Conocimiento previo del N razonable
proPar Integración numérica diviVence-25 int main (int argc, char *argv[]) { struct timeval t0, tf, t; double a, b, miValor, valor; int trozos, yo, numProcesos; MPI_Init (&argc, &argv); MPI_Comm_rank (MPI_COMM_WORLD, &yo); MPI_Comm_size (MPI_COMM_WORLD, &numProcesos); a = atof (argv[1]); b = atof (argv[2]); trozos = atoi (argv[3]); if (yo == 0) gettimeofday (&t0, NULL); miValor = funcion(yo, a, b, trozos, numProcesos); MPI_Reduce(&miValor, &valor, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_...); if (yo == 0) { printf ("Valor de la integral = %20.18f\n", valor); gettimeofday (&tf, NULL); timersub (&tf, &t0, &t); printf ("Tiempo = %ld:%ld (seg:mseg)\n", t.tv_sec, t.tv_usec/1000); } MPI_Finalize(); return 0;
proPar Integración numérica diviVence-26 double funcion (int yo, double a, double b, int trozos, int numProcesos) { double delta, valor = 0.0, miA; int i, iFin; delta = (b - a) / (double) trozos; miA = a + ((double)yo * ((b-a)/numProcesos)); printf ("%d miA = %f\n", yo, miA); iFin = trozos/numProcesos; for (i=0; i<iFin; i++) { valor += sin(miA); miA += delta; } return delta * valor; ? 1 2 4 # Tiempo 4:629 2:518 1:317 An 1,84 3,51 En 0,92 0,88 PC1: integra 0.0 1.0 200.000.000 1 2 4 # Tiempo 6:783 4:049 2:696 An 1,68 2,52 En 0,84 0,63 integra 0.0 3.1416 200.000.000
proPar Integración numérica diviVence-27 # Tiempo An En PC9: integra 0.0 Π 400.000.000 1 12:597 2 7:391 1,70 0,85 4 4:863 2,59 0,65 8 2:550 4,94 0,62 ¡ Igual de mal ! ¿ Mejorable ? ¡Salto de la Rana! # Tiempo An En 1 12:597 2 6:266 2,01 1,01 4 3:159 3,99 1,00 8 1:682 7,49 0,94
proPar Integración numérica diviVence-28 double funcion (int yo, double a, double b, int trozos, int numProcesos) { double delta, miDelta, valor = 0.0, miA; int i, iFin; delta = (b - a) / (double) trozos; miDelta = delta * (double) numProcesos; miA = a + ((double)yo * delta); printf ("%d miA = %f\n", yo, miA); iFin = trozos/numProcesos; for (i=0; i<iFin; i++) { valor += sin(miA); miA += miDelta; } return delta * valor;
proPar Integración numérica diviVence-29 Divide y Vencerás y Asignación Estática (4Pi) a b P0 P1 P2 P3 a b 4 A4 N Area 2 A2 = (b – a) / N (N=¿?) ¿Esbozo del programa paralelo? ¡¡ Igual que antes !! n An 2n A2n |A2n-An| < cotaError
proPar Integración numérica diviVence-30 miA = a + ((double)yo * ((b-a)/numProcesos)); miB = miA + ((b-a)/numProcesos); trozos = 1000; fActual = funcion(miA, miB, trozos); do { fAnterior = fActual; trozos = trozos + 1000; } while (fabs(fActual - fAnterior) > cotaError); MPI_Reduce(&fActual, &valor, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); ? 1 2 4 # Tiempo 44:009 15:273 3:527 An 2,88 12,48 En 1,44 3,12 ? Reparto no equilibrado de trabajo Superaceleración ficticia ¿ Mejorable ?
proPar Integración numérica diviVence-31 trozos = 1000; fActual = funcion(yo, trozos) MPI_Reduce(&fActual, &FActual, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); MPI_Bcast (&FActual, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); do { FAnterior = FActual; trozos = trozos + 1000; fActual = funcion(yo, trozos); MPI_Reduce(&fActual, &FActual, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); } while (fabs(FActual - FAnterior) > cotaError); 1 2 4 # Tiempo 44:009 23:754 12:372 An 1,85 3,56 En 0,93 0,89 integra 0.0 1.0 0.0000000001
proPar Problema de los N-cuerpos diviVence-32 “N-body” Interacción de astros en el espacio (galaxia) m1 m2 m3 m4 m2 (xi,yi) Vi i ti (xi+1,yi+1) Vi+1 i+1 ti+1 t t m (Vi+1-Vi) F = t Vi+1 = Vi + (F t / m) dist = Vi t (xi+1,yi+1) = f (dist, i, Xi, Yi) G mp mq F = F = m a r2 q p composición i+1
proPar Problema de los N-cuerpos diviVence-33 for (t=0; t<tMax; t++) /* Para cada periodo */ for (i=0; i<N; i++) { /* Para cada cuerpo */ FNew[i] = NuevaFuerza(i); VNew[i] = NuevaVelocidad(i); PNew[i] = NuevaPosicion(i); } for (i=0; i<N; i++){ /*Actualizar estado global*/ F[i] = Fnew[i] V[i] = VNew[i]; P[i] = PNew[i]; ¿Esbozo del programa paralelo? No computable eficientemente O(N2)
proPar Algoritmo de Burnes-Hut diviVence-34 Idea: Disminuir N agrupando cuerpos próximos Centro de masa r ¿ Cómo determinar las agrupaciones ?
proPar Algoritmo de Burnes-Hut diviVence-35 Idea: Dividir el espacio en (8 ó 4) regiones (cubos o cuadrados) que contengan un único cuerpo y luego ......... QuadTree: Árbol cuaternario En cada nodo: Masa total Centro de masa
proPar Algoritmo de Burnes-Hut diviVence-36 Idea: ..... y luego calcular (F,V,X,Y) recorriendo árbol Para Sigue r >= d / tal que = 1.0 o menor r d ¿ Parar ?
proPar Algoritmo de Burnes-Hut diviVence-37
proPar Algoritmo de Burnes-Hut diviVence-38 for (t=0; t<tMax; t++) { /* Para cada periodo */ formarArbol; CalcularMasasYCentros; CalcularNuevasFuerzas; Actualizar; } Complejidad = O(NlogN) ??? Bisección recursiva ortogonal ¿Programa paralelo? Asignar nodos a procesos Árbol muy poco equilibrado
proPar Algoritmo de Burnes-Hut diviVence-39 9 21 10 1 Media = 10,25
proPar Algoritmo de Burnes-Hut diviVence-40 Bisección recursiva ortogonal FIN