Ir al contenido

Simulacro PD05 (2026-I resuelta)

Formato (igual que la PD04): en grupo de 2 o 3, en el laboratorio. La PD se entrega al final de la clase (80 % de la nota) y la Tarea unos días después completa los ejercicios (20 %). Si usas fuentes externas, hay que citarlas.

EjercicioTemaPuntosDónde está resuelto
1 · TsunamiEDP hiperbólica + MPI P2P + tiempos + gráfica3Onda y tsunami
2 · OMP taskDAG + sections vs tasks medido2.5esta página
3 · Bitónicoisoeficiencia malla vs hipercubo, Hyksort1.5Escalabilidad de ordenamientos

Resumen de la solución completa, que está en Onda y tsunami:

  1. Término nuevo, tal como lo da el enunciado: newval[i] = 2·values[i] − oldval[i] + τ²·(values[i−1] − 2·values[i] + values[i+1]) + 0.01·sin(K·(i/180.0)). Sin π y sin Δt². El código entregado trae K * i / 180 con enteros (división entera): hay que corregirlo a 180.0 y declararlo, porque cambia la conclusión (27.38 en la costa sin corregir, 13.67 corregido, con K = 10). Conviene precalcular la fuente, porque no depende del tiempo.
  2. Paralelizar (1 pt): partes iguales de N/p puntos (con resto), 2 celdas fantasma, MPI_Send/MPI_Recv en orden par/impar con los vecinos en cada paso, índice global para la fuente y las fronteras (0.1·sin y 0.1·cos), que solo fijan el primer y el último proceso. Mostrar que la salida es idéntica a la secuencial.
  3. Tiempos (1 pt): p = 1, 2, 4, 8 con los mismos parámetros, con 1024 puntos y con un N mayor. Con 1024 puntos no conviene pasar de p = 2 (domina la latencia). Con 10⁶ puntos la versión con sin() llega a S = 3.3 con p = 8, pero la versión con F precalculada es más rápida con 1 proceso que la otra con 8, y está limitada por la memoria (S ≈ 1.3).
  4. Gráfica (1 pt): curvas superpuestas para K = 10, 15, 20, 25. Ninguna ola de la costa supera 20 (13.67, 12.07, 10.59, 9.12). Solo K = 10 pasa de 20 (24.08), y lejos, en x ≈ 0.84. La amplitud baja al subir K porque K es la frecuencia de la fuente: las olas cortas acumulan menos altura.
min_x = minimo(x, n);
max_x = maximo(x, n);
calculo_z(z, min_x, max_x, n);
calculo_y(y, x, n);
calculo_x(x, y, n);
calculo_v(v, z, x);

“Solo el primer argumento de las funciones calculo_() es modificado.” Esa frase define qué escribe cada función y qué lee:

TareaLeeEscribe
minimoxmin_x
maximoxmax_x
calculo_zmin_x, max_xz
calculo_yxy
calculo_xyx
calculo_vz, xv

Hay dos clases de dependencia:

  • Verdadera (lectura después de escritura, RAW): calculo_z necesita min_x y max_x. calculo_x necesita y. calculo_v necesita z y el x nuevo.
  • Anti-dependencia (escritura después de lectura, WAR): calculo_x sobreescribe x, y minimo, maximo y calculo_y leen x. calculo_x no puede empezar hasta que esos tres terminen de leer. Es la trampa del ejercicio: si se olvida, minimo o maximo podrían leer un x ya modificado.
min_x = minimo(x)0.11 smax_x = maximo(x)0.11 scalculo_y(y, x)0.51 scalculo_z(z, min, max)0.62 scalculo_x(x, y)≈ 0 scalculo_v(v, z, x)0.045 s— gris: dependencia verdadera (lee lo que otro escribió)- - roja: anti-dependencia (calculo_x sobreescribe x, que min y max todavía leen)

Cómo leerlo: el primer nivel (min, max, calculo_y) es independiente y puede ir en paralelo. calculo_z espera a min y max. calculo_x espera a calculo_y (flecha gris) y además a min y max (flechas rojas, anti-dependencia sobre x). calculo_v junta las dos ramas. Debajo de cada nodo está su tiempo medido en la versión de prueba.

Ruta crítica y speedup máximo (con los tiempos medidos, como pide la PD04 2.3):

  • T₁ = 0.11 + 0.11 + 0.62 + 0.51 + 0 + 0.045 ≈ 1.40 s
  • Rutas: min → z → v = 0.11 + 0.62 + 0.045 = 0.775 s. y → x → v = 0.51 + 0 + 0.045 = 0.555 s.
  • T∞ = 0.775 s (ruta crítica min/max → calculo_z → calculo_v)
  • Smáx = T₁ / T∞ ≈ 1.40 / 0.775 ≈ 1.8 ¿Alcanzan 2 hilos T∞? Sí, con este reparto:
Hilo0 – 0.11 s0.11 – 0.62 s0.62 – 0.73 s0.73 – 0.775 s
Aminimocalculo_zcalculo_z (sigue)calculo_v
Bmaximocalculo_ycalculo_x (≈ 0), luego librelibre

calculo_z empieza a los 0.11 s (ya están min y max) y termina a los 0.73 s. Para entonces calculo_x ya terminó (0.62 s), así que calculo_v termina a los 0.775 s = T∞. El hilo B no retrasa la ruta crítica.

2b · Dos versiones: sections y tasks (1.5 pts)

Sección titulada «2b · Dos versiones: sections y tasks (1.5 pts)»

Con sections: hay que agrupar por niveles. La barrera implícita al final de cada sections respeta todas las dependencias (incluida la anti-dependencia, porque calculo_x va en el segundo bloque):

