Ir al contenido

Tareas, dependencias e ICVs

#pragma omp task [untied] [default(shared|none)] [private(list)] [shared(list)]
[firstprivate(list)] [if(expr)] [depend(tipo: lista)]
bloque

La tarea se ejecuta de inmediato o se difiere, según haya hilos libres. Patrón típico: un solo hilo crea las tareas (dentro de single) y el equipo entero las ejecuta.

U4.3 — tres tareas sin dependencias
#pragma omp parallel num_threads(2)
{
#pragma omp single
{
#pragma omp task
printf("t1 : impresion random desde el hilo : %d\n", omp_get_thread_num());
#pragma omp task
printf("t2 : impresion random desde el hilo : %d\n", omp_get_thread_num());
#pragma omp task
printf("t3 : impresion random desde el hilo : %d\n", omp_get_thread_num());
}
}

El orden de t1, t2, t3 y el hilo que ejecuta cada una cambian en cada corrida.

hilo 0 (single)task T1 out(x)task T2 in(x)task T3taskwaitcreapool de tareasT1T2T3xtomanhilo 1: T1, luego T2hilo 2: T3hilo 3: ociosoT2 lee x, que T1 escribe (out → in): T2 no empieza hasta que T1 termina.T3 no tiene depend: puede correr antes, durante o después. taskwait espera a las tres.

Cómo leerlo: el hilo 0, dentro de single, crea tres tareas y las deja en un pool. Los demás hilos las toman cuando están libres. La flecha roja es la dependencia out(x) → in(x): T2 no puede empezar hasta que T1 termine. T3 no tiene depend, así que puede correr en cualquier momento. taskwait hace que el hilo 0 espere a sus tres tareas hijas.

TipoSignificaEspera a tareas anteriores que tengan la misma variable en…
inla tarea lee la variableout o inout
out / inoutla tarea escribe (o lee y escribe)in, out o inout

Es la misma idea de las dependencias del DAG (U1.3): lectura después de escritura, escritura después de lectura y escritura después de escritura. Dos lecturas no se ordenan entre sí.

Tareas con dependencias: task depend(in/out)

Un hilo (dentro de single) crea las tareas en orden; luego cualquier hilo libre las ejecuta. Las flechas son esperas obligatorias.

int x = 1;
#pragma omp parallel
#pragma omp single
{
    #pragma omp task shared(x) depend(out: x)
    x = 2;
    #pragma omp task shared(x) depend(in: x)
    printf("x = %d", x);
}
tarea 0/2
T1depend(out: x)T2depend(in: x)x = 1
(salida vacía)
  • taskwait: el hilo espera a que terminen las tareas hijas que creó hasta ese punto.
  • taskgroup { … }: espera a todas las tareas creadas dentro del bloque y sus descendientes. En el ejemplo de U4.3, start_background_work() se crea fuera del grupo, así que sigue corriendo libre mientras cada paso espera solo al recorrido del árbol (compute_tree). Con taskwait participarían todas las tareas hijas.
  • taskyield: la tarea actual cede el hilo para que corra otra tarea. Mejora la concurrencia cuando hay tareas largas que podrían acaparar un hilo.

Cada región/tarea recibe una copia de las ICVs, que controlan el comportamiento del programa:

FunciónControla
omp_set_num_threads(n)hilos de la próxima región paralela
omp_set_dynamic(0/1)si el runtime puede ajustar el número de hilos
omp_set_nested(0/1)permitir regiones paralelas anidadas
omp_set_max_active_levels(n)cuántos niveles anidados pueden estar activos

Consultas útiles: omp_get_num_threads(), omp_get_thread_num(), omp_get_max_threads(), omp_get_max_active_levels(), omp_get_wtime().

U4.3 — paralelismo anidado
omp_set_nested(1);
omp_set_max_active_levels(8);
omp_set_dynamic(0);
omp_set_num_threads(2);
#pragma omp parallel // nivel externo: 2 hilos
{
omp_set_num_threads(3);
#pragma omp parallel // cada uno abre un equipo de 3
{
omp_set_num_threads(4);
#pragma omp single
printf("Inner: ..."); // se imprime 2 veces (una por equipo interno)
}
#pragma omp barrier
#pragma omp single
printf("Outer: ..."); // se imprime 1 vez
}

No siempre hace falta for: cada hilo puede calcular qué parte del arreglo le toca a partir de su id y del número de hilos. Es la misma idea de “n/p datos por proceso” de MPI, pero en memoria compartida.

U4.3 — cada hilo elige su subdominio
#pragma omp parallel default(shared) private(iam, nt, ipoints, istart)
{
iam = omp_get_thread_num();
nt = omp_get_num_threads();
ipoints = npoints / nt; // tamaño del bloque
istart = iam * ipoints; // inicio del bloque
if (iam == nt - 1) // el último hilo hace lo que sobra
ipoints = npoints - istart;
subdomain(x, istart, ipoints);
}
int x = 1;
#pragma omp parallel
#pragma omp single
{
#pragma omp task shared(x) depend(out: x)
x = 2;
#pragma omp task shared(x) depend(in: x)
printf("x+1 = %d. ", x + 1);
#pragma omp task shared(x) depend(in: x)
printf("x+2 = %d\n", x + 2);
}

¿Cuáles son las salidas posibles? ¿Puede salir x+1 = 2?

Ver solución

Las dos tareas que imprimen dependen de la primera (in después de out), así que siempre ven x = 2. Entre ellas no hay dependencia (dos in), por lo que el orden es libre:

  • x+1 = 3. x+2 = 4
  • x+2 = 4 y luego x+1 = 3.

x+1 = 2 no puede salir: requeriría leer x antes de que la primera tarea la cambie.

  • Crear las tareas fuera de single: cada hilo del equipo las crea, y se ejecutan p veces.
  • Olvidar que las variables locales del creador son, por defecto, firstprivate en la tarea: si la tarea las modifica, el cambio no se ve fuera. Usa shared y luego taskwait.
  • Usar el resultado de una tarea sin taskwait (o taskgroup) antes.
  • Esperar que depend ordene tareas de padres distintos: solo ordena tareas hermanas (creadas por la misma tarea).

Se crean en este orden: T1 depend(out: a), T2 depend(in: a), T3 depend(out: a), T4 depend(in: b). ¿Qué órdenes son posibles?

Ver solución
  • T1 → T2 (T2 lee lo que T1 escribe).
  • T2 → T3 (T3 no puede escribir a antes de que T2 lo lea).
  • T1 → T3 (dos escrituras de a en orden).
  • T4 no comparte variables con nadie: va en cualquier lugar.

Así que T1, T2, T3 siempre en ese orden, y T4 en cualquiera de las 4 posiciones: 4 órdenes posibles (T4 T1 T2 T3, T1 T4 T2 T3, T1 T2 T4 T3, T1 T2 T3 T4), además de que T4 puede correr a la vez que cualquiera.

Autoevaluación

0/5 correctas · 0 respondidas
1. Una tarea con depend(in: x) espera a tareas anteriores con…
2. Orden de creación: in: x (printf) y luego out: x (x = 2), con x = 1 al inicio. Se imprime…
3. taskwait espera a…
4. Sin omp_set_nested(1), un parallel dentro de otro parallel usa…
5. ¿Por qué se suelen crear las tareas dentro de single?

Fuente ·U4.3 Paralelismo compartido-OMP.pdf (ICVs, parallel, task, depend, taskgroup, taskyield)