Partilhar via


2. Diretivas

As diretivas baseiam-se em #pragma diretivas definidas nas normas C e C++. Os compiladores que suportam a API 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 OpenMP.

2.1 Formato da diretiva

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

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

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

As diretivas são sensíveis a maiúsculas e minúsculas. A ordem pela qual as cláusulas aparecem nas diretivas não é significativa. As cláusulas relativas às diretivas podem ser repetidas sempre que necessário, sem prejuízo das restrições enumeradas na descrição de cada cláusula. Se a lista de variáveis aparecer em uma cláusula, ela deverá especificar apenas variáveis. Só pode ser especificado um nome de diretiva por diretiva. Por exemplo, a seguinte diretiva não é permitida:

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

Uma diretiva OpenMP aplica-se a, no máximo, uma declaração subsequente, que deve ser um bloco estruturado.

2.2 Compilação condicional

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

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

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

2.3 Construção paralela

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

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

A cláusula é uma das seguintes:

  • if( expressão escalar)
  • private( lista de variáveis)
  • firstprivate( lista de variáveis)
  • default(shared | none)
  • shared( lista de variáveis)
  • copyin( lista de variáveis)
  • reduction( Operador:lista de variáveis)
  • num_threads( Expressão inteira)

Quando um thread chega a uma construção paralela, uma equipe de threads é criada se um dos seguintes casos for verdadeiro:

  • Nenhuma if cláusula está presente.
  • A expressão if avalia-se como um valor não 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 a if expressão for igual a zero, a região será serializada.

Para determinar o número de threads solicitados, as regras a seguir serão consideradas em ordem. Aplicar-se-á a primeira regra cuja condição esteja preenchida:

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

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

  3. Se a variável OMP_NUM_THREADS de ambiente estiver 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 num_threads cláusula estiver presente, ela substituirá o número de threads solicitados pela omp_set_num_threads função library ou pela OMP_NUM_THREADS variável de ambiente apenas para a região paralela à qual ela é aplicada. Regiões paralelas posteriores não são afetadas por ele.

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 desativado, 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 desativado 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 omp_set_dynamic função de biblioteca e a OMP_DYNAMIC variável de ambiente 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 em um determinado momento é definido pela implementação. Uma vez criado, o número de threads na equipe permanece constante durante a duração dessa região paralela. Ele pode ser alterado explicitamente pelo usuário ou automaticamente pelo sistema de 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, e cada thread pode executar um caminho de instruções diferente dos outros threads. As diretivas encontradas fora da extensão lexical de uma região paralela são designadas 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 encontra outra construção paralela, ele cria uma nova equipe e se torna o mestre dessa nova equipe. As 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 omp_set_nested de biblioteca de tempo de execução ou a variável OMP_NESTEDde ambiente . No entanto, o número de threads numa equipa que executa uma região paralela aninhada é definido pela forma como é implementado.

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

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

  • Não é especificado se ocorrem quaisquer efeitos secundários dentro da expressão if ou num_threads expressão.

  • 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 capturado pelo mesmo thread que lançou a exceção.

  • A diretiva só pode incluir uma única num_threads cláusula. A num_threads expressão é avaliada fora do contexto da região paralela e deve ser avaliada com um valor inteiro positivo.

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

Referências cruzadas

2.4 Construções de partilha do trabalho

Um estrutura de partilha de trabalho distribui a execução da declaração associada entre os membros da equipa que a encontram. As diretivas de compartilhamento de trabalho não lançam novos threads e não há nenhuma barreira implícita na entrada de uma construção de compartilhamento de trabalho.

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

OpenMP define as seguintes construções de compartilhamento de trabalho, e essas construções são descritas nas seções a seguir:

2.4.1 Para a construção

A for diretiva identifica uma construção iterativa de compartilhamento de trabalho que especifica que as iterações do loop associado serão executadas em paralelo. As iterações do loop são distribuídas for entre threads que já existem na equipe que executa a construção paralela à qual ele se liga. A sintaxe da construção for é a seguinte:

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

