Partilhar via


2. Diretivas

As diretivas são baseadas nas diretivas #pragma definidas nos padrões C e C++. Os compiladores que dão suporte à API de OpenMP C e C++ incluirão uma opção de linha de comando que ativa e permite a interpretação de todas as diretivas do compilador de OpenMP.

2.1 Formato de diretiva

A sintaxe de uma diretiva de OpenMP é formalmente especificada pela gramática no apêndice C e informalmente, conforme segue:

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

Cada diretiva começa com #pragma omp, para reduzir o potencial de conflito com outras diretivas de pragma (extensões não OpenMP ou de fornecedor para OpenMP) com os mesmos nomes. O restante da diretiva segue as convenções dos padrões C e C++ para diretivas de compilador. Em particular, o espaço em branco pode ser usado antes e depois do # e o espaço em branco e deve ser usado às vezes para separar as palavras em uma diretiva. Os tokens de pré-processamento seguidos de #pragma omp estão sujeitos à substituição de macro.

As diretivas diferenciam maiúsculas de minúsculas. A ordem em que as cláusulas aparecem nas diretivas não é significativa. As cláusulas em diretivas podem ser repetidas conforme necessário, sujeitas às restrições listadas na descrição de cada cláusula. Se a variable-list aparecer em uma cláusula, ela deverá especificar apenas variáveis. Apenas um directive-name pode ser especificado por diretiva. Por exemplo, a diretiva a seguir não é permitida:

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

Uma diretiva de OpenMP se aplica a no máximo uma instrução bem-sucedida, que deve ser um bloco estruturado.

2.2 Compilação condicional

O nome da macro _OPENMP é definido por implementações compatíveis com OpenMP como o yyymm de constante decimal, que será o ano e o mês da especificação aprovada. Essa macro não deve ser objeto de uma diretiva de pré-processamento #define ou #undef.

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

Se os fornecedores definirem extensões para OpenMP, eles poderão especificar macros predefinidas adicionais.

2.3 Constructo paralelo

A diretiva a seguir define uma região paralela, que é uma região do programa que deve ser executada por muitos threads em paralelo. Essa diretiva é o constructo fundamental que inicia a execução paralela.

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

A cláusula é uma das seguintes:

  • 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 )

Quando um thread chega a um constructo paralelo, uma equipe de threads é criada se um dos casos a seguir for verdadeiro:

  • Nenhuma cláusula if está presente.
  • A expressão if é avaliada como um valor diferente de zero.

Esse thread se torna o thread mestre da equipe, com um número de thread de 0, e todos os threads da equipe, incluindo o thread mestre, executam a região em paralelo. Se o valor da expressão if for zero, a região será serializada.

Para determinar o número de threads solicitados, as regras a seguir serão consideradas na ordem. A primeira regra cuja condição é atendida será aplicada:

  1. Se a cláusula num_threads estiver presente, o valor da expressão de inteiro será o número de threads solicitados.

  2. Se a função de biblioteca omp_set_num_threads tiver sido chamada, o valor do argumento na chamada executada mais recentemente será o número de threads solicitados.

  3. Se a variável de ambiente OMP_NUM_THREADS for definida, o valor dessa variável de ambiente será o número de threads solicitados.

  4. Se nenhum dos métodos acima for usado, o número de threads solicitados será definido pela implementação.

Se a cláusula num_threads estiver presente, ela substituirá o número de threads solicitados pela função de biblioteca omp_set_num_threads ou pela variável de ambiente OMP_NUM_THREADS somente para a região paralela à qual ela é aplicada. As regiões paralelas posteriores não são afetadas por ela.

O número de threads que executam a região paralela também depende se o ajuste dinâmico do número de threads está habilitado. Se o ajuste dinâmico estiver desabilitado, o número solicitado de threads executará a região paralela. Se o ajuste dinâmico estiver habilitado, o número solicitado de threads será o número máximo de threads que podem executar a região paralela.

Se uma região paralela for encontrada enquanto o ajuste dinâmico do número de threads estiver desabilitado, e o número de threads solicitados para a região paralela for maior do que o número que o sistema de tempo de execução pode fornecer, o comportamento do programa será definido pela implementação. Uma implementação pode, por exemplo, interromper a execução do programa ou serializar a região paralela.

A função de biblioteca omp_set_dynamic e a variável de ambiente OMP_DYNAMIC podem ser usadas para habilitar e desabilitar o ajuste dinâmico do número de threads.

O número de processadores físicos que realmente hospedam os threads a qualquer momento é definido pela implementação. Depois de criado, o número de threads na equipe permanece constante pela duração da região paralela. Ele pode ser alterado explicitamente pelo usuário ou automaticamente pelo sistema em tempo de execução de uma região paralela para outra.

As instruções contidas na extensão dinâmica da região paralela são executadas por cada thread, sendo que cada thread pode executar um caminho de instruções diferente dos outros threads. As diretivas encontradas fora da extensão léxica de uma região paralela são conhecidas como diretivas órfãs.

Há uma barreira implícita no final de uma região paralela. Somente o thread mestre da equipe continua a execução no final de uma região paralela.

