Compartir a través de


2. Directivas

Las directivas se basan en las directivas #pragma definidas en los estándares de C y C++. Los compiladores que admitan la API de OpenMP C y C++ incluirán una opción de línea de comandos que activa y permite interpretar todas las directivas del compilador de OpenMP.

2.1 Formato de directivas

La sintaxis de una directiva OpenMP está formalmente especifica mediante la gramática en el apéndice C e informalmente de la siguiente manera:

#pragma omp directive-name  [clause[ [,] clause]...] new-line

Cada directiva comienza con #pragma omp, para reducir el potencial de conflicto con otras directivas pragma (no OpenMP o extensiones de proveedor a OpenMP) con los mismos nombres. El resto de la directiva sigue las convenciones de los estándares de C y C++ para las directivas del compilador. En concreto, se pueden usar espacios en blanco antes y después de # y a veces se deben usar espacios en blanco para separar las palabras de una directiva. Los tokens de preprocesamiento que siguen a #pragma omp están sujetos a reemplazo de macros.

Las directivas distinguen entre mayúsculas de minúsculas. El orden en que las cláusulas aparecen en las directivas no es significativa. Las cláusulas sobre directivas pueden repetirse según sea necesario, con las restricciones que figuran en la descripción de cada cláusula. Si la variable-list aparece en una cláusula, solo debe especificar las variables. Sólo se puede especificar un nombre de directiva por directiva. Por ejemplo, no se permite la siguiente directiva:

/* ERROR - multiple directive names not allowed */
#pragma omp parallel barrier

Una directiva OpenMP se aplica como máximo a una instrucción correcta, que debe ser un bloque estructurado.

2.2 Compilación condicional

El nombre de la macro _OPENMP se define mediante implementaciones compatibles con OpenMP como la constante decimal aaaamm, que será el año y el mes de la especificación aprobada. Esta macro no debe ser el sujeto de una directiva de preprocesamiento #define o #undef.

#ifdef _OPENMP
iam = omp_get_thread_num() + index;
#endif

Si los proveedores definen extensiones en OpenMP, pueden especificar macros predefinidas adicionales.

2.3 Construcción paralela

La siguiente directiva define una región paralela, que es una región del programa que se va a ejecutar en muchos subprocesos en paralelo. Esta directiva es la construcción fundamental que inicia la ejecución en paralelo.

#pragma omp parallel [clause[ [, ]clause] ...] new-line   structured-block

La cláusula es una de las siguientes:

  • if( scalar-expression )
  • private( variable-list )
  • firstprivate( variable-list )
  • default(shared | none)
  • shared( variable-list )
  • copyin( variable-list )
  • reduction( operator : variable-list )
  • num_threads( integer-expression )

Cuando un subproceso llega a una construcción paralela, se crea un equipo de subprocesos si uno de los casos siguientes es true:

  • No hay ninguna cláusula if presente.
  • La expresión if da como resultado un valor distinto de cero.

Este subproceso se convierte en el subproceso maestro del equipo, con un número de subproceso de 0 y todos los subprocesos del equipo, incluido el subproceso maestro, ejecutan la región en paralelo. Si el valor de la expresión if es cero, la región se serializa.

Para determinar el número de subprocesos solicitados, se considerarán las siguientes reglas en orden. Se aplicará la primera regla cuya condición se cumpla:

  1. Si la cláusula num_threads está presente, el valor de la expresión entera es el número de subprocesos solicitados.

  2. Si se ha llamado a la función de biblioteca omp_set_num_threads, el valor del argumento en la llamada ejecutada más recientemente es el número de subprocesos solicitados.

  3. Si se define la variable de entorno OMP_NUM_THREADS, el valor de esta variable de entorno es el número de subprocesos solicitados.

  4. Si no se usa ninguno de los métodos anteriores, el número de subprocesos solicitados es definido por la implementación.

Si la cláusula num_threads está presente, reemplaza el número de subprocesos solicitados por la función de biblioteca omp_set_num_threads o la variable de entorno OMP_NUM_THREADS solo para la región paralela a la que se aplica. Las regiones paralelas posteriores no se ven afectadas por ella.

El número de subprocesos que ejecutan la región paralela también depende de si está habilitado el ajuste dinámico del número de subprocesos. Si el ajuste dinámico está deshabilitado, el número solicitado de subprocesos ejecutará la región paralela. Si se habilita el ajuste dinámico, el número solicitado de subprocesos es el número máximo de subprocesos que pueden ejecutar la región paralela.

Si se encuentra una región paralela mientras se deshabilita el ajuste dinámico del número de subprocesos y el número de subprocesos solicitados para la región paralela es mayor que el número que el sistema en tiempo de ejecución puede proporcionar, el comportamiento del programa está definido por la implementación. Por ejemplo, una implementación puede interrumpir la ejecución del programa o puede serializar la región paralela.

La función de biblioteca omp_set_dynamic y la variable de entorno OMP_DYNAMIC se pueden usar para habilitar y deshabilitar el ajuste dinámico del número de subprocesos.

El número de procesadores físicos que realmente hospedan los subprocesos en un momento dado está definido por la implementación. Una vez creado, el número de subprocesos del equipo permanece constante durante la duración de esa región paralela. El usuario puede cambiarlo explícitamente o automáticamente por el sistema en tiempo de ejecución de una región paralela a otra.

Cada subproceso ejecuta las instrucciones contenidas en la extensión dinámica de la región paralela y cada subproceso puede ejecutar una ruta de acceso de las instrucciones diferentes de los demás subprocesos. Las directivas encontradas fuera de la extensión léxica de una región paralela se conocen como directivas huérfanas.

Hay una barrera implícita al final de una región paralela. Solo el subproceso maestro del equipo continúa la ejecución al final de una región paralela.

