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:
Si la cláusula
num_threads
está presente, el valor de la expresión entera es el número de subprocesos solicitados.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.Si se define la variable de entorno
OMP_NUM_THREADS
, el valor de esta variable de entorno es el número de subprocesos solicitados.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ónnum_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
ynum_threads
no se especifica.
Referencias cruzadas
- Cláusulas
private
,firstprivate
,default
,shared
,copyin
yreduction
(sección 2.7.2) - Variable de entorno OMP_NUM_THREADS
- Función de biblioteca de omp_set_dynamic
- Variable de entorno OMP_DYNAMIC
- función omp_set_nested
- Variable de entorno evaluada OMP_NESTED
- Función de biblioteca omp_set_num_threads
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ónbreak
.Los valores de las expresiones de control de bucle del bucle
for
asociado a una directivafor
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 directivafor
.Solo una cláusula
ordered
puede aparecer en la directivafor
.Solo una cláusula
nowait
puede aparecer en la directivafor
.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
- Cláusulas
private
,firstprivate
,lastprivate
, yreduction
(sección 2.7.2) - Variable de entorno OMP_SCHEDULE
- Construcción ordered
- Cláusula schedule
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 directivasections
.Solo una cláusula
nowait
puede aparecer en la directivasections
.
Referencias cruzadas
- Cláusulas
private
,firstprivate
,lastprivate
, yreduction
(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 directivasingle
. - La cláusula
copyprivate
no se debe usar con la cláusulanowait
.
Referencias cruzadas
- Cláusulas
private
,firstprivate
, ycopyprivate
(sección 2.7.2)
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:
- Directiva parallel for
- Directiva parallel sections
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
- Directiva parallel
- Directiva for
- Cláusulas de atributo de datos
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:
- Construcción master
- Construcción critical
- Directiva barrier
- Construcción atomic
- Directiva flush
- Construcción ordered
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 directivaordered
.
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 directivathreadprivate
en cada unidad de traducción en la que se declara.Una variable
threadprivate
no debe aparecer en ninguna cláusula excepto encopyin
,copyprivate
,schedule
,num_threads
o la cláusulaif
.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
- subprocesos dinámicos
- Variable de entorno OMP_DYNAMIC
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 calificadoconst
a menos que tenga un tipo de clase con un miembromutable
.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 directivaparallel
no pueden especificarse en una cláusulaprivate
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 directivaparallel
no pueden especificarse en una cláusulafirstprivate
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 directivaparallel
no pueden especificarse en una cláusulalastprivate
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 directivafor
oparallel 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 serconst
calificado.Las variables que son privadas dentro de una región paralela o que aparecen en la cláusula
reduction
de una directivaparallel
no pueden especificarse en una cláusulareduction
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 variablethreadprivate
.
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áusulaprivate
ofirstprivate
para la misma directivasingle
.Si se encuentra una directiva
single
con una cláusulacopyprivate
en la extensión dinámica de una región paralela, todas las variables especificadas en la cláusulacopyprivate
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
ybarrier
se enlazan dinámicamente aparallel
, si es que existe, independientemente del valor de cualquier cláusulaif
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 directivafor
que la enlaza dinámicamente.La directiva
atomic
aplica el acceso exclusivo con respecto a las directivasatomic
de todos los subprocesos, no solo al equipo actual.La directiva
critical
aplica el acceso exclusivo con respecto a las directivascritical
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 directivaparallel
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
ysingle
que se enlazan a la misma directivaparallel
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
ysingle
no se permiten en la extensión dinámica de las regionescritical
,ordered
ymaster
si las directivas se enlazan a la mismas directivaparallel
que las regiones.Las directivas
barrier
no se permiten en la extensión dinámica de las regionesfor
,ordered
,sections
,single
,master
ycritical
si las directivas se enlazan a la mismas directivaparallel
que las regiones.Las directivas
master
, no se permiten en la extensión dinámica de las regionesfor
,sections
ysingle
si las directivasmaster
se enlazan a la mismas directivaparallel
que las directivas de uso compartido.Las directivas
ordered
no se permiten en la extensión dinámica de las regionescritical
si las directivas se enlazan a la mismas directivaparallel
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.