Se um thread em uma equipe executando uma região paralela encontrar outro constructo paralelo, ele criará uma nova equipe e se tornará o mestre dessa nova equipe. Regiões paralelas aninhadas são serializadas por padrão. Como resultado, por padrão, uma região paralela aninhada é executada por uma equipe composta por um thread. O comportamento padrão pode ser alterado usando a função de biblioteca de runtime omp_set_nested ou a variável de ambiente OMP_NESTED. No entanto, o número de threads em uma equipe que executa uma região paralela aninhada é definido pela implementação.

As restrições para a diretiva parallel são as seguintes:

  • No máximo, uma cláusula if pode aparecer na diretiva.

  • Não é especificado se algum efeito colateral interno se uma expressão num_threads ocorrer.

  • Um throw executado dentro de uma região paralela deve fazer com que a execução seja retomada dentro da extensão dinâmica do mesmo bloco estruturado e deve ser capturada pelo mesmo thread que gerou a exceção.

  • Apenas uma cláusula num_threads pode aparecer na diretiva. A expressão num_threads é avaliada fora do contexto da região paralela e deve ser avaliada para um valor inteiro positivo.

  • A ordem de avaliação das cláusulas if e num_threads cláusulas não é especificada.

Referências-cruzadas

2.4 Construtos de compartilhamento de trabalho

Um constructo de compartilhamento de trabalho distribui a execução da instrução associada entre os membros da equipe que a encontram. As diretivas de compartilhamento de trabalho não inicializam novos threads e não há nenhuma barreira implícita na entrada de um constructo de compartilhamento de trabalho.

A sequência de constructos de compartilhamento de trabalho e diretivas barrier encontradas deve ser a mesma para cada thread em uma equipe.

O OpenMP define os seguintes constructos de compartilhamento de trabalho e esses constructos são descritos nas seções a seguir:

2.4.1 Constructo for

A diretiva for identifica um constructo de compartilhamento de trabalho iterativo que especifica que as iterações do loop associado serão executadas em paralelo. As iterações do loop for são distribuídas entre threads que já existem na equipe, executando o constructo paralelo ao qual ele se associa. A sintaxe do constructo for é a seguinte:

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

A cláusula é uma das seguintes:

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

A diretiva for coloca restrições na estrutura do loop for correspondente. Especificamente, o loop for correspondente deve ter forma canônica:

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

init-expr
Um dos seguintes:

  • var = lb
  • integer-type var = lb

incr-expr
Um dos seguintes:

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

var
Uma variável de inteiro com sinal. Se essa variável fosse compartilhada de outra forma, ela seria implicitamente tornada privada pela duração da for. Não modifique essa variável dentro do corpo da instrução for. A menos que a variável seja especificada como lastprivate, seu valor após o loop é indeterminado.

logical-op
Um dos seguintes:

  • <
  • <=
  • >
  • >=

lb, b e incr
Expressões de inteiros de loop invariável. Não há sincronização durante a avaliação dessas expressões, portanto, quaisquer efeitos colaterais avaliados produzem resultados indeterminados.

A forma canônica permite que o número de iterações de loop seja computado na entrada do loop. Essa computação é feita com valores no tipo de var, após promoções integrais. Em particular, se o valor de b - lb + incr não puder ser representado nesse tipo, o resultado será indeterminado. Além disso, se logical-op for < ou <=, então incr-expr deverá fazer com que var aumente em cada iteração do loop. Se logical-op for > ou >=, então incr-expr deverá fazer com que var diminua em cada iteração do loop.

A cláusula schedule especifica como as iterações do loop for são divididas entre threads da equipe. A correção de um programa não deve depender de qual thread executa uma iteração específica. O valor de chunk_size, se especificado, deve ser uma expressão de inteiro de loop invariável com um valor positivo. Não há sincronização durante a avaliação dessa expressão, portanto, quaisquer efeitos colaterais avaliados produzem resultados indeterminados. O kind de agendamento pode ter um dos seguintes valores:

Tabela 2-1: valores kind da cláusula schedule

Valor Descrição
static Quando schedule(static, chunk_size ) for especificado, as iterações serão divididas em partes de um tamanho especificado por chunk_size. As partes são atribuídas estaticamente a threads na equipe em distribuição equilibrada na ordem do número de thread. Quando nenhum chunk_size for especificado, o espaço de iteração será dividido em partes que são aproximadamente iguais em tamanho, com uma parte atribuída a cada thread.
dinâmico Quando schedule(dynamic, chunk_size ) for especificado, as iterações serão divididas em uma série de partes, cada uma contendo iterações chunk_size. Cada parte é atribuída a um thread que está aguardando uma atribuição. O thread executa a parte de iterações e aguarda a próxima atribuição, até não restar qualquer parte a ser atribuída. A última parte a ser atribuída pode ter um número menor de iterações. Quando nenhum chunk_size for especificado, será padronizado como 1.
guiado Quando schedule(guided, chunk_size ) for especificado, as iterações serão atribuídas a threads em partes com tamanhos decrescentes. Quando um thread finaliza sua parte atribuída de iterações, ele é atribuído dinamicamente a outra parte, até que mais nenhuma seja deixada. Para um chunk_size de 1, o tamanho de cada parte é aproximadamente o número de iterações não atribuídas dividido pelo número de threads. Esses tamanhos diminuem quase exponencialmente até 1. Para um chunk_size com valor k maior que 1, os tamanhos diminuem quase exponencialmente para k, exceto que a última parte pode ter menos de k iterações. Quando nenhum chunk_size for especificado, será padronizado como 1.
runtime Quando schedule(runtime) for especificado, a decisão sobre o agendamento será adiada até o runtime. O kind de agendamento e o tamanho das partes podem ser escolhidos em tempo de execução definindo a variável de ambiente OMP_SCHEDULE. Se essa variável de ambiente não estiver definida, o agendamento resultante será definido pela implementação. Quando schedule(runtime) é especificado, chunk_size não deve ser especificado.