Si un subproceso de un equipo que ejecuta una región paralela encuentra otra construcción paralela, este crea un nuevo equipo y se convierte en el maestro de ese nuevo equipo. Las regiones paralelas anidadas se serializan de forma predeterminada. Como resultado, un equipo compuesto por un subproceso ejecuta una región paralela anidada de forma predeterminada. El comportamiento predeterminado se puede cambiar mediante la función de biblioteca omp_set_nested en tiempo de ejecución o la variable de entorno OMP_NESTED. Sin embargo, el número de subprocesos de un equipo que ejecuta una región paralela anidada está definida por la implementación.

Las restricciones a la directiva parallel son las siguientes:

  • Como máximo, una cláusula if puede aparecer en la directiva.

  • No se especifica si se producen efectos secundarios dentro de la expresión num_threads o la expresión if.

  • Un objeto throw ejecutado dentro de una región paralela debe hacer que la ejecución se reanude dentro de la extensión dinámica del mismo bloque estructurado y el mismo subproceso que produjo la excepción.

  • Solo una cláusula num_threads puede aparecer en la directiva. La expresión num_threads se evalúa fuera del contexto de la región paralela y debe evaluarse como un valor entero positivo.

  • El orden de evaluación de las cláusulas if y num_threads no se especifica.

Referencias cruzadas

2.4 Construcciones de uso compartido

Una construcción de uso compartido distribuye la ejecución de la instrucción asociada entre los miembros del equipo que la encuentran. Las directivas de uso compartido no inician nuevos subprocesos y no hay barreras implícitas en la entrada a una construcción de uso compartido.

La secuencia de construcciones de uso compartido y las directivas barrier encontradas debe ser la misma para cada subproceso de un equipo.

OpenMP define las siguientes construcciones de uso compartido y estas construcciones se describen en las secciones siguientes:

2.4.1 Construcción for

La directiva for identifica una construcción iterativa de uso compartido que especifica que las iteraciones del bucle asociado se ejecutarán en paralelo. Las iteraciones del bucle for se distribuyen entre los subprocesos existentes en el equipo que ejecuta la construcción paralela a la que se enlaza. La sintaxis de la construcción for es la siguiente:

#pragma omp for [clause[[,] clause] ... ] new-line for-loop

La cláusula es una de las siguientes:

  • private( variable-list )
  • firstprivate( variable-list )
  • lastprivate( variable-list )
  • reduction( operator : variable-list )
  • ordered
  • schedule( kind [, chunk_size] )
  • nowait

La directiva for aplica restricciones a la estructura del bucle for correspondiente. En concreto, el bucle correspondiente for debe tener una forma canónica:

for ( init-expr ; var logical-op b ; incr-expr )

init-expr
Uno de los siguientes:

  • var = lb
  • integer-type var = lb

incr-expr
Uno de los siguientes:

  • ++ var
  • var ++
  • -- var
  • var --
  • var += incr
  • var -= incr
  • var = var + incr
  • var = incr + var
  • var = var - incr

var
Variable entera con signo. Si esta variable se compartiera de otro modo, se convertiría implícitamente en privada durante la duración de for. No modifique esta variable dentro del cuerpo de la instrucción for. A menos que se especifique la variable lastprivate, su valor después de que el bucle está indeterminado.

logical-op
Uno de los siguientes:

  • <
  • <=
  • >
  • >=

lb, b e incr
Expresiones enteras invariables de bucle. No existe sincronización durante la evaluación de estas expresiones, por lo que los efectos secundarios evaluados producen resultados indeterminados.

El formato canónico permite calcular el número de iteraciones de bucle en la entrada al bucle. Este cálculo se realiza con valores en el tipo de var, después de las promociones integrales. En concreto, si el valor de b - lb + incr no se puede representar en ese tipo, el resultado es indeterminado. Además, si logical-op es < o <=, incr-expr debe hacer que var aumente en cada iteración del bucle. Si logical-op es > o >=, incr-expr debe hacer que var disminuya en cada iteración del bucle.

La cláusula schedule especifica cómo se dividen las iteraciones del bucle for entre los subprocesos del equipo. La corrección de un programa no debe depender del subproceso que ejecute una iteración determinada. El valor chunk_size, si se especifica, debe ser una expresión de entero invariable del bucle con un valor positivo. No existe sincronización durante la evaluación de esta expresión, por lo que los efectos secundarios evaluados producen resultados indeterminados. El tipo programado kind puede tener uno de los siguientes valores:

Tabla 2-1: valores de cláusula schedule kind

Valor Descripción
static Cuando se especifica schedule(static, chunk_size ), las iteraciones se dividen en fragmentos de un tamaño especificado por chunk_size. Los fragmentos se asignan estáticamente a los subprocesos del equipo de forma rotatoria en el orden del número de subproceso. Cuando no se especifica ningún chunk_size, el espacio de iteración se divide en fragmentos que son aproximadamente iguales en tamaño, con un fragmento asignado a cada subproceso.
dinámico Cuando se especifica schedule(dynamic, chunk_size ), las iteraciones se dividen en una serie de fragmentos, cada una de las cuales contiene iteraciones chunk_size. Cada fragmento se asigna a un subproceso que está esperando una asignación. El subproceso ejecuta el fragmento de iteraciones y después espera su siguiente asignación, hasta que no se asigne ningún fragmento. El último fragmento que se va a asignar puede tener un número menor de iteraciones. Cuando no se especifica ningún chunk_size, el valor predeterminado es 1.
guiado Cuando se especifica schedule(guided, chunk_size ), las iteraciones se asignan a subprocesos en fragmentos con tamaños decrecientes. Cuando un subproceso termina su fragmento asignado de iteraciones, se le asigna dinámicamente otro fragmento hasta que no se deja ninguno. Para un chunk_size de 1, el tamaño de cada fragmento es aproximadamente el número de iteraciones sin asignar divididas por el número de subprocesos. Estos tamaños disminuyen casi exponencialmente a 1. Para un chunk_size con valor k mayor que 1, los tamaños se reducen casi exponencialmente a k, salvo que el último fragmento pueda tener menos de k iteraciones. Cuando no se especifica ningún chunk_size, el valor predeterminado es 1.
motor en tiempo de ejecución Cuando se especifica schedule(runtime), la decisión relativa a la programación se aplaza hasta el tiempo de ejecución. El tipo de programación kind y el tamaño de los fragmentos se pueden elegir en tiempo de ejecución al establecer la variable de entorno OMP_SCHEDULE. Si no se establece esta variable de entorno, la programación resultante está definida por la implementación. Cuando se especifica schedule(runtime), no se debe especificar chunk_size.

