Repartir bucles — omp for y schedule
El constructor for
Sección titulada «El constructor for»#pragma omp for va dentro de una región parallel y reparte las iteraciones del bucle que le sigue. No crea hilos: usa el equipo existente. Es paralelismo de datos (cada hilo, un trozo del arreglo).
#pragma omp parallel private(f, i){ f = 7; #pragma omp for for (i = 0; i < 20; i++) a[i] = b[i] + f * (i - 1);}Atajo combinado (región + reparto en una línea): #pragma omp parallel for.
Cláusulas de for: private, firstprivate, lastprivate, reduction, schedule(kind[, chunk]), collapse(n), ordered, nowait.
schedule: cómo se reparten
Sección titulada «schedule: cómo se reparten»| Tipo | Qué hace | Cuándo conviene |
|---|---|---|
static | Bloques contiguos casi iguales, decididos antes de empezar | Iteraciones de costo parecido |
static, c | Bloques de c en turno rotativo (hilo 0, 1, …, 0, 1, …) | Costo que crece o varía de forma regular |
dynamic, c | Cada hilo libre pide el siguiente bloque de c (por defecto c = 1) | Costos irregulares o impredecibles |
guided, c | Como dynamic, pero los bloques empiezan grandes y se achican hasta c | Irregular, con menos pedidos que dynamic |
runtime | Lo decide la variable de entorno OMP_SCHEDULE al ejecutar | Probar sin recompilar |
auto | Lo decide el compilador / runtime | — |
Cómo leerlo: cada fila es un schedule distinto para el mismo bucle de 16 iteraciones y 4 hilos; el número y el color dicen qué hilo ejecuta cada iteración. static y static, 2 son fijos (siempre igual). dynamic y guided dependen de quién termina primero, así que la fila es una corrida posible. En guided los bloques empiezan grandes (aprox. iteraciones que faltan / hilos) y se achican; el tamaño exacto depende de la implementación.
¿Qué hilo hace cada iteración? #pragma omp for schedule(static)
Cada bloque es un chunk (grupo de iteraciones seguidas). El ancho es lo que tarda. Pulsa «Ejecutar» para ver a los hilos trabajar.
Lo que imprime Ejemp02_omp_for.cpp
Sección titulada «Lo que imprime Ejemp02_omp_for.cpp»#define N 1 << 4 // 16 iteraciones#pragma omp parallel private(id){ // particione N equitativamente entre hilos #pragma omp for schedule(static) private(i) for (i = 0; i < N; i++) { id = omp_get_thread_num(); c[i] = a[i] + b[i]; printf(" %i \t %i\n", id, i); } #pragma omp for for (i = 0; i < N; i++) c[i] *= 2;}Ejecutado con 4 hilos (las líneas salen desordenadas, pero el reparto siempre es el mismo):
hilo iteraciones 0 0 1 2 3 1 4 5 6 7 2 8 9 10 11 3 12 13 14 15Cambia el pragma por schedule(static, 3), schedule(dynamic) o schedule(dynamic, 3) (están comentados en el archivo) y compara con el playground.
nowait y collapse
Sección titulada «nowait y collapse»nowait: quita la barrera implícita al final delfor. Los hilos que terminan siguen con lo próximo sin esperar.collapse(n): juntanbucles anidados en uno solo con más iteraciones, para repartir mejor (útil si el bucle externo tiene pocas vueltas).
#pragma omp parallel{ #pragma omp for schedule(static) nowait for (i = 0; i < n; i++) c[i] = (a[i] + b[i]) / 2.0f; #pragma omp for schedule(static) nowait for (i = 0; i < n; i++) z[i] = sqrtf(c[i]); #pragma omp for schedule(static) nowait for (i = 1; i <= n; i++) y[i] = z[i-1] + a[i];}#pragma omp for collapse(2) lastprivate(jlast, klast)for (k = 1; k <= 2; k++) for (j = 1; j <= 3; j++) { jlast = j; klast = k; }#pragma omp singleprintf("%d %d\n", klast, jlast); // imprime: 2 3Se forma un solo bucle de 2 × 3 = 6 iteraciones. lastprivate deja los valores de la última iteración secuencial (k = 2, j = 3).
Ejercicio
Sección titulada «Ejercicio»En la lámina 21 de U4.1 aparece #pragma omp parallel for … schedule(static, chunk) seguido de un bloque con #pragma omp section. ¿Qué está mal y cómo se corrige?
Ver solución
parallel for debe ir directamente antes de un bucle for, no de un bloque { }, y section solo tiene sentido dentro de sections. Lo correcto es poner el for inmediatamente después:
#pragma omp parallel for shared(a, b, c, chunk) private(i) schedule(static, chunk)for (i = 0; i < N; i++) c[i] = a[i] + b[i];(Respuesta propia: la lámina lo muestra así, sin marcarlo como error.)
Errores típicos
Sección titulada «Errores típicos»- Pensar que
dynamicsiempre es mejor: cada pedido de bloque cuesta. Con iteraciones de costo parecido,staticgana. - Usar
nowaitentre bucles dependientes condynamico con distinto número de iteraciones: el hilo que calculac[i]ya no es necesariamente el que calculaz[i]. - Paralelizar un bucle con dependencia entre iteraciones (
a[i] = a[i-1] + …): ningúnschedulelo arregla, el resultado es incorrecto. - Olvidar que
schedule(runtime)toma el valor deOMP_SCHEDULEal ejecutar.
Práctica extra
Sección titulada «Práctica extra»- Bucle de 12 iteraciones, 3 hilos,
schedule(static, 2). ¿Qué iteraciones hace cada hilo?
Ver solución
Bloques de 2 por turnos: hilo 0 → 0, 1, 6, 7 · hilo 1 → 2, 3, 8, 9 · hilo 2 → 4, 5, 10, 11.
- 16 iteraciones, 4 hilos, la iteración i cuesta i unidades (costo creciente). Calcula el tiempo con
staticy constatic, 1(sin overhead) y el speedup de cada uno.
Ver solución
Trabajo total: 0 + 1 + … + 15 = 120.
static: hilo 0 → 0+1+2+3 = 6, hilo 1 → 22, hilo 2 → 38, hilo 3 → 12+13+14+15 = 54. T = 54, S = 120/54 = 2.22.static, 1: hilo 0 → 0+4+8+12 = 24, hilo 1 → 28, hilo 2 → 32, hilo 3 → 36. T = 36, S = 120/36 = 3.33.
El ideal sería 120/4 = 30. Repartir por turnos mezcla iteraciones baratas y caras en cada hilo.
Autoevaluación
0/6 correctas · 0 respondidas#pragma omp for fuera de una región parallel…schedule(static): el hilo 2 hace…nowait en un for…schedule(static, 2) con 3 hilos y 12 iteraciones: el hilo 0 hace…collapse(2) sirve para…Fuente ·U4.1 Paralelismo compartido-OMP.pdf (láminas 13–17, 21) · U4.3 Paralelismo compartido-OMP.pdf (nowait, collapse) · semana_9/Ejemp02_omp_for.cpp