Na ausência de uma cláusula schedule definida explicitamente, o padrão schedule será definido pela implementação.

Um programa compatível com OpenMP não deve depender de um agendamento específico para a execução correta. Um programa não deve depender de um kind de agendamento em conformidade precisamente com a descrição fornecida acima, pois é possível ter variações nas implementações do mesmo kind de agendamento em diferentes compiladores. As descrições podem ser usadas para selecionar o agendamento apropriado para uma situação específica.

A cláusula ordered deve estar presente quando as diretivas ordered se associam ao constructo for.

Há uma barreira implícita no final de um constructo for, a menos que uma cláusula nowait seja especificada.

As restrições para a diretiva for são as seguintes:

  • O loop for deve ser um bloco estruturado e, além disso, sua execução não deve ser encerrada por uma instrução break.

  • Os valores das expressões de controle de loop do loop for associado a uma diretiva for devem ser os mesmos para todos os threads na equipe.

  • A variável de iteração do loop for deve ter um tipo inteiro com sinal.

  • Apenas uma cláusula schedule pode aparecer em uma diretiva for.

  • Apenas uma cláusula ordered pode aparecer em uma diretiva for.

  • Apenas uma cláusula nowait pode aparecer em uma diretiva for.

  • Não é especificado se ou com que frequência ocorrem efeitos colaterais nas expressões chunk_size, lb, b ou incr.

  • O valor da expressão chunk_size deve ser o mesmo para todos os threads na equipe.

Referências-cruzadas

2.4.2 Constructo sections

A diretiva sections identifica um constructo de compartilhamento de trabalho não iterativo que especifica um conjunto de constructos que devem ser divididos entre threads em uma equipe. Cada seção é executada uma vez por um thread na equipe. A sintaxe da diretiva sections é a seguinte:

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

A cláusula é uma das seguintes:

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

Cada seção é precedida por uma diretiva section, embora a diretiva section seja opcional para a primeira seção. As diretivas section devem aparecer dentro da extensão lexical da diretiva sections. Há uma barreira implícita no final de um constructo sections, a menos que uma nowait seja especificada.

As restrições para a diretiva sections são as seguintes:

  • Uma diretiva section não deve aparecer fora da extensão lexical da diretiva sections.

  • Apenas uma cláusula nowait pode aparecer em uma diretiva sections.

Referências-cruzadas

  • Cláusulas private, firstprivate, lastprivate e reduction (seção 2.7.2)

2.4.3 Constructo single

A diretiva single identifica um constructo que especifica que o bloco estruturado associado é executado por apenas um thread na equipe (não necessariamente o thread mestre). A sintaxe da diretiva single é a seguinte:

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

A cláusula é uma das seguintes:

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

Há uma barreira implícita após o constructo single, a menos que uma cláusula nowait seja especificada.

As restrições para a diretiva single são as seguintes:

  • Apenas uma cláusula nowait pode aparecer em uma diretiva single.
  • A cláusula copyprivate não deve ser usada com a cláusula nowait.

Referências-cruzadas

2.5 Constructos de compartilhamento de trabalho em paralelo combinados

Os constructos de compartilhamento de trabalho paralelo combinados são atalhos para especificar uma região paralela que tem apenas um constructo de compartilhamento de trabalho. A semântica dessas diretivas é a mesma que especificar explicitamente uma diretiva parallel seguida de um único constructo de compartilhamento de trabalho.

As seções a seguir descrevem os constructos de compartilhamento de trabalho paralelo combinados:

2.5.1 Constructo parallel for

A diretiva parallel for é um atalho para uma região parallel que contém apenas uma diretiva for. A sintaxe da diretiva parallel for é a seguinte:

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

Essa diretiva permite todas as cláusulas da diretiva parallel e da diretiva for, exceto a cláusula nowait, com significados e restrições idênticos. A semântica é a mesma que especificar explicitamente uma diretiva parallel imediatamente seguida por uma diretiva for.

Referências-cruzadas

2.5.2 Constructo parallel sections

A diretiva parallel sections fornece uma forma de atalho para especificar uma região parallel que tenha apenas uma diretiva sections. A semântica é a mesma que especificar explicitamente uma diretiva parallel imediatamente seguida por uma diretiva sections. A sintaxe da diretiva parallel sections é a seguinte:

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

A clause pode ser uma das cláusulas aceitas pelas diretivas parallel e sections, exceto a cláusula nowait.

Referências-cruzadas

2.6 Diretivas master e synchronization

As seções a seguir descrevem:

2.6.1 Constructo master

A diretiva master identifica um constructo que especifica um bloco estruturado executado pelo thread mestre da equipe. A sintaxe da diretiva master é a seguinte:

#pragma omp master new-linestructured-block

Outros threads na equipe não executam o bloco estruturado associado. Não há barreira implícita na entrada ou na saída do constructo mestre.

2.6.2 Constructo critical

A diretiva critical identifica um constructo que restringe a execução do bloco estruturado associado a um único thread por vez. A sintaxe da diretiva critical é a seguinte:

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

Um name opcional pode ser usado para identificar a região crítica. Os identificadores usados para identificar uma região crítica têm vínculo externo e estão em um espaço de nome separado dos espaços de nome usados por rótulos, marcas, membros e identificadores comuns.

Um thread aguarda no início de uma região crítica até que nenhum outro thread esteja executando uma região crítica (em qualquer lugar do programa) com o mesmo nome. Todas as diretivas critical sem nome são mapeadas para o mesmo nome não especificado.

2.6.3 Diretiva barrier

A diretiva barrier sincroniza todos os threads em uma equipe. Quando encontrado, cada thread na equipe aguarda até que todos os outros tenham alcançado esse ponto. A sintaxe da diretiva barrier é a seguinte:

#pragma omp barrier new-line

Depois que todos os threads na equipe tiverem encontrado a barreira, cada thread na equipe começa a executar as instruções após a diretiva de barreira em paralelo. Como a diretiva barrier não tem uma instrução de linguagem C como parte de sua sintaxe, há algumas restrições em seu posicionamento dentro de um programa. Para obter mais informações sobre a gramática formal, consulte o apêndice C. O exemplo abaixo ilustra essas restrições.