En ausencia de una cláusula schedule definida explícitamente, el valor predeterminado schedule está definido por la implementación.

Un programa compatible con OpenMP no debe depender de una programación determinada para la ejecución correcta. Un programa no debe basarse en un kind de programación que se ajuste precisamente a la descripción indicada anteriormente, ya que es posible tener variaciones en las implementaciones del mismo kind de programación en distintos compiladores. Las descripciones se pueden usar para seleccionar la programación adecuada para una situación determinada.

La cláusula ordered debe estar presente cuando las directivas ordered se enlazan a la construcción for.

Hay una barrera implícita al final de una construcción for a menos que se especifique una cláusula nowait.

Las restricciones a la directiva for son las siguientes:

  • El bucle for debe ser un bloque estructurado, además su ejecución no debe terminarse mediante una instrucción break.

  • Los valores de las expresiones de control de bucle del bucle for asociado a una directiva for deben ser los mismos para todos los subprocesos del equipo.

  • La variable for de iteración de bucle debe tener un tipo entero con signo.

  • Solo una cláusula schedule puede aparecer en la directiva for.

  • Solo una cláusula ordered puede aparecer en la directiva for.

  • Solo una cláusula nowait puede aparecer en la directiva for.

  • No se especifica si se producen o con qué frecuencia se producen efectos secundarios dentro de las expresiones chunk_size, lb, b o incr.

  • El valor de la expresión chunk_size debe ser el mismo para todos los subprocesos del equipo.

Referencias cruzadas

2.4.2 Construcción de secciones

La directiva sections identifica una construcción de uso compartido de trabajo no bidireccional que especifica un conjunto de construcciones que se van a dividir entre subprocesos de un equipo. Cada sección se ejecuta una vez por un subproceso en el equipo. La sintaxis de la directiva sections es la siguiente:

#pragma omp sections [clause[[,] clause] ...] new-line
   {
   [#pragma omp section new-line]
      structured-block
   [#pragma omp section new-linestructured-block ]
...
}

La cláusula es una de las siguientes:

  • private( variable-list )
  • firstprivate( variable-list )
  • lastprivate( variable-list )
  • reduction( operator : variable-list )
  • nowait

Cada sección va precedida de una directiva section, aunque la directiva section es opcional para la primera sección. Las directivas section deben aparecer dentro de la extensión léxica de la directiva sections. Hay una barrera implícita al final de una construcción sections a menos que se especifique nowait.

Las restricciones a la directiva sections son las siguientes:

  • Una directiva section no debe aparecer fuera de la extensión léxica de la directiva sections.

  • Solo una cláusula nowait puede aparecer en la directiva sections.

Referencias cruzadas

  • Cláusulas private, firstprivate, lastprivate, y reduction (sección 2.7.2)

2.4.3 Construcción única

La directiva single identifica una construcción que especifica que solo un subproceso del equipo ejecuta el bloque estructurado asociado (no necesariamente el subproceso maestro). La sintaxis de la directiva single es la siguiente:

#pragma omp single [clause[[,] clause] ...] new-linestructured-block

La cláusula es una de las siguientes:

  • private( variable-list )
  • firstprivate( variable-list )
  • copyprivate( variable-list )
  • nowait

Hay una barrera implícita después de la construcción single a menos que se especifique una cláusula nowait.

Las restricciones a la directiva single son las siguientes:

  • Solo una cláusula nowait puede aparecer en la directiva single.
  • La cláusula copyprivate no se debe usar con la cláusula nowait.

Referencias cruzadas

2.5 Construcciones de uso compartido paralelas combinadas

Las construcciones combinadas de uso compartido paralelo son métodos abreviados para especificar una región paralela que solo tiene una construcción de uso compartido. La semántica de estas directivas es la misma que la especificación explícita de una directiva parallel seguida de una única construcción de uso compartido.

En las secciones siguientes se describen las construcciones combinadas de uso compartido paralelo:

2.5.1 Construcción parallel for

La directiva parallel for es un acceso directo para una región parallel que solo contiene una única directiva for. La sintaxis de la directiva parallel for es la siguiente:

#pragma omp parallel for [clause[[,] clause] ...] new-linefor-loop

Esta directiva permite todas las cláusulas de la directiva parallel y la directiva for, excepto la cláusula nowait, con significados y restricciones idénticos. La semántica es la misma que la especificación explícita de una directiva parallel inmediatamente seguida de una directiva for.

Referencias cruzadas

2.5.2 Construcción de secciones paralelas

La directiva parallel sections proporciona un formulario de método abreviado para especificar un área parallel que solo tiene una única directiva sections. La semántica es la misma que la especificación explícita de una directiva parallel inmediatamente seguida de una directiva sections. La sintaxis de la directiva parallel sections es la siguiente:

#pragma omp parallel sections  [clause[[,] clause] ...] new-line
   {
   [#pragma omp section new-line]
      structured-block
   [#pragma omp section new-linestructured-block  ]
   ...
}

La cláusula puede ser una de las cláusulas aceptadas por las directivas parallel y sections, excepto la cláusula nowait.

Referencias cruzadas

2.6 Directivas maestras y de sincronización

En las secciones siguientes se describe:

2.6.1 Construcción master

La directiva master identifica una construcción que especifica un bloque estructurado ejecutado por el subproceso maestro del equipo. La sintaxis de la directiva master es la siguiente:

#pragma omp master new-linestructured-block

Otros subprocesos del equipo no ejecutan el bloque estructurado asociado. No hay ninguna barrera implícita en la entrada o salida de la construcción maestra.

2.6.2 Construcción critical

La directiva critical identifica una construcción que restringe la ejecución del bloque estructurado asociado a un único subproceso a la vez. La sintaxis de la directiva critical es la siguiente:

#pragma omp critical [(name)]  new-linestructured-block

Se puede usar un nombre opcional para identificar la región crítica. Los identificadores usados para identificar una región crítica tienen una vinculación externa y están en un espacio de nombres independiente de los espacios de nombres usados por etiquetas, tags, miembros e identificadores ordinarios.

Un subproceso espera al principio de una región crítica hasta que ningún otro subproceso ejecute una región crítica (en cualquier parte del programa) con el mismo nombre. Todas las directivas sin nombre critical se asignan al mismo nombre no especificado.

2.6.3 Directiva barrier

La directiva barrier sincroniza todos los subprocesos de un equipo. Cuando se encuentra, cada subproceso del equipo espera hasta que todos los demás hayan llegado a este punto. La sintaxis de la directiva barrier es la siguiente:

#pragma omp barrier new-line

Después de que todos los subprocesos del equipo hayan encontrado la barrera, cada subproceso del equipo comienza a ejecutar las instrucciones después de la directiva de barrera en paralelo. Dado que la directiva barrier no tiene una instrucción de lenguaje C como parte de su sintaxis, hay algunas restricciones en su colocación dentro de un programa. Para obtener más información sobre la gramática formal, consulte el apéndice C. En el ejemplo siguiente se muestran estas restricciones.

/* ERROR - The barrier directive cannot be the immediate
*          substatement of an if statement
*/
if (x!=0)
   #pragma omp barrier
...

/* OK - The barrier directive is enclosed in a
*      compound statement.
*/
if (x!=0) {
   #pragma omp barrier
}

2.6.4 Construcción atomic

La directiva atomic garantiza que una ubicación de memoria específica se actualice de forma atómica, en lugar de exponerla a la posibilidad de varios subprocesos de escritura simultánea. La sintaxis de la directiva atomic es la siguiente:

#pragma omp atomic new-lineexpression-stmt

La instrucción de expresión debe tener una de las formas siguientes:

  • x binop = expr
  • x ++
  • ++ x
  • x --
  • -- x

En las expresiones anteriores:

  • x es una expresión lvalue con tipo escalar.

  • expr es una expresión con tipo escalar y no hace referencia al objeto designado por x.

  • binop no es un operador de sobrecarga y es uno de +, *, -, /, &, ^, |, << o >>.

Aunque está definido por la implementación, si una implementación reemplaza todas las directivas atomic por las directivas critical que tienen el mismo nombre único, la directiva atomic permite una mejor optimización. A menudo, hay instrucciones de hardware disponibles que pueden realizar la actualización atómica con la menor sobrecarga.

Solo la carga y el almacén del objeto designado por x son atómicas; la evaluación de expr no es atómica. Para evitar las condiciones de carrera, todas las actualizaciones de la ubicación en paralelo deben protegerse con la directiva atomic, excepto las que se sabe que están libres de condiciones de carrera.

Las restricciones a la directiva atomic son las siguientes:

  • Todas las referencias atómicas a la ubicación de almacenamiento x a lo largo del programa deben tener un tipo compatible.

Ejemplos

extern float a[], *p = a, b;
/* Protect against races among multiple updates. */
#pragma omp atomic
a[index[i]] += b;
/* Protect against races with updates through a. */
#pragma omp atomic
p[i] -= 1.0f;

extern union {int n; float x;} u;
/* ERROR - References through incompatible types. */
#pragma omp atomic
u.n++;
#pragma omp atomic
u.x -= 1.0f;

2.6.5 Directiva flush

La directiva flush, ya sea explícita o implícita, especifica un punto de secuencia "entre subprocesos" en el que se requiere la implementación para asegurarse de que todos los subprocesos de un equipo tengan una vista coherente de determinados objetos (especificados a continuación) en la memoria. Esto significa que las evaluaciones anteriores de expresiones que hacen referencia a esos objetos están completas y las evaluaciones posteriores aún no empiezan. Por ejemplo, los compiladores deben restaurar los valores de los objetos de los registros en la memoria, y es posible que el hardware tenga que vaciar los búferes de escritura en la memoria y recargar los valores de los objetos de la memoria.

La sintaxis de la directiva flush es la siguiente:

#pragma omp flush [(variable-list)]  new-line

Si las variables pueden designar todos los objetos que requieren de sincronización, esas variables se pueden especificar en la variable-list opcional. Si un puntero está presente en la variable-list, es el propio puntero el que se vacía y no el objeto al que hace referencia el puntero.

Una directiva flush sin una variable-list sincroniza todos los objetos compartidos excepto los objetos inaccesibles con duración de almacenamiento automática. (Es probable que tenga más sobrecarga que una flush con una variable-list). Una directiva flush sin una lista de variables está implícita para las siguientes directivas:

  • barrier
  • A la entrada y salida de critical
  • A la entrada y salida de ordered
  • A la entrada y salida de parallel
  • A la salida de for
  • A la salida de sections
  • A la salida de single
  • A la entrada y salida de parallel for
  • A la entrada y salida de parallel sections

La directiva no está implícita si existe una cláusula nowait. Debe tenerse en cuenta que la directiva flush no está implícita para ninguno de los siguientes elementos:

  • A la entrada de for
  • A la entrada o salida de master
  • A la entrada de sections
  • A la entrada de single

Una referencia que tiene acceso al valor de un objeto con un tipo calificado volátil se comporta como si hubiera una directiva flush que especifique ese objeto en el punto de secuencia anterior. Una referencia que modifica el valor de un objeto con un tipo calificado volátil se comporta como si hubiera una directiva flush que especifique ese objeto en el siguiente punto de secuencia.

Dado que la directiva flush no tiene una instrucción de lenguaje C como parte de su sintaxis, hay algunas restricciones en su colocación dentro de un programa. Para obtener más información sobre la gramática formal, consulte el apéndice C. En el ejemplo siguiente se muestran estas restricciones.

/* ERROR - The flush directive cannot be the immediate
*          substatement of an if statement.
*/
if (x!=0)
   #pragma omp flush (x)
...

/* OK - The flush directive is enclosed in a
*      compound statement
*/
if (x!=0) {
   #pragma omp flush (x)
}

Las restricciones a la directiva flush son las siguientes:

  • Una variable especificada en una directiva flush no debe tener un tipo de referencia.

2.6.6 Construcción ordered

El bloque estructurado que sigue a una directiva ordered se ejecuta en el orden en que las iteraciones se ejecutarían en un bucle secuencial. La sintaxis de la directiva ordered es la siguiente:

#pragma omp ordered new-linestructured-block

Una directiva ordered debe estar dentro de la extensión dinámica de una construcción for o parallel for. La directiva for o parallel for a la que se enlaza la construcción ordered debe tener una cláusula especificada ordered como se describe en la sección 2.4.1. En la ejecución de una construcción for o parallel for con una cláusula ordered, las construcciones ordered se ejecutan estrictamente en el orden en que se ejecutarían en una ejecución secuencial del bucle.

Las restricciones a la directiva ordered son las siguientes:

  • Una iteración de un bucle con una construcción for no debe ejecutar la misma directiva ordenada más de una vez y no debe ejecutar más de una directiva ordered.

2.7 Entorno de datos

En esta sección se presenta una directiva y varias cláusulas para controlar el entorno de datos durante la ejecución de regiones paralelas, como se indica a continuación:

  • Se proporciona una directiva threadprivate para hacer que las variables de ámbito de archivo, de espacio de nombres o de ámbito de bloque estáticos sean locales para un subproceso.

  • En la sección 2.7.2 se describen las cláusulas que se pueden especificar en las directivas para controlar los atributos de uso compartido de variables durante la duración de las construcciones paralelas o de uso compartido.

2.7.1 Directiva threadprivate

La directiva threadprivate hace que las variables de ámbito de archivo, con espacio de nombres o ámbito de bloque estático especificadas en la variable-list sean privadas para un subproceso. variable-list es una lista separada por comas de variables que no tienen un tipo incompleto. La sintaxis de la directiva threadprivate es la siguiente:

#pragma omp threadprivate(variable-list) new-line

Cada copia de una variable threadprivate se inicializa una vez, en un punto no especificado del programa antes de la primera referencia a esa copia y de la manera habitual (es decir, como la copia maestra se inicializaría en una ejecución serie del programa). Tenga en cuenta que si se hace referencia a un objeto en un inicializador explícito de una variable threadprivate y el valor del objeto se modifica antes de la primera referencia a una copia de la variable, no se especifica el comportamiento.

Al igual que con cualquier variable privada, un subproceso no debe hacer referencia a la copia de otro subproceso de un objeto threadprivate. Durante las regiones en serie y las regiones maestras del programa, las referencias serán a la copia del subproceso maestro del objeto.

Una vez ejecutada la primera región paralela, se garantiza que los datos de los objetos threadprivate se conserven solo si el mecanismo de subprocesos dinámicos se ha deshabilitado y si el número de subprocesos permanece sin cambios para todas las regiones paralelas.

Las restricciones a la directiva threadprivate son las siguientes:

  • Una directiva threadprivate para las variables de ámbito de archivo o de ámbito de espacio de nombres debe aparecer fuera de cualquier definición o declaración y debe preceder léxicamente a todas las referencias de cualquiera de las variables de su lista.

  • Cada variable de la variable-list de una directiva threadprivate en el ámbito de archivo o espacio de nombres debe hacer referencia a una declaración de variable en el ámbito de archivo o espacio de nombres que precede léxicamente a la directiva.

  • Una directiva threadprivate para las variables estáticas del ámbito de bloque debe aparecer en el ámbito de la variable y no en un ámbito anidado. La directiva debe preceder léxicamente a todas las referencias de cualquiera de las variables de su lista.

  • Cada variable de la variable-list de una directiva threadprivate en el ámbito de bloque, debe referirse a una declaración de variable en el mismo ámbito que precede léxicamente a la directiva. La declaración de variable debe usar el especificador de clase de almacenamiento estático.

  • Si se especifica una variable en una directiva threadprivate en una unidad de traducción, se debe especificar en una directiva threadprivate en cada unidad de traducción en la que se declara.

  • Una variable threadprivate no debe aparecer en ninguna cláusula excepto en copyin, copyprivate, schedule, num_threads o la cláusula if.

  • La dirección de una variable threadprivate no es una constante de dirección.

  • Una variable threadprivate no debe tener un tipo incompleto o un tipo de referencia.

  • Una variable threadprivate con tipo de clase no POD debe tener un constructor de copia accesible e inequívoca si se declara con un inicializador explícito.

En el ejemplo siguiente se muestra cómo la modificación de una variable que aparece en un inicializador puede provocar un comportamiento no especificado y también cómo evitar este problema mediante un objeto auxiliar y un constructor de copia.

int x = 1;
T a(x);
const T b_aux(x); /* Capture value of x = 1 */
T b(b_aux);
#pragma omp threadprivate(a, b)

void f(int n) {
   x++;
   #pragma omp parallel for
   /* In each thread:
   * Object a is constructed from x (with value 1 or 2?)
   * Object b is copy-constructed from b_aux
   */
   for (int i=0; i<n; i++) {
      g(a, b); /* Value of a is unspecified. */
   }
}

Referencias cruzadas

2.7.2 Cláusulas de atributos de uso compartido de datos

Varias directivas aceptan cláusulas que permiten a un usuario controlar los atributos de uso compartido de variables durante la duración de la región. Las cláusulas de atributo de uso compartido solo se aplican a las variables en la extensión léxica de la directiva sobre la que aparece la cláusula. No todas las cláusulas siguientes están permitidas en todas las directivas. La lista de cláusulas que son válidas en una directiva determinada se describe con la directiva.

Si una variable es visible cuando se encuentra una construcción paralela o de uso compartido, y la variable no se especifica en una cláusula de atributo sharing o directiva threadprivate, entonces se comparte la variable. Las variables estáticas declaradas dentro de la extensión dinámica de una región paralela se comparten. Se comparte la memoria asignada del montón (por ejemplo, mediante malloc() en C o C++ o el operador new en C++). (Sin embargo, el puntero a esta memoria puede ser privado o compartido). Las variables con duración de almacenamiento automático declaradas dentro de la extensión dinámica de una región paralela son privadas.

La mayoría de las cláusulas aceptan un argumento variable-list, que es una lista separada por comas de variables que son visibles. Si una variable a la que se hace referencia en una cláusula de atributo de uso compartido de datos tiene un tipo derivado de una plantilla y no hay otras referencias a esa variable en el programa, el comportamiento es indefinido.

Todas las variables que aparecen dentro de las cláusulas de directiva deben ser visibles. Las cláusulas se pueden repetir según sea necesario, pero no se puede especificar ninguna variable en más de una cláusula, salvo que se pueda especificar una variable firstprivate y en una cláusula lastprivate.

En las secciones siguientes se describen las cláusulas de atributo de uso compartido de datos:

2.7.2.1 private

La cláusula private declara que las variables de la variable-list son privadas para cada subproceso de un equipo. La sintaxis de la cláusula private es la siguiente:

private(variable-list)

El comportamiento de una variable especificada en una cláusula private es el siguiente. Se asigna un nuevo objeto con duración de almacenamiento automática para la construcción. El tamaño y la alineación del nuevo objeto vienen determinados por el tipo de la variable. Esta asignación se produce una vez para cada subproceso del equipo y se invoca un constructor predeterminado para un objeto de la clase si es necesario; de lo contrario, el valor inicial es indeterminado. El objeto original al que hace referencia la variable tiene un valor indeterminado al entrar en la construcción, no debe ser modificado dentro de la extensión dinámica de la construcción y tiene un valor indeterminado al salir de la construcción.

En la extensión léxica de la construcción de directiva, la variable hace referencia al nuevo objeto privado asignado por el subproceso.

Las restricciones a la cláusula private son las siguientes:

  • Una variable con un tipo de clase especificado en una cláusula private debe tener un constructor predeterminado accesible e inequívoco.

  • Una variable especificada en una cláusula private no debe tener un tipo calificado const a menos que tenga un tipo de clase con un miembro mutable.

  • Una variable especificada en una cláusula private no debe tener un tipo incompleto o un tipo de referencia.

  • Las variables que aparecen en la cláusula reduction de una directiva parallel no pueden especificarse en una cláusula private en una directiva de uso compartido que se enlaza a la construcción paralela.

2.7.2.2 firstprivate

La cláusula firstprivate proporciona un superconjunto de la funcionalidad proporcionada por la cláusula private. La sintaxis de la cláusula firstprivate es la siguiente:

firstprivate(variable-list)

Las variables especificadas en la variable-list tienen semántica de cláusulas private, como se describe en la sección 2.7.2.1. La inicialización o construcción ocurre como si se realizara una vez por subproceso, antes de la ejecución del subproceso de la construcción. Para una cláusula firstprivate en una construcción paralela, el valor inicial del nuevo objeto privado es el valor del objeto original que existe inmediatamente antes de la construcción paralela del subproceso que lo encuentra. Para una cláusula firstprivate en una construcción de uso compartido, el valor inicial del nuevo objeto privado para cada subproceso que ejecuta la construcción de uso compartido es el valor del objeto original que existe antes del momento en que el mismo subproceso encuentra la construcción de uso compartido. Además, para los objetos de C++, el nuevo objeto privado para cada subproceso se copia a partir del objeto original.

Las restricciones a la cláusula firstprivate son las siguientes:

  • Una variable especificada en una cláusula firstprivate no debe tener un tipo incompleto o un tipo de referencia.

  • Una variable con un tipo de clase especificado como firstprivate debe tener un constructor accesible e inequívoco.

  • Las variables que son privadas dentro de una región paralela o que aparecen en la cláusula reduction de una directiva parallel no pueden especificarse en una cláusula firstprivate en una directiva de uso compartido que se enlaza a la construcción paralela.

2.7.2.3 lastprivate

La cláusula lastprivate proporciona un superconjunto de la funcionalidad proporcionada por la cláusula private. La sintaxis de la cláusula lastprivate es la siguiente:

lastprivate(variable-list)

Las variables especificadas en la variable-list tienen la semántica de cláusulas private. Cuando aparece una cláusula lastprivate en la directiva que identifica una construcción de uso compartido, el valor de cada variable lastprivate de la última iteración secuencial del bucle asociado o la directiva léxica de la última de sección, se asigna al objeto original de la variable. Las variables que no tienen asignado un valor por la última iteración de for o parallel for, o por la última sección léxica de la directiva sections o parallel sections, tienen valores indeterminados después de la construcción. Los subobjetos sin asignar también tienen un valor indeterminado después de la construcción.

Las restricciones a la cláusula lastprivate son las siguientes:

  • Se aplican todas las restricciones para private.

  • Una variable con un tipo de clase especificado como lastprivate debe tener un operador de asignación de copia accesible e inequívoco.

  • Las variables que son privadas dentro de una región paralela o que aparecen en la cláusula reduction de una directiva parallel no pueden especificarse en una cláusula lastprivate en una directiva de uso compartido que se enlaza a la construcción paralela.

2.7.2.4 shared

Esta cláusula comparte variables que aparecen en la variable-list entre todos los subprocesos de un equipo. Todos los subprocesos de un equipo acceden a la misma área de almacenamiento para las variables shared.

La sintaxis de la cláusula shared es la siguiente:

shared(variable-list)

2.7.2.5 default

La cláusula default permite al usuario afectar a los atributos de uso compartido de datos de las variables. La sintaxis de la cláusula default es la siguiente:

default(shared | none)

Especificar default(shared) es equivalente a enumerar explícitamente cada variable visible actualmente en una cláusula shared, a menos que sea threadprivate o const calificado. En ausencia de una cláusula explícita default, el comportamiento predeterminado es el mismo que si se hubiera especificado default(shared).

Especificar default(none) requiere que al menos uno de los siguientes valores sea true para cada referencia a una variable en la extensión léxica de la construcción paralela:

  • La variable se muestra explícitamente en una cláusula de atributo de uso compartido de datos de una construcción que contiene la referencia.

  • La variable se declara dentro de la construcción paralela.

  • La variable es threadprivate.

  • La variable tiene un tipo calificado const.

  • La variable es la variable de control de bucle para un bucle for que sigue inmediatamente a una directiva for o parallel for y la referencia de variable aparece dentro del bucle.

La especificación de una variable en una cláusula firstprivate, lastprivate o reduction de una directiva delimitada, se produce una referencia implícita a la variable en el contexto envolvente. Estas referencias implícitas también están sujetas a los requisitos enumerados anteriormente.

Solo se puede especificar una sola cláusula default en una directiva parallel.

El atributo predeterminado de uso compartido de datos de una variable se puede invalidar mediante las cláusulas private, firstprivate, lastprivate, reduction y shared, como se muestra en el ejemplo siguiente:

#pragma  omp  parallel  for  default(shared)  firstprivate(i)\
   private(x)  private(r)  lastprivate(i)

2.7.2.6 reduction

Esta cláusula realiza una reducción en las variables escalares que aparecen en la variable-list, con el operador op. La sintaxis de la cláusula reduction es la siguiente:

reduction( op : variable-list )

Se suele especificar una reducción para una instrucción con una de las siguientes formas:

  • x = x op expr
  • x binop = expr
  • x = expr op x (excepto la resta)
  • x ++
  • ++ x
  • x --
  • -- x

donde:

x
Una de las variables de reducción especificadas en la lista.

variable-list
Lista separada por comas de variables de reducción escalar.

expr
Expresión con tipo escalar que no hace referencia a x.

op
No es un operador sobrecargado, sino uno de +, *, -, &, ^, |, && o ||.

binop
No es un operador sobrecargado, sino uno de +, *, -, &, ^ o |.

A continuación se muestra un ejemplo de la cláusula reduction:

#pragma omp parallel for reduction(+: a, y) reduction(||: am)
for (i=0; i<n; i++) {
   a += b[i];
   y = sum(y, c[i]);
   am = am || b[i] == c[i];
}

Como se muestra en el ejemplo, un operador puede estar oculto dentro de una llamada de función. El usuario debe tener cuidado de que el operador especificado en la cláusula reduction coincida con la operación de reducción.

Aunque el operando derecho del operador || no tiene efectos secundarios en este ejemplo, se permiten, pero deben usarse con cuidado. En este contexto, un efecto secundario que está garantizado que no ocurra durante la ejecución secuencial del bucle puede ocurrir durante la ejecución en paralelo. Esta diferencia puede producirse porque el orden de ejecución de las iteraciones es indeterminado.

El operador se usa para determinar el valor inicial de las variables privadas usadas por el compilador para la reducción y para determinar el operador de finalización. Especificar el operador explícitamente permite que la instrucción de reducción esté fuera de la extensión léxica de la construcción. Se puede especificar cualquier número de cláusulas reduction en la directiva, pero una variable puede aparecer como máximo en una cláusula reduction para esa directiva.

Se crea una copia privada de cada variable de la variable-list, una para cada subproceso, como si se hubiera usado la cláusula private. La copia privada se inicializa según el operador (consulte la tabla siguiente).

Al final de la región para la que se especificó la cláusula reduction, el objeto original se actualiza para reflejar el resultado de combinar su valor original con el valor final de cada una de las copias privadas mediante el operador especificado. Los operadores de reducción son asociativos (excepto la resta) y el compilador puede volver a asociar libremente el cálculo del valor final. (Los resultados parciales de una reducción de resta se agregan para formar el valor final).

El valor del objeto original se vuelve indeterminado cuando el primer subproceso alcanza la cláusula contenedora y permanece así hasta que se completa el cálculo de reducción. Normalmente, el cálculo se completará al final de la construcción; sin embargo, si la cláusula reduction se usa en una construcción a la que nowait también se aplica, el valor del objeto original permanece indeterminado hasta que se haya realizado una sincronización de barreras para asegurarse de que todos los subprocesos hayan completado la cláusula reduction.

En la tabla siguiente se enumeran los operadores que son válidos y sus valores de inicialización canónicos. El valor de inicialización real será coherente con el tipo de datos de la variable de reducción.

Operador Inicialización
+ 0
* 1
- 0
& ~0
| 0
^ 0
&& 1
|| 0

Las restricciones a la cláusula reduction son las siguientes:

  • El tipo de las variables de la cláusula reduction debe ser válido para el operador de reducción, salvo que nunca se permitan los tipos de puntero y los tipos de referencia.

  • Una variable especificada en la cláusula reduction no debe ser const calificado.

  • Las variables que son privadas dentro de una región paralela o que aparecen en la cláusula reduction de una directiva parallel no pueden especificarse en una cláusula reduction en una directiva de uso compartido que se enlaza a la construcción paralela.

    #pragma omp parallel private(y)
    { /* ERROR - private variable y cannot be specified
                  in a reduction clause */
        #pragma omp for reduction(+: y)
        for (i=0; i<n; i++)
           y += b[i];
    }
    
    /* ERROR - variable x cannot be specified in both
                a shared and a reduction clause */
    #pragma omp parallel for shared(x) reduction(+: x)
    

2.7.2.7 copyin

La cláusula copyin proporciona un mecanismo para asignar el mismo valor a las variables threadprivate para cada subproceso del equipo que ejecuta la región paralela. Para cada variable especificada en una cláusula copyin, el valor de la variable en el subproceso maestro del equipo se copia, como si fuera asignación, a las copias privadas de subprocesos al principio de la región paralela. La sintaxis de la cláusula copyin es la siguiente:

copyin(
variable-list
)

Las restricciones a la cláusula copyin son las siguientes:

  • Una variable que está especificada en la cláusula copyin debe tener un operador de asignación de copia accesible e inequívoco.

  • Una variable que está especificada en la cláusula copyin debe ser una variable threadprivate.

2.7.2.8 copyprivate

La cláusula copyprivate proporciona un mecanismo para usar una variable privada para transmitir un valor de un miembro de un equipo a los demás miembros. Es una alternativa al uso de una variable compartida para el valor cuando resulte complicado proporcionar esta variable compartida (por ejemplo, en una recursividad que requiere una variable diferente en cada nivel). La cláusula copyprivate solo puede aparecer en la directiva single.

La sintaxis de la cláusula copyprivate es la siguiente:

copyprivate(
variable-list
)

El efecto de la cláusula copyprivate en las variables de su variable-list se produce después de la ejecución del bloque estructurado asociado a la construcción single y antes de que cualquiera de los subprocesos del equipo haya dejado la barrera al final de la construcción. Entonces, en todos los demás subprocesos del equipo, para cada variable de la variable-list, esa variable se define (como si fuera asignación) con el valor de la variable correspondiente en el subproceso que ejecutó el bloque estructurado de la construcción.

Las restricciones a la cláusula copyprivate son las siguientes:

  • Una variable especificada en la cláusula copyprivate no debe aparecer en una cláusula private o firstprivate para la misma directiva single.

  • Si se encuentra una directiva single con una cláusula copyprivate en la extensión dinámica de una región paralela, todas las variables especificadas en la cláusula copyprivate deben ser privadas en el contexto envolvente.

  • Una variable que está especificada en la cláusula copyprivate debe tener un operador de asignación de copia accesible e inequívoco.

2.8 Enlace de directivas

El enlace dinámico de las directivas debe cumplir las reglas siguientes:

  • Las directivas for, sections, single, master y barrier se enlazan dinámicamente a parallel, si es que existe, independientemente del valor de cualquier cláusula if que pueda estar presente en esa directiva. Si no se está ejecutando ninguna región paralela actualmente, un equipo compuesto solo por el subproceso maestro ejecuta las directivas.

  • La directiva ordered se enlaza a la directiva for que la enlaza dinámicamente.

  • La directiva atomic aplica el acceso exclusivo con respecto a las directivas atomic de todos los subprocesos, no solo al equipo actual.

  • La directiva critical aplica el acceso exclusivo con respecto a las directivas critical de todos los subprocesos, no solo al equipo actual.

  • Una directiva nunca se puede enlazarse a ninguna directiva fuera del parallel más cercano de forma dinámica.

2.9 Anidamiento de directivas

El anidamiento dinámico de las directivas debe cumplir las reglas siguientes:

  • Una directiva parallel de forma dinámica dentro de otra directiva parallel establece lógicamente un nuevo equipo, que se compone solo del subproceso actual, a menos que se habilite el paralelismo anidado.

  • Las directivas for, sections y single que se enlazan a la misma directiva parallel no se pueden anidar entre sí.

  • Las directivas critical con el mismo nombre no se pueden anidar entre sí. Tenga en cuenta que esta restricción no es suficiente para evitar el interbloqueo.

  • Las directivas for, sections y single no se permiten en la extensión dinámica de las regiones critical, ordered y master si las directivas se enlazan a la mismas directiva parallel que las regiones.

  • Las directivas barrier no se permiten en la extensión dinámica de las regiones for, ordered, sections, single, master y critical si las directivas se enlazan a la mismas directiva parallel que las regiones.

  • Las directivas master, no se permiten en la extensión dinámica de las regiones for, sections y single si las directivas master se enlazan a la mismas directiva parallel que las directivas de uso compartido.

  • Las directivas ordered no se permiten en la extensión dinámica de las regiones critical si las directivas se enlazan a la mismas directiva parallel que las regiones.

  • Cualquier directiva permitida cuando se ejecuta dinámicamente dentro de una región paralela también se permite cuando se ejecuta fuera de una región paralela. Cuando se ejecuta dinámicamente fuera de una región paralela especificada por el usuario, la directiva es ejecutada por un equipo compuesto solo por el subproceso maestro.