A cláusula é uma das seguintes:

  • private( lista de variáveis)
  • firstprivate( lista de variáveis)
  • lastprivate( lista de variáveis)
  • reduction( Operador:lista de variáveis)
  • ordered
  • schedule( tipo [,chunk_size] )
  • nowait

A diretiva for impõe restrições à estrutura do ciclo correspondente ao for. Especificamente, o loop correspondente for deve ter forma canônica:

for ( init-expr;var lógico-op b;Incr-EXPR)

init-expr
Um dos seguintes:

  • = lb
  • var do tipo inteiro = lb

Incr-EXPR
Um dos seguintes:

  • ++ VAR
  • VAR++
  • -- VAR
  • VAR--
  • += INCR
  • -= INCR
  • = + INCR
  • = + VAR
  • = - INCR

VAR
Uma variável inteira assinada. Se essa variável, de outra forma, seria compartilhada, é implicitamente tornada privada durante a duração do for. Não modifique esta variável no corpo da for instrução. A menos que a variável seja especificada lastprivate, seu valor após o loop é indeterminado.

operador lógico
Um dos seguintes:

  • <
  • <=
  • >
  • >=

lb, b e incr
Loop de expressões inteiras invariantes. Não há sincronização durante a avaliação dessas expressões, então quaisquer efeitos colaterais avaliados produzem resultados indeterminados.

A forma canônica permite que o número de iterações de loop seja calculado na entrada do loop. Este cálculo é feito 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 é indeterminado. Além disso, se logical-op é < ou <=, então incr-expr deve fazer com que var aumente em cada iteração do loop. Se logical-op é > ou >=, então incr-expr deve fazer com que var diminua em cada iteração do loop.

A schedule cláusula especifica como as for iterações do loop são divididas entre os 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 inteira invariante de loop com um valor positivo. Não há sincronização durante a avaliação desta expressão, pelo que quaisquer efeitos secundários avaliados produzem resultados indeterminados. O tipo de agenda pode ser um dos seguintes valores:

Tabela 2-1: schedule Valores do tipo de cláusula

Valor Descrição
estático Quando schedule(static,chunk_size) é especificado, as iterações são divididas em partes de um tamanho especificado por chunk_size. Os blocos são atribuídos estaticamente aos threads na equipa de forma circular, na ordem dos números dos threads. Quando nenhum chunk_size é especificado, o espaço de iteração é dividido em partes que são aproximadamente iguais em tamanho, com um bloco atribuído a cada thread.
dinâmico Quando schedule(dynamic,chunk_size) é especificado, as iterações são divididas em uma série de partes, cada uma contendo chunk_size iterações. Cada bloco é atribuído a um thread que está aguardando uma atribuição. A thread executa o bloco de iterações e, em seguida, aguarda a sua próxima atribuição, até que não restem blocos para serem atribuídos. A última parte a ser atribuída pode ter um número menor de iterações. Quando nenhum chunk_size é especificado, o padrão é 1.
guiado Quando schedule(guided,tamanho_do_chunk) é especificado, as iterações são atribuídas a threads em blocos com tamanhos decrescentes. Quando uma tarefa termina o seu bloco atribuído de iterações, é-lhe atribuído dinamicamente outro bloco, até que não reste nenhum. Para uma chunk_size de 1, o tamanho de cada bloco é aproximadamente o número de iterações não atribuídas dividido pelo número de threads. Estes tamanhos diminuem quase exponencialmente para 1. Para um chunk_size com valor k maior que 1, os tamanhos diminuem quase exponencialmente para k, exceto que o último pedaço pode ter menos de k iterações. Quando nenhum chunk_size é especificado, o padrão é 1.
runtime Quando schedule(runtime) é especificado, a decisão sobre o agendamento é adiada até o tempo de execução. O tipo de agendamento e o tamanho dos blocos podem ser escolhidos em tempo de execução, definindo a variável OMP_SCHEDULEde ambiente . Se essa variável de ambiente não estiver definida, o cronograma resultante será definido pela implementação. Quando schedule(runtime) é especificado, chunk_size não deve ser especificado.

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

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