/* 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 Constructo atomic

A diretiva atomic garante que um local de memória específico seja atualizado atomicamente, em vez de expô-lo à possibilidade de vários threads de gravação simultâneos. A sintaxe da diretiva atomic é a seguinte:

#pragma omp atomic new-lineexpression-stmt

A instrução de expressão deve ter uma das seguintes formas:

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

Nas expressões anteriores:

  • x é uma expressão lvalue com tipo escalar.

  • expr é uma expressão com tipo escalar e não faz referência ao objeto designado por x.

  • binop não é um operador sobrecarregado e é um de +, *, -, /, &, ^, |, << ou >>.

Embora seja definido pela implementação se uma implementação substitui todas as diretivas atomic por diretivas critical que têm o mesmo name exclusivo, a diretiva atomic permite uma otimização melhor. Muitas vezes, há instruções de hardware disponíveis que podem executar a atualização atômica com a menor sobrecarga.

Somente a carga e o armazenamento do objeto designado por x são atômicos; a avaliação de expr não é atômica. Para evitar condições de corrida, todas as atualizações do local em paralelo devem ser protegidas com a diretiva atomic, exceto aquelas que são conhecidas por estarem livres de condições de corrida.

As restrições para a diretiva atomic são as seguintes:

  • É necessário que todas as referências atômicas para o local de armazenamento x em todo o programa tenham um tipo compatível.

Exemplos

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 Diretiva flush

A diretiva flush, explícita ou implícita, especifica um ponto de sequência "entre threads" no qual a implementação é necessária para garantir que todos os threads em uma equipe tenham uma exibição consistente de determinados objetos (especificados abaixo) na memória. Isso significa que as avaliações anteriores de expressões que fazem referência a esses objetos estão concluídas e as avaliações subsequentes ainda não foram iniciadas. Por exemplo, os compiladores devem restaurar os valores dos objetos de registros para memória, e o hardware pode precisar liberar buffers de gravação na memória e recarregar os valores dos objetos da memória.

A sintaxe da diretiva flush é a seguinte:

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

Se os objetos que exigem sincronização puderem ser designados por variáveis, essas variáveis poderão ser especificadas na variable-list opcional. Se um ponteiro estiver presente na variable-list, o ponteiro em si será liberado, não o objeto ao qual o ponteiro se refere.

Uma flush diretiva sem uma variable-list sincroniza todos os objetos compartilhados, exceto objetos inacessíveis com duração automática de armazenamento. (É provável que isso tenha mais sobrecarga do que um flush com uma variable-list.) Uma flush diretiva sem uma variable-list está implícita para as seguintes diretivas:

  • barrier
  • Na entrada e saída de critical
  • Na entrada e saída de ordered
  • Na entrada e saída de parallel
  • Na saída de for
  • Na saída de sections
  • Na saída de single
  • Na entrada e saída de parallel for
  • Na entrada e saída de parallel sections

A diretiva não estará implícita se uma cláusula nowait estiver presente. Deve-se observar que a diretiva flush não está implícita para nenhuma das seguintes diretivas:

  • Na entrada para for
  • Na entrada ou saída de master
  • Na entrada para sections
  • Na entrada para single

Uma referência que acessa o valor de um objeto com um tipo qualificado como volátil se comporta como se houvesse uma diretiva flush especificando esse objeto no ponto de sequência anterior. Uma referência que modifica o valor de um objeto com um tipo qualificado como volátil se comporta como se houvesse uma diretiva flush especificando esse objeto no ponto de sequência subsequente.

Como a diretiva flush não tem uma instrução de linguagem C como parte de sua sintaxe, há algumas restrições em seu posicionamento dentro de um programa. Para obter mais informações sobre a gramática formal, consulte o apêndice C. O exemplo abaixo ilustra essas restrições.

/* 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)
}

As restrições para a diretiva flush são as seguintes:

  • Uma variável especificada em uma diretiva flush não deve ter um tipo de referência.

2.6.6 Constructo ordered

O bloco estruturado após uma diretiva ordered é executado na ordem em que as iterações seriam executadas em um loop sequencial. A sintaxe da diretiva ordered é a seguinte:

#pragma omp ordered new-linestructured-block

Uma diretiva ordered deve estar dentro da extensão dinâmica de um constructo for ou parallel for. A diretiva for ou parallel for à qual o constructo ordered se associa deve ter uma cláusula ordered especificada conforme descrito na seção 2.4.1. Na execução de um constructo for ou parallel for com uma cláusula ordered, os constructos ordered são executados estritamente na ordem em que seriam em uma execução sequencial do loop.

As restrições para a diretiva ordered são as seguintes:

  • Uma iteração de um loop com um constructo for não deve executar a mesma diretiva ordenada mais de uma vez e não deve executar mais de uma diretiva ordered.

2.7 Ambiente de dados

Esta seção apresenta uma diretiva e várias cláusulas para controlar o ambiente de dados durante a execução de regiões paralelas, da seguinte maneira:

  • Uma diretiva threadprivate é fornecida para tornar as variáveis de escopo de arquivo, namespace ou escopo de bloco estático locais para um thread.

  • As cláusulas que podem ser especificadas nas diretivas para controlar os atributos de compartilhamento de variáveis pela duração dos constructos paralelos ou de compartilhamento de trabalho são descritas na seção 2.7.2.

2.7.1 Diretiva threadprivate

A diretiva threadprivate torna as variáveis de escopo de arquivo, namespace ou escopo de bloco estático nomeadas especificadas na variable-list privadas para um thread. variable-list é uma lista separada por vírgulas de variáveis que não têm um tipo incompleto. A sintaxe da diretiva threadprivate é a seguinte:

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

Cada cópia de uma variável threadprivate é inicializada uma vez em um ponto não especificado no programa, antes da primeira referência a essa cópia e da maneira usual (ou seja, como a cópia mestra seria inicializada em uma execução serial do programa). Observe que se um objeto for referenciado em um inicializador explícito de uma variável threadprivate e o valor do objeto for modificado antes da primeira referência a uma cópia da variável, o comportamento será não especificado.

Assim como acontece com qualquer variável privada, um thread não deve referenciar a cópia de um objeto threadprivate de outro thread. Durante as regiões seriais e mestras do programa, as referências serão para a cópia do objeto do thread mestre.

Depois que a primeira região paralela for executada, os dados nos objetos threadprivate deverão persistir somente se o mecanismo de threads dinâmicos tiver sido desabilitado e se o número de threads permanecer inalterado para todas as regiões paralelas.

As restrições para a diretiva threadprivate são as seguintes:

  • Uma diretiva threadprivate para variáveis de escopo de arquivo ou de escopo de namespace deve aparecer fora de qualquer definição ou declaração e deve preceder lexicalmente todas as referências a qualquer uma das variáveis em sua lista.

  • Cada variável na variable-list de uma diretiva threadprivate no escopo de arquivo ou namespace deve se referir a uma declaração de variável no escopo de arquivo ou de namespace que precede lexicalmente a diretiva.

  • Uma diretiva threadprivate para variáveis de escopo de bloco estático deve aparecer no escopo da variável e não em um escopo aninhado. A diretiva deve preceder lexicalmente todas as referências a qualquer uma das variáveis em sua lista.

  • Cada variável na variable-list de uma diretiva threadprivate no escopo de bloco deve se referir a uma declaração de variável no mesmo escopo que precede lexicalmente a diretiva. A declaração de variável deve usar o especificador de classe de armazenamento estático.

  • Se uma variável for especificada em uma diretiva threadprivate em uma unidade de tradução, ela deverá ser especificada em uma diretiva threadprivate em cada unidade de tradução na qual é declarada.

  • Uma variável threadprivate não deve aparecer em nenhuma cláusula, exceto a cláusula copyin, copyprivate, schedule, num_threads ou if.

  • O endereço de uma variável threadprivate não é uma constante de endereço.

  • Uma variável threadprivate não deve ter um tipo incompleto ou um tipo de referência.

  • Uma variável threadprivate com tipo de classe não POD deve ter um construtor de cópia acessível e inequívoco se for declarada com um inicializador explícito.

O exemplo a seguir ilustra como modificar uma variável que aparece em um inicializador pode causar um comportamento não especificado e também como evitar esse problema usando um objeto auxiliar e um construtor de cópia.

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. */
   }
}

Referências-cruzadas

2.7.2 Cláusulas de atributo de compartilhamento de dados

Várias diretivas aceitam cláusulas que permitem a um usuário controlar os atributos de compartilhamento de variáveis pela duração da região. As cláusulas de atributo de compartilhamento se aplicam apenas a variáveis na extensão lexical da diretiva na qual a cláusula aparece. Nem todas as cláusulas a seguir são permitidas em todas as diretivas. A lista de cláusulas válidas em uma diretiva específica é descrita com a diretiva.