#pragma omp parallel sections
{
#pragma omp section
mn = minimo(x);
#pragma omp section
mx = maximo(x);
#pragma omp section
calculo_y(y, x, n);
}
#pragma omp parallel sections
{
#pragma omp section
calculo_z(z, mn, mx, n);
#pragma omp section
calculo_x(x, y, n);
}
calculo_v(v, z, x);

Con task + depend: se declaran los datos que cada tarea lee (in) y escribe (out/inout). El runtime construye el DAG. depend(inout: x) en calculo_x hace que espere a todas las tareas anteriores con depend(in: x), así que la anti-dependencia queda cubierta sola:

#pragma omp parallel
#pragma omp single
{
#pragma omp task depend(in: x) depend(out: mn) shared(x, mn)
mn = minimo(x);
#pragma omp task depend(in: x) depend(out: mx) shared(x, mx)
mx = maximo(x);
#pragma omp task depend(in: x) depend(out: y) shared(x, y)
calculo_y(y, x, n);
#pragma omp task depend(in: mn, mx) depend(out: z) shared(z, mn, mx)
calculo_z(z, mn, mx, n);
#pragma omp task depend(in: y) depend(inout: x) shared(x, y)
calculo_x(x, y, n);
#pragma omp task depend(in: z, x) depend(out: v) shared(v, z, x)
calculo_v(v, z, x);
}

shared(...) se pone explícito por seguridad. Regla: dentro de una tarea, una variable que no es compartida en el contexto que la crea es firstprivate por defecto. Aquí x, mn, … se declaran antes del parallel, así que ya son compartidas. Pero si se declararan dentro del parallel, cada tarea escribiría en su copia y el valor de mn se perdería.

Funciones de prueba con costos distintos (calculo_z ≈ 0.62 s, calculo_y ≈ 0.51 s, min y max ≈ 0.11 s, calculo_x ≈ 0, calculo_v ≈ 0.045 s), n = 200 000, 3 hilos, 5 corridas. Las tres versiones dan el mismo v = −3.8394599126.

VersiónTiempo (5 corridas)Modelo del DAG
secuencial1.29 – 1.47 sT₁ ≈ 1.40 s
sections1.09 – 1.23 s0.51 + 0.62 + 0.045 ≈ 1.18 s
tasks + depend0.73 – 0.75 sT∞ ≈ 0.78 s
sections (2 regiones con barrera implícita)hilo 0hilo 1hilo 2minmaxcalculo_ycalculo_zvhilos 0 y 1 ociosos →1.18 stasks con dependhilo 0hilo 1hilo 2minmaxcalculo_ycalculo_zv0.78 s0.0 s0.2 s0.4 s0.6 s0.8 s1.0 s1.2 s

Cómo leerlo: con sections, calculo_z no puede empezar hasta que todo el primer bloque termine (línea roja a 0.51 s), aunque solo necesita min y max, que acabaron a los 0.11 s. Mientras tanto los hilos 0 y 1 están ociosos. Con tasks, calculo_z arranca a los 0.11 s y el programa termina en la ruta crítica (0.78 s).

Ventajas y desventajas (lo que pide comparar)

Sección titulada «Ventajas y desventajas (lo que pide comparar)»
sectionstask + depend
Cómo expresa el paralelismobloques fijos separados por barrerasgrafo de dependencias por datos
Respeta el DAGsolo por niveles: sobre-sincronizaexacto: cada tarea espera solo lo que lee
Tiempo en este problema1.18 s (pierde 0.4 s en la barrera)0.78 s (= T∞)
Número de unidadesfijo al escribir el códigodinámico (recursión, bucles, listas)
Overheadbajomayor por tarea (crear y planificar). Importa con tareas pequeñas
Facilidadsimple de leerhay que acertar in/out. Un depend mal puesto es una carrera silenciosa
Cuándo convienepocas tareas de costo parecidocostos desiguales, DAG irregular, recursión (quicksort con tasks)

Ejercicio 3 · Ordenamiento bitónico (1.5 pts)

Sección titulada «Ejercicio 3 · Ordenamiento bitónico (1.5 pts)»

Resuelto paso a paso en Escalabilidad de ordenamientos. Las respuestas:

  • 3a: malla n ∝ 2√p, hipercubo n ∝ 2log² p. La malla escala mejor (necesita menos W para mantener E) para p < 2¹⁶. Se puede añadir el matiz del cruce.
  • 3b: Hyksort da n log n ∝ p log² p, una isoeficiencia polinomial. Escala mejor que los anteriores.
  • Código que compila y cuya salida paralela coincide con la secuencial (mostrarlo)
  • DAG con todas las dependencias, incluidas las anti-dependencias (WAR)
  • T₁, T∞, ruta crítica y Smáx si hay tiempos por tarea
  • Tiempos con omp_get_wtime / MPI_Wtime, varias corridas, misma configuración, y decir en qué máquina
  • Tabla T, S, E y una explicación de por qué (no solo “escala bien”)
  • Isoeficiencia con los pasos: To = pTp − W, luego W = K·To, luego despejar n
  • Citar fuentes externas si se usan

Autoevaluación

0/5 correctas · 0 respondidas
1. En el ejercicio 2, ¿por qué calculo_x debe esperar a minimo y maximo?
2. ¿Qué tareas forman el primer nivel del DAG?
3. ¿Por qué tasks fue más rápido que sections aquí?
4. depend(inout: x) en calculo_x hace que espere a…
5. Con T₁ = 1.40 s y T∞ = 0.775 s, S_máx ≈

Fuente ·2026I-CS4052-PD5.pdf · ej02_tareas.cpp (solución propia, ejecutada) · CS4052-PD04-S02.pdf (formato)