A cláusula ordered deve estar presente quando as diretivas ordered se vinculam à construção for.

Há uma barreira implícita no final de uma for construção, a menos que uma nowait cláusula seja especificada.

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

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

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

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

  • Apenas uma única cláusula schedule pode aparecer numa diretiva for.

  • Apenas uma única cláusula ordered pode aparecer numa diretiva for.

  • Apenas uma única cláusula nowait pode aparecer numa diretiva for.

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

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

Referências cruzadas

2.4.2 Construção de secções

A diretiva sections identifica uma construção de partilha de tarefa não iterativa que especifica um conjunto de blocos de código que devem ser divididos entre as threads numa equipa. 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( lista de variáveis)
  • firstprivate( lista de variáveis)
  • lastprivate( lista de variáveis)
  • reduction( Operador:lista de variáveis)
  • nowait

Cada secção é precedida de uma section diretiva, embora a section diretiva seja facultativa para a primeira secção. As section diretivas devem figurar dentro do âmbito lexical da sections diretiva. Há uma barreira implícita no final de uma sections construção, a menos que um nowait seja especificado.

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

  • Uma section diretiva não deve figurar fora do âmbito lexical da sections diretiva.

  • Apenas uma única cláusula nowait pode aparecer numa diretiva sections.

Referências cruzadas

  • private, firstprivate, lastprivate, e reduction cláusulas (ponto 2.7.2)

2.4.3 construção única

A single diretiva identifica uma construção 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( lista de variáveis)
  • firstprivate( lista de variáveis)
  • copyprivate( lista de variáveis)
  • nowait

Há uma barreira implícita após o single a não ser que uma cláusula nowait seja especificada.

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

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

Referências cruzadas

2.5 Construções paralelas combinadas de partilha do trabalho

Construções de compartilhamento de trabalho paralelas combinadas são atalhos para especificar uma região paralela que tem apenas uma construção de compartilhamento de trabalho. A semântica destas diretivas é a mesma que especificar uma diretiva parallel explicitamente seguida de um único construto de partilha de trabalho.

As seções a seguir descrevem as construções combinadas de compartilhamento de trabalho paralelo:

2.5.1 Paralelo para construção

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

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

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

Referências cruzadas

2.5.2 Construção de secções paralelas

A diretiva parallel sections fornece uma maneira de atalho para especificar uma região parallel que contém apenas uma diretiva sections. A semântica é a mesma que especificar explicitamente uma parallel diretiva imediatamente seguida de uma sections diretiva. 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 cláusula pode ser uma das cláusulas aceites pelas diretivas parallel e sections, exceto a cláusula nowait.

Referências cruzadas

2.6 Diretivas mestre e de sincronização

As secções seguintes descrevem:

2.6.1 Construção mestre

A master diretiva identifica uma construção que especifica um bloco estruturado que é 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á nenhuma barreira implícita nem à entrada nem à saída da estrutura principal.

2.6.2 Construção crítica

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

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

Um nome opcional pode ser usado para identificar a região crítica. Os identificadores usados para identificar uma região crítica têm ligação externa e estão em um espaço de nome separado dos espaços de nome usados por rótulos, tags, 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 sem nome critical são associadas ao mesmo nome não especificado.

2.6.3 Diretiva relativa às barreiras

A barrier diretiva sincroniza todos os threads em uma equipe. Quando encontrado, cada thread da equipe espera até que todos os outros tenham chegado a este ponto. A sintaxe da diretiva barrier é a seguinte:

#pragma omp barrier new-line

Depois que todos os threads da equipe encontraram a barreira, cada thread da equipe começa a executar as declarações após a diretiva de barreira em paralelo. Como a barrier diretiva não tem uma instrução de linguagem C como parte de sua sintaxe, há algumas restrições sobre 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 Construção atómica

A diretiva atomic garante que um local de memória específico seja atualizado de forma atômica, em vez de expô-lo à possibilidade de múltiplos, simultâneos filamentos de escrita. 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 dos +, *, -, /, &, ^, |, <<, ou >>.

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