Se uma variável estiver visível quando um constructo paralelo ou de compartilhamento de trabalho for encontrado e a variável não for especificada em uma cláusula de atributo de compartilhamento ou diretiva threadprivate, a variável será compartilhada. As variáveis estáticas declaradas dentro da extensão dinâmica de uma região paralela são compartilhadas. A memória alocada por heap (por exemplo, usando malloc() em C ou C++ ou o operador new em C++) é compartilhada. (O ponteiro para essa memória, no entanto, pode ser privado ou compartilhado.) As variáveis com duração de armazenamento automática declaradas dentro da extensão dinâmica de uma região paralela são privadas.

A maioria das cláusulas aceita um argumento variable-list, que é uma lista separada por vírgulas de variáveis que são visíveis. Se uma variável referenciada em uma cláusula de atributo de compartilhamento de dados tiver um tipo derivado de um modelo e não houver outras referências a essa variável no programa, o comportamento será indefinido.

Todas as variáveis que aparecem dentro de cláusulas de diretiva devem estar visíveis. As cláusulas podem ser repetidas conforme necessário, mas nenhuma variável pode ser especificada em mais de uma cláusula, exceto que uma variável possa ser especificada em uma cláusula firstprivate e uma lastprivate.

As seções a seguir descrevem as cláusulas de atributo de compartilhamento de dados:

2.7.2.1 private

A cláusula private declara as variáveis na lista de variáveis como privadas para cada thread em uma equipe. A sintaxe da cláusula private é a seguinte:

private(variable-list)

O comportamento de uma variável especificada em uma cláusula privateé o seguinte. Um novo objeto com duração de armazenamento automática é alocado para o constructo. O tamanho e o alinhamento do novo objeto são determinados pelo tipo da variável. Essa alocação ocorre uma vez para cada thread na equipe e um construtor padrão é invocado para um objeto de classe, se necessário; caso contrário, o valor inicial será indeterminado. O objeto original referenciado pela variável tem um valor indeterminado após a entrada no constructo, não deve ser modificado dentro da extensão dinâmica do constructo e ter um valor indeterminado ao sair do constructo.

Na extensão lexical do constructo da diretiva, a variável referencia o novo objeto privado alocado pelo thread.

As restrições para a cláusula private são as seguintes:

  • Uma variável com um tipo de classe que é especificada em uma cláusula private deve ter um construtor padrão acessível e inequívoco.

  • Uma variável especificada em uma cláusula private não deve ter um tipo qualificado como const, a menos que tenha um tipo de classe com um membro mutable.

  • Uma variável especificada em uma cláusula private não deve ter um tipo incompleto ou um tipo de referência.

  • As variáveis que aparecem na cláusula reduction de uma diretiva parallel não podem ser especificadas em uma cláusula private em uma diretiva de compartilhamento de trabalho que se associa ao constructo paralelo.

2.7.2.2 firstprivate

A cláusula firstprivate fornece um superconjunto da funcionalidade fornecida pela cláusula private. A sintaxe da cláusula firstprivate é a seguinte:

firstprivate(variable-list)

As variáveis especificadas na variable-list têm semântica de cláusula private, conforme descrito na seção 2.7.2.1. A inicialização ou construção ocorre como se tivesse sido feita uma vez por thread, antes da execução do thread do constructo. Para uma cláusula firstprivate em um constructo paralelo, o valor inicial do novo objeto privado é o valor do objeto original que existe imediatamente antes do constructo paralelo para o thread que o encontra. Para uma cláusula firstprivate em um constructo de compartilhamento de trabalho, o valor inicial do novo objeto privado para cada thread que executa o constructo de compartilhamento de trabalho é o valor do objeto original que existe antes do ponto no tempo em que o mesmo thread encontra o constructo de compartilhamento de trabalho. Além disso, para objetos C++, o novo objeto privado para cada thread é construído por cópia com base no objeto original.

As restrições para a cláusula firstprivate são as seguintes:

  • Uma variável especificada em uma cláusula firstprivate não deve ter um tipo incompleto ou um tipo de referência.

  • Uma variável com um tipo de classe que é especificada em firstprivate deve ter um construtor de cópia acessível e inequívoco.

  • As variáveis que são privadas dentro de uma região paralela ou aparecem na cláusula reduction de uma diretiva parallel não podem ser especificadas em uma cláusula firstprivate em uma diretiva de compartilhamento de trabalho que se associa ao constructo paralelo.

2.7.2.3 lastprivate

A cláusula lastprivate fornece um superconjunto da funcionalidade fornecida pela cláusula private. A sintaxe da cláusula lastprivate é a seguinte:

lastprivate(variable-list)

Variáveis especificadas na variable-list têm semântica de cláusula private. Quando uma cláusula lastprivate aparece na diretiva que identifica um constructo de compartilhamento de trabalho, o valor de cada variável lastprivate da última iteração sequencial do loop associado ou da diretiva da última seção lexicalmente é atribuído ao objeto original da variável. Variáveis que não são atribuídas a um valor pela última iteração de for ou parallel for, ou pela última seção lexical da diretiva sections ou parallel sections, têm valores indeterminados após o constructo. Subobjetos não atribuídos também têm um valor indeterminado após o constructo.

As restrições para a cláusula lastprivate são as seguintes:

  • Todas as restrições para private se aplicam.

  • Uma variável com um tipo de classe que é especificada em uma cláusula lastprivate deve ter um operador de atribuição de cópia acessível e inequívoco.

  • As variáveis que são privadas dentro de uma região paralela ou aparecem na cláusula reduction de uma diretiva parallel não podem ser especificadas em uma cláusula lastprivate em uma diretiva de compartilhamento de trabalho que se associa ao constructo paralelo.

2.7.2.4 shared

Essa cláusula compartilha variáveis que aparecem na variable-list entre todos os threads de uma equipe. Todos os threads dentro de uma equipe acessam a mesma área de armazenamento para variáveis shared.

A sintaxe da cláusula shared é a seguinte:

shared(variable-list)

2.7.2.5 default

A cláusula default permite que o usuário afete os atributos de compartilhamento de dados de variáveis. A sintaxe da cláusula default é a seguinte:

default(shared | none)

Especificar default(shared) é equivalente a listar explicitamente cada variável atualmente visível em uma cláusula shared, a menos que ela seja qualificada como threadprivate ou const. Na ausência de uma cláusula explícita default, o comportamento padrão será o mesmo que se default(shared) tivesse sido especificado.

Especificar default(none) exige que pelo menos uma das seguintes condições deve ser verdadeira para cada referência a uma variável na extensão lexical do constructo paralelo:

  • A variável é explicitamente listada em uma cláusula de atributo de compartilhamento de dados de um constructo que contém a referência.

  • A variável é declarada dentro do constructo paralelo.

  • A variável é threadprivate.

  • A variável tem um tipo qualificado como const.

  • A variável é a variável de controle de loop para um loop for que segue imediatamente uma diretiva for ou parallel for e a referência de variável aparece dentro do loop.

Especificar uma variável em um cláusula firstprivate, lastprivate ou reduction de uma diretiva fechada causa uma referência implícita à variável no contexto delimitado. Essas referências implícitas também estão sujeitas aos requisitos listados acima.

Apenas uma única cláusula default pode ser especificada em uma diretiva parallel.

O atributo de compartilhamento de dados padrão de uma variável pode ser substituído usando as cláusulas private, firstprivate, lastprivate, reduction e shared como demonstrado pelo exemplo a seguir:

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

2.7.2.6 reduction

Essa cláusula executa uma redução nas variáveis escalares que aparecem na variable-list, com o operador op. A sintaxe da cláusula reduction é a seguinte:

reduction( op : variable-list )

Normalmente, uma redução é especificada para uma instrução com uma das seguintes formas:

  • x = x op expr
  • x binop = expr
  • x = expr op x (exceto subtração)
  • x ++
  • ++ x
  • x --
  • -- x

em que:

x
Uma das variáveis de redução especificadas na lista.

variable-list
Uma lista separada por vírgulas de variáveis de redução escalar.

expr
Uma expressão com tipo escalar que não referencia x.

op
Não é um operador sobrecarregado, mas um de +, *, -, &, ^, |, && ou ||.

binop
Não é um operador sobrecarregado, mas um de +, *, -, &, ^ ou |.

A seguir está um exemplo da 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];
}

Conforme mostrado no exemplo, um operador pode estar oculto dentro de uma chamada de função. O usuário deve ter cuidado para que o operador especificado na cláusula reduction corresponda à operação de redução.

Embora o operando direito do operador || não tenha efeitos colaterais neste exemplo, eles são permitidos, mas devem ser usados com cuidado. Nesse contexto, um efeito colateral que com certeza não ocorreria durante a execução sequencial do loop pode ocorrer durante a execução paralela. Essa diferença pode ocorrer porque a ordem de execução das iterações é indeterminado.

O operador é usado para determinar o valor inicial de quaisquer variáveis privadas usadas pelo compilador para a redução e para determinar o operador de finalização. Especificar o operador explicitamente permite que a instrução de redução fique fora da extensão lexical da construção. Qualquer número de cláusulas reduction pode ser especificado na diretiva, mas uma variável pode aparecer em no máximo uma cláusula reduction para essa diretiva.

Uma cópia privada de cada variável na variable-list é criada, uma para cada thread, como se a cláusula private tivesse sido usada. A cópia privada é inicializada de acordo com o operador (consulte a tabela a seguir).

No final da região para a qual a cláusula reduction foi especificada, o objeto original é atualizado para refletir o resultado da combinação de seu valor original com o valor final de cada uma das cópias privadas usando o operador especificado. Os operadores de redução são todos associativos (exceto para subtração) e o compilador pode reassociar livremente a computação do valor final. (Os resultados parciais de uma redução de subtração são adicionados para formar o valor final.)

O valor do objeto original torna-se indeterminado quando o primeiro thread alcança a cláusula de contenção e permanece assim até que a computação de redução seja concluída. Normalmente, a computação será concluída no fim da construção; no entanto, se a cláusula reduction for usada em um constructo ao qual nowait também é aplicada, o valor do objeto original permanecerá indeterminado até que uma sincronização de barreira tenha sido executada para garantir que todos os threads tenham concluído a cláusula reduction.