Apenas a carga e o armazenamento do objeto designado por x são atômicos; A avaliação da EXPR não é atómica. Para evitar condições de corrida, todas as atualizações da localização em paralelo devem ser protegidas com a diretiva atomic, exceto aquelas que são conhecidas por não apresentarem condições de corrida.

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

  • Todas as referências atômicas ao local de armazenamento x em todo o programa são necessárias para ter 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 de limpeza

A diretiva flush, seja explícita ou implícita, especifica um ponto de sequência "cross-thread" no qual a implementação deve garantir que todos os threads de uma equipa tenham uma visão consistente de certos 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 completas e as avaliações subsequentes ainda não começaram. Por exemplo, os compiladores devem restaurar os valores dos objetos dos registos para a memória, e o hardware pode precisar esvaziar buffers de escrita para a 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 todos os objetos que exigem sincronização puderem ser designados por variáveis, essas variáveis poderão ser especificadas na lista de variáveis opcionais. Se um ponteiro estiver presente na lista de variáveis, o ponteiro em si será liberado, não o objeto ao qual o ponteiro se refere.

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

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

A diretiva não está implícita se uma nowait cláusula estiver presente. Note-se que a flush diretiva não está implícita para nenhum dos seguintes:

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

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

Como a flush diretiva não tem uma instrução de linguagem C como parte de sua sintaxe, há algumas restrições sobre 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 à diretiva flush são as seguintes:

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

2.6.6 Construção ordenada

O bloco estruturado que segue uma ordered diretiva é 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 ordered diretiva deve estar dentro da extensão dinâmica de uma for ou parallel for construção. A diretiva for ou parallel for à qual o ordered construto se liga deve ter uma cláusula ordered especificada conforme descrito no ponto 2.4.1. Na execução de um for ou parallel for construto com uma cláusula ordered, os construtos ordered são executados estritamente pela ordem em que seriam executados numa execução sequencial do loop.

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

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

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 forma:

  • Uma diretiva threadprivate é fornecida para tornar as variáveis file-scope, namespace-scope ou static block-scope locais para um thread.

  • As cláusulas que podem ser especificadas nas diretivas para controlar os atributos de partilha de variáveis durante a duração das construções paralelas ou de partilha de trabalho são descritas na secção 2.7.2.

2.7.1 Diretiva threadprivate

A threadprivate diretiva torna as variáveis nomeadas file-scope, namespace-scope ou static block-scope 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 threadprivate variável é 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 mestre seria inicializada em uma execução serial do programa). Observe que se um objeto é referenciado em um inicializador explícito de uma threadprivate variável e o valor do objeto é modificado antes da primeira referência a uma cópia da variável, o comportamento não é especificado.

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

Após a execução da primeira região paralela, é garantido que os threadprivate dados nos objetos persistirão somente se o mecanismo de threads dinâmicos tiver sido desativado e se o número de threads permanecer inalterado para todas as regiões paralelas.

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

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

  • Cada variável na lista de variáveis de uma threadprivate diretiva no escopo de arquivo ou namespace deve se referir a uma declaração de variável no escopo de arquivo ou namespace que precede lexicamente a diretiva.

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

  • Cada variável na lista de variáveis de uma diretiva em âmbito de bloco deve referir-se a uma declaração de variável no mesmo âmbito que precede lexicamente a diretiva. A declaração de variável deve usar o especificador estático de classe de armazenamento.

  • Se uma variável é especificada em uma threadprivate diretiva em uma unidade de tradução, ela deve ser especificada em uma threadprivate diretiva em cada unidade de tradução em que é declarada.

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

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

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

  • Uma threadprivate variável com tipo de classe não-POD deve ter um construtor de cópia acessível e inequívoca 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 atributos de partilha de dados