A tabela a seguir lista os operadores válidos e seus valores de inicialização canônica. O valor de inicialização real será consistente com o tipo de dados da variável de redução.

Operador Inicialização
+ 0
* 1
- 0
& ~0
| 0
^ 0
&& 1
|| 0

As restrições para a cláusula reduction são as seguintes:

  • O tipo das variáveis na cláusula reduction deve ser válido para o operador de redução, exceto que tipos de ponteiro e tipos de referência nunca são permitidos.

  • Uma variável especificada na cláusula reduction não deve ser qualificada como const.

  • As variáveis que são privadas dentro de uma região paralela ou aparecem na cláusula reduction de uma diretiva parallel não podem ser especificadas em uma cláusula reduction em uma diretiva de compartilhamento de trabalho que se associa ao constructo paralelo.

    #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

A cláusula copyin fornece um mecanismo para atribuir o mesmo valor a variáveis threadprivate para cada thread na equipe que executa a região paralela. Para cada variável especificada em uma cláusula copyin, o valor da variável no thread mestre da equipe é copiado, como se por atribuição, para as cópias de thread privado no início da região paralela. A sintaxe da cláusula copyin é a seguinte:

copyin(
variable-list
)

As restrições para a cláusula copyin são as seguintes:

  • Uma variável especificada na cláusula copyin deve ter um operador de atribuição de cópia acessível e inequívoco.

  • Uma variável especificada na cláusula copyin deve ser uma variável threadprivate.

2.7.2.8 copyprivate

A cláusula copyprivate fornece um mecanismo para usar uma variável privada para transmitir um valor de um membro de uma equipe para os outros membros. É uma alternativa usar uma variável compartilhada para o valor quando fornecer essa variável compartilhada seria difícil (por exemplo, em uma recursão que exige uma variável diferente em cada nível). A cláusula copyprivate só pode aparecer na diretiva single.

A sintaxe da cláusula copyprivate é a seguinte:

copyprivate(
variable-list
)

O efeito da cláusula copyprivate sobre as variáveis em sua lista de variáveis ocorre após a execução do bloco estruturado associado ao constructo single e antes que qualquer um dos threads na equipe tenha deixado a barreira no final da construção. Em seguida, em todos os outros threads na equipe, para cada variável na variable-list, essa variável se torna definida (como se por atribuição) com o valor da variável correspondente no thread que executou o bloco estruturado do constructo.

As restrições para a cláusula copyprivate são as seguintes:

  • Uma variável especificada na cláusula copyprivate não deve aparecer em uma cláusula private ou firstprivate para a mesma diretiva single.

  • Se uma diretiva single com uma cláusula copyprivate for encontrada na extensão dinâmica de uma região paralela, todas as variáveis especificadas na cláusula copyprivate deverão ser privadas no contexto delimitador.

  • Uma variável especificada na cláusula copyprivate deve ter um operador de atribuição de cópia acessível e inequívoco.

2.8 Associação de diretiva

A associação dinâmica de diretivas deve obedecer às seguintes regras:

  • As diretivas for, sections, single, master e barrier associam-se à parallel delimitada dinamicamente, se existir, independentemente do valor de qualquer cláusula if que possa estar presente nessa diretiva. Se nenhuma região paralela estiver sendo executada no momento, as diretivas serão executadas por uma equipe composta apenas pelo thread mestre.

  • A diretiva ordered associa-se à for delimitada dinamicamente.

  • A diretiva atomic impõe acesso exclusivo em relação às diretivas atomic em todos os threads, não apenas na equipe atual.

  • A diretiva critical impõe acesso exclusivo em relação às diretivas critical em todos os threads, não apenas na equipe atual.

  • Uma diretiva nunca pode se associar a nenhuma diretiva fora da parallel delimitada dinamicamente mais próxima.

2.9 Aninhamento de diretiva

O aninhamento dinâmico de diretivas deve obedecer às seguintes regras:

  • Uma diretiva parallel dinamicamente dentro de outra parallel estabelece logicamente uma nova equipe, que é composta apenas pelo thread atual, a menos que o paralelismo aninhado esteja habilitado.

  • As diretivas for, sections e single associadas à mesma parallel não têm permissão para serem aninhadas um dentro da outra.

  • As diretivas critical com o mesmo nome não têm permissão para serem aninhadas um dentro da outra. Observe que essa restrição não é suficiente para evitar deadlock.

  • As diretivas for, sections e single não são permitidas na extensão dinâmica das regiões critical, ordered e master se as diretivas se associam às mesmas parallel que as regiões.

  • As diretivas barrier não são permitidas na extensão dinâmica das regiões for, ordered, sections, single, master e critical se as diretivas se associam às mesmas parallel que as regiões.

  • As diretivas master não são permitidas na extensão dinâmica das regiões for, sections e single se as diretivas master se associam às mesmas parallel que as diretivas de compartilhamento de trabalho.

  • As diretivas ordered não são permitidas na extensão dinâmica das regiões critical se as diretivas se associam às mesmas parallel que as regiões.

  • Qualquer diretiva permitida quando executada dinamicamente dentro de uma região paralela também é permitida quando executada fora de uma região paralela. Quando executada dinamicamente fora de uma região paralela especificada pelo usuário, a diretiva é executada por uma equipe composta apenas pelo thread mestre.