Várias diretivas aceitam cláusulas que permitem que um usuário controle os atributos de compartilhamento de variáveis durante a duração da região. As cláusulas de partilha aplicam-se apenas a variáveis no âmbito lexical da diretiva em que aparecem. Nem todas as cláusulas seguintes são permitidas em todas as diretivas. A lista de cláusulas válidas numa determinada diretiva é descrita com a diretiva.

Se uma variável é visível quando uma construção paralela ou de compartilhamento de trabalho é encontrada, e a variável não é especificada em uma cláusula de atributo de compartilhamento ou threadprivate diretiva, então a variável é 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 no 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ático declarada dentro da extensão dinâmica de uma região paralela são privadas.

A maioria das cláusulas aceita um argumento de lista de variáveis , 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 nas cláusulas diretivas devem ser 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 pode ser especificada tanto em uma firstprivate cláusula quanto em uma lastprivate cláusula.

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

2.7.2.1 privado

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

private(variable-list)

O comportamento de uma variável especificada em uma private cláusula é o seguinte. Um novo objeto com duração de armazenamento automático é alocado para a estrutura. 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 é indeterminado. O objeto original referenciado pela variável tem um valor indeterminado na entrada do constructo, não deve ser modificado dentro da extensão dinâmica do constructo, e tem um valor indeterminado ao sair do constructo.

No contexto lexical da estrutura da diretiva, a variável refere-se ao novo objeto privado alocado pela thread.

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

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

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

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

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

2.7.2.2 Primeiro privado

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

firstprivate(variable-list)

As variáveis especificadas na lista de variáveis têm private semântica de cláusulas, conforme descrito no ponto 2.7.2.1. A inicialização ou construção acontece como se fosse feita uma vez por thread, antes da execução da construção pelo thread. Para uma firstprivate cláusula em uma construção paralela, o valor inicial do novo objeto privado é o valor do objeto original que existe imediatamente antes da construção paralela para o thread que o encontra. Para uma firstprivate cláusula em uma construção de compartilhamento de trabalho, o valor inicial do novo objeto privado para cada thread que executa a construção de compartilhamento de trabalho é o valor do objeto original que existe antes do ponto no tempo em que o mesmo thread encontra a construção de compartilhamento de trabalho. Além disso, para objetos C++, o novo objeto particular para cada thread é uma cópia construída a partir do objeto original.

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

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

  • Uma variável com um tipo de classe especificado como firstprivate deve ter um construtor de cópia acessível e inequívoca.

  • As variáveis que são privadas dentro de uma região paralela ou que aparecem na cláusula reduction de uma diretiva parallel não podem ser especificadas numa cláusula firstprivate numa diretiva de compartilhamento de trabalho que se liga à construção paralela.

2.7.2.3 Último privado

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

lastprivate(variable-list)

As variáveis especificadas na lista de variáveis têm private semântica de cláusula. Quando uma cláusula lastprivate aparece na diretiva que identifica uma construção de partilha de trabalho, o valor de cada variável lastprivate da última iteração sequencial do ciclo associado, ou da última diretiva de seção lexical, é atribuído ao objeto original da variável. As variáveis a que não é atribuído um valor pela última iteração da for ou parallel for, ou pela última seção em termos lexicais da diretiva sections ou parallel sections, têm valores indeterminados após a estrutura. Subobjetos não atribuídos também têm um valor indeterminado após a construção.

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

  • Todas as restrições aplicam-se a private.

  • Uma variável com um tipo de classe especificado como 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 que aparecem na cláusula reduction de uma diretiva parallel não podem ser especificadas numa cláusula lastprivate numa diretiva de compartilhamento de trabalho que se liga à construção paralela.

2.7.2.4 partilhado

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

A sintaxe da shared cláusula é a seguinte.

shared(variable-list)

2.7.2.5 padrão

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

default(shared | none)

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

A especificação default(none) requer que pelo menos uma das seguintes opções seja verdadeira para cada referência a uma variável na extensão lexical da construção paralela:

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

  • A variável é declarada dentro da construção paralela.

  • A variável é threadprivate.

  • A variável tem um tipo const qualificado.

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

Especificar uma variável em uma firstprivate, lastprivate, ou reduction cláusula de uma diretiva anexada causa uma referência implícita à variável no contexto de delimitação. Tais referências implícitas também estão sujeitas aos requisitos acima enumerados.

default Só uma parallel única cláusula pode ser especificada numa diretiva.

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

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

2.7.2.6 Redução

Esta cláusula executa uma redução nas variáveis escalares que aparecem na lista de variáveis, com o operador op. A sintaxe da reduction cláusula é a seguinte.

reduction( op:lista de variáveis)

Uma redução é normalmente especificada para uma declaração com uma das seguintes formas:

  • x=xopexpr
  • xbinop=expr
  • x=expropx (exceto para subtração)
  • x++
  • ++ x
  • x--
  • -- x

onde:

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

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

EXPR
Uma expressão com tipo escalar que não faz referência a x.

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

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

Segue-se um exemplo da reduction cláusula:

#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 mostrado no exemplo, um operador pode estar oculto dentro de uma chamada de função. O utilizador 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 é garantido não ocorrer 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 é indeterminada.

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. A especificação do operador permite explicitamente que a declaração de redução esteja fora da extensão lexical da construção. A diretiva pode especificar um certo número de reduction cláusulas, mas uma variável pode figurar, no máximo, numa reduction cláusula dessa diretiva.

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

No final da região para a qual a reduction cláusula 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 o cálculo 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 atinge a cláusula que contém e permanece assim até que o cálculo da redução seja concluído. Normalmente, o cálculo será concluído no final da construção; no entanto, se a reduction cláusula for usada em uma construção à 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 reduction cláusula.

A tabela a seguir lista os operadores que são válidos e seus valores canônicos de inicialização. O valor real de inicialização 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 à cláusula são as reduction seguintes:

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

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

  • As variáveis que são privadas dentro de uma região paralela ou que aparecem na cláusula reduction de uma diretiva parallel não podem ser especificadas numa cláusula reduction numa diretiva de compartilhamento de trabalho que se liga à construção 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 Cópia

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

copyin(
variable-list
)

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

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

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

2.7.2.8 CopyPrivate

A copyprivate cláusula 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 ao uso de uma variável compartilhada para o valor quando fornecer tal variável compartilhada seria difícil (por exemplo, em uma recursão que requer uma variável diferente em cada nível). A copyprivate cláusula só pode constar da single diretiva.

A sintaxe da copyprivate cláusula é a seguinte.

copyprivate(
variable-list
)

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

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

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

  • Caso se encontre uma single diretiva com uma copyprivate cláusula no alcance dinâmico de uma região paralela, todas as variáveis especificadas na copyprivate cláusula devem ser privadas no contexto envolvente.

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

2.8 Diretiva vinculativa

A vinculação dinâmica das diretivas deve respeitar as seguintes regras:

  • As diretivas for, sections, single, master e barrier vinculam-se ao envolvimento dinâmico parallel, caso exista, 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 vincula-se ao for dinâmico abrangente.

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

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

  • Uma diretiva nunca pode vincular-se a qualquer diretiva fora do compartimento paralleldinâmico mais próximo.

2.9 Aninhamento de diretivas

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

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

  • for, sections e single são as diretivas que se ligam ao mesmo parallel e não podem ser aninhadas entre si.

  • critical diretivas com o mesmo nome não podem ser aninhadas umas nas outras. Observe que essa restrição não é suficiente para evitar o impasse.

  • for, sectionse single as diretivas não são permitidas na extensão dinâmica de critical, orderede master regiões se as diretivas vincularem o mesmo parallel que as regiões.

  • barrier as diretivas não são permitidas na extensão dinâmica de for, ordered, sections, single, mastere critical regiões se as diretivas vincularem o mesmo parallel que as regiões.

  • master diretivas não são permitidas na extensão dinâmica de for, sections e single se as diretivas master se vincularem ao mesmo parallel que as diretivas de partilha de trabalho.

  • ordered As diretivas não são permitidas na extensão dinâmica das critical regiões se as diretivas se ligarem ao mesmo 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.