R. Exemplos
Veja os exemplos a seguir dos constructos definidos neste documento. Uma instrução depois de uma diretiva é composta somente quando necessário, e uma instrução não composta é recuada de uma diretiva anterior a ela.
A.1 Um loop simples em paralelo
O exemplo a seguir demonstra como paralelizar um loop usando o paralelo para diretiva. A variável de iteração de loop é privada por padrão, portanto, não é necessário especificá-la explicitamente em uma cláusula privada.
#pragma omp parallel for
for (i=1; i<n; i++)
b[i] = (a[i] + a[i-1]) / 2.0;
A.2 Compilação condicional
Os exemplos a seguir ilustram o uso da compilação condicional usando a macro OpenMP _OPENMP. Com a compilação OpenMP, a macro _OPENMP
se torna definida.
# ifdef _OPENMP
printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif
O operador de pré-processador definido permite que mais de uma macro seja testada em uma única diretiva.
# if defined(_OPENMP) && defined(VERBOSE)
printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif
A.3 Regiões paralelas
A diretiva paralela pode ser usada em programas paralelos de alta granularidade. No exemplo a seguir, cada thread na região paralela decide em qual parte da matriz global x
trabalhar, com base no número do thread:
#pragma omp parallel shared(x, npoints) private(iam, np, ipoints)
{
iam = omp_get_thread_num();
np = omp_get_num_threads();
ipoints = npoints / np;
subdomain(x, iam, ipoints);
}
A.4 A cláusula nowait
Se houver muitos loops independentes em uma região paralela, você poderá usar a cláusula nowait para evitar a barreira implícita no final da diretiva for
da seguinte maneira:
#pragma omp parallel
{
#pragma omp for nowait
for (i=1; i<n; i++)
b[i] = (a[i] + a[i-1]) / 2.0;
#pragma omp for nowait
for (i=0; i<m; i++)
y[i] = sqrt(z[i]);
}
A.5 A diretiva crítica
O exemplo a seguir inclui várias diretivas críticas. O exemplo ilustra um modelo de enfileiramento no qual uma tarefa é removida da lista e trabalhada. Para se proteger contra muitos threads que removem da fila a mesma tarefa, a operação de remoção da fila deve estar em uma seção critical
. Como as duas filas neste exemplo são independentes, elas são protegidas por diretivas critical
com nomes diferentes, xaxis e yaxis.
#pragma omp parallel shared(x, y) private(x_next, y_next)
{
#pragma omp critical ( xaxis )
x_next = dequeue(x);
work(x_next);
#pragma omp critical ( yaxis )
y_next = dequeue(y);
work(y_next);
}
A.6 A cláusula lastprivate
A execução correta às vezes depende do valor que a última iteração de um loop atribui a uma variável. Esses programas devem listar todas essas variáveis como argumentos para uma cláusula lastprivate para que os valores das variáveis sejam os mesmos de quando o loop é executado em sequência.
#pragma omp parallel
{
#pragma omp for lastprivate(i)
for (i=0; i<n-1; i++)
a[i] = b[i] + b[i+1];
}
a[i]=b[i];
No exemplo anterior, o valor de i
no final da região paralela será igual a n-1
, como no caso sequencial.
A.7 A cláusula reduction
O exemplo a seguir demonstra a cláusula reduction:
#pragma omp parallel for private(i) shared(x, y, n) \
reduction(+: a, b)
for (i=0; i<n; i++) {
a = a + x[i];
b = b + y[i];
}
A.8 Seções paralelas
No exemplo a seguir (para a seção 2.4.2), as funções xaxis, yaxis e zaxis podem ser executadas simultaneamente. A primeira diretiva section
é opcional. Todas as diretivas section
precisam aparecer na extensão lexical do constructo parallel sections
.
#pragma omp parallel sections
{
#pragma omp section
xaxis();
#pragma omp section
yaxis();
#pragma omp section
zaxis();
}
A.9 Diretivas single
O exemplo a seguir demonstra o uso da diretiva single. No exemplo, apenas um thread (geralmente o primeiro que encontra a diretiva single
) imprime a mensagem de progresso. O usuário não deve fazer nenhuma suposição sobre qual thread executará a seção single
. Todos os outros threads ignorarão a seção single
e pararão na barreira no final do constructo single
. Se outros threads puderem continuar sem aguardar o thread executando a seção single
, uma cláusula nowait
poderá ser especificada na diretiva single
.
#pragma omp parallel
{
#pragma omp single
printf_s("Beginning work1.\n");
work1();
#pragma omp single
printf_s("Finishing work1.\n");
#pragma omp single nowait
printf_s("Finished work1 and beginning work2.\n");
work2();
}
A.10 Ordenação sequencial
As seções ordenadas são úteis para ordenar sequencialmente a saída do trabalho que é feito em paralelo. O programa a seguir imprime os índices em ordem sequencial:
#pragma omp for ordered schedule(dynamic)
for (i=lb; i<ub; i+=st)
work(i);
void work(int k)
{
#pragma omp ordered
printf_s(" %d", k);
}
A.11 Um número fixo de threads
Alguns programas dependem de um número fixo e pré-especificado de threads para serem executados corretamente. Como a configuração padrão para o ajuste dinâmico do número de threads é definida pela implementação, esses programas podem optar por desativar a capacidade de threads dinâmicos e definir o número de threads explicitamente para manter a portabilidade. O exemplo a seguir mostrar como fazer isso usando omp_set_dynamic e omp_set_num_threads:
omp_set_dynamic(0);
omp_set_num_threads(16);
#pragma omp parallel shared(x, npoints) private(iam, ipoints)
{
if (omp_get_num_threads() != 16)
abort();
iam = omp_get_thread_num();
ipoints = npoints/16;
do_by_16(x, iam, ipoints);
}
Neste exemplo, o programa será executado corretamente somente se for executado por 16 threads. Se a implementação não puder dar suporte a 16 threads, o comportamento deste exemplo será definido pela implementação.
O número de threads que executam uma região paralela permanece constante durante uma região paralela, independentemente da configuração dos threads dinâmicos. O mecanismo de threads dinâmicos determina o número de threads a serem usados no início da região paralela e o mantém constante pela duração da região.
A.12 A diretiva atomic
O exemplo a seguir evita condições de corrida (atualizações simultâneas de um elemento de x por muitos threads) usando a diretiva atomic :
#pragma omp parallel for shared(x, y, index, n)
for (i=0; i<n; i++)
{
#pragma omp atomic
x[index[i]] += work1(i);
y[i] += work2(i);
}
A vantagem de usar a diretiva atomic
neste exemplo é que ela permite que atualizações de dois elementos diferentes de x ocorram em paralelo. Se, em vez disso, for usada uma diretiva crítica, todas as atualizações de elementos de x serão executadas serialmente (embora não em qualquer ordem garantida).
A diretiva atomic
se aplica somente à instrução C ou C++ imediatamente após ela. Como resultado, os elementos de y não são atualizados atomicamente neste exemplo.
A.13 Uma diretiva flush com uma lista
O exemplo a seguir usa a diretiva flush
para sincronização ponto a ponto de objetos específicos entre pares de threads:
int sync[NUMBER_OF_THREADS];
float work[NUMBER_OF_THREADS];
#pragma omp parallel private(iam,neighbor) shared(work,sync)
{
iam = omp_get_thread_num();
sync[iam] = 0;
#pragma omp barrier
// Do computation into my portion of work array
work[iam] = ...;
// Announce that I am done with my work
// The first flush ensures that my work is
// made visible before sync.
// The second flush ensures that sync is made visible.
#pragma omp flush(work)
sync[iam] = 1;
#pragma omp flush(sync)
// Wait for neighbor
neighbor = (iam>0 ? iam : omp_get_num_threads()) - 1;
while (sync[neighbor]==0)
{
#pragma omp flush(sync)
}
// Read neighbor's values of work array
... = work[neighbor];
}
A.14 Uma diretiva flush sem uma lista
O exemplo a seguir (para a seção 2.6.5) distingue os objetos compartilhados afetados por uma diretiva flush
sem lista dos objetos compartilhados que não são afetados:
// omp_flush_without_list.c
#include <omp.h>
int x, *p = &x;
void f1(int *q)
{
*q = 1;
#pragma omp flush
// x, p, and *q are flushed
// because they are shared and accessible
// q is not flushed because it is not shared.
}
void f2(int *q)
{
#pragma omp barrier
*q = 2;
#pragma omp barrier
// a barrier implies a flush
// x, p, and *q are flushed
// because they are shared and accessible
// q is not flushed because it is not shared.
}
int g(int n)
{
int i = 1, j, sum = 0;
*p = 1;
#pragma omp parallel reduction(+: sum) num_threads(10)
{
f1(&j);
// i, n and sum were not flushed
// because they were not accessible in f1
// j was flushed because it was accessible
sum += j;
f2(&j);
// i, n, and sum were not flushed
// because they were not accessible in f2
// j was flushed because it was accessible
sum += i + j + *p + n;
}
return sum;
}
int main()
{
}
A.15 O número de threads usados
Considere o exemplo incorreto a seguir (para a seção 3.1.2):
np = omp_get_num_threads(); // misplaced
#pragma omp parallel for schedule(static)
for (i=0; i<np; i++)
work(i);
A chamada omp_get_num_threads()
retorna 1 na seção serial do código, portanto, np sempre será igual a 1 no exemplo anterior. Para determinar o número de threads que serão implantados na região paralela, a chamada deve estar dentro da região paralela.
O exemplo a seguir mostra como regenerar este programa sem incluir uma consulta para o número de threads:
#pragma omp parallel private(i)
{
i = omp_get_thread_num();
work(i);
}
A.16 Bloqueios
No exemplo a seguir (para a seção 3.2), o argumento para as funções de bloqueio deve ter tipo omp_lock_t
e não há necessidade de liberá-lo. As funções de bloqueio fazem com que os threads fiquem ociosos enquanto aguardam a entrada na primeira seção crítica, mas fazem outro trabalho enquanto aguardam a entrada na segunda. A função omp_set_lock
bloqueia, mas a função omp_test_lock
não, o que permite que o trabalho em skip()
seja feito.
// omp_using_locks.c
// compile with: /openmp /c
#include <stdio.h>
#include <omp.h>
void work(int);
void skip(int);
int main() {
omp_lock_t lck;
int id;
omp_init_lock(&lck);
#pragma omp parallel shared(lck) private(id)
{
id = omp_get_thread_num();
omp_set_lock(&lck);
printf_s("My thread id is %d.\n", id);
// only one thread at a time can execute this printf
omp_unset_lock(&lck);
while (! omp_test_lock(&lck)) {
skip(id); // we do not yet have the lock,
// so we must do something else
}
work(id); // we now have the lock
// and can do the work
omp_unset_lock(&lck);
}
omp_destroy_lock(&lck);
}
A.17 Bloqueios aninháveis
O exemplo a seguir (para a seção 3.2) demonstra como um bloqueio aninhável pode ser usado para sincronizar as atualizações para uma estrutura inteira e para um de seus membros.
#include <omp.h>
typedef struct {int a,b; omp_nest_lock_t lck;} pair;
void incr_a(pair *p, int a)
{
// Called only from incr_pair, no need to lock.
p->a += a;
}
void incr_b(pair *p, int b)
{
// Called both from incr_pair and elsewhere,
// so need a nestable lock.
omp_set_nest_lock(&p->lck);
p->b += b;
omp_unset_nest_lock(&p->lck);
}
void incr_pair(pair *p, int a, int b)
{
omp_set_nest_lock(&p->lck);
incr_a(p, a);
incr_b(p, b);
omp_unset_nest_lock(&p->lck);
}
void f(pair *p)
{
extern int work1(), work2(), work3();
#pragma omp parallel sections
{
#pragma omp section
incr_pair(p, work1(), work2());
#pragma omp section
incr_b(p, work3());
}
}
A.18 Aninhado para diretivas
O exemplo a seguir de for
directive nesting está em conformidade porque as diretivas for
internas e externas se vinculam a diferentes regiões paralelas:
#pragma omp parallel default(shared)
{
#pragma omp for
for (i=0; i<n; i++)
{
#pragma omp parallel shared(i, n)
{
#pragma omp for
for (j=0; j<n; j++)
work(i, j);
}
}
}
Uma variação a seguir do exemplo anterior também está em conformidade:
#pragma omp parallel default(shared)
{
#pragma omp for
for (i=0; i<n; i++)
work1(i, n);
}
void work1(int i, int n)
{
int j;
#pragma omp parallel default(shared)
{
#pragma omp for
for (j=0; j<n; j++)
work2(i, j);
}
return;
}
A.19 Exemplos que mostram o aninhamento incorreto de diretivas de compartilhamento do trabalho
Os exemplos nesta seção ilustram as regras de aninhamento de diretiva.
O exemplo a seguir não está em conformidade porque as diretivas internas e externas for
estão aninhadas e vinculadas à mesma diretiva parallel
:
void wrong1(int n)
{
#pragma omp parallel default(shared)
{
int i, j;
#pragma omp for
for (i=0; i<n; i++) {
#pragma omp for
for (j=0; j<n; j++)
work(i, j);
}
}
}
A seguinte versão aninhada dinamicamente do exemplo anterior também não está em conformidade:
void wrong2(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++)
work1(i, n);
}
}
void work1(int i, int n)
{
int j;
#pragma omp for
for (j=0; j<n; j++)
work2(i, j);
}
O exemplo a seguir não está em conformidade porque as diretivas for
e single
estão aninhadas e vinculadas à mesma região paralela:
void wrong3(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++) {
#pragma omp single
work(i);
}
}
}
O exemplo a seguir não está em conformidade porque uma diretiva barrier
dentro de um for
pode resultar em deadlock:
void wrong4(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++) {
work1(i);
#pragma omp barrier
work2(i);
}
}
}
O exemplo a seguir não está em conformidade devido aos resultados barrier
no deadlock, já que apenas um thread por vez pode inserir a seção crítica:
void wrong5()
{
#pragma omp parallel
{
#pragma omp critical
{
work1();
#pragma omp barrier
work2();
}
}
}
O exemplo a seguir não está em conformidade devido aos resultados barrier
no deadlock, já que apenas um thread executa a seção single
:
void wrong6()
{
#pragma omp parallel
{
setup();
#pragma omp single
{
work1();
#pragma omp barrier
work2();
}
finish();
}
}
A.20 Associar diretivas barrier
As regras de associação de diretiva exigem que uma diretiva barrier
seja associada à diretiva parallel
delimitadora mais próxima. Para saber mais sobre a associação de diretivas, consulte a seção 2.8.
No exemplo a seguir, a chamada de main para sub2 está em conformidade porque a barrier
(na sub3) se associa à região paralela na sub2. A chamada de main para sub1 está em conformidade porque a barrier
se associa à região paralela na sub-rotina sub2. A chamada de main para sub3 está em conformidade porque a barrier
não se associa a nenhuma região paralela e é ignorada. Além disso, a barrier
somente sincroniza a equipe de threads na região paralela delimitadora e não todos os threads criados na sub1.
int main()
{
sub1(2);
sub2(2);
sub3(2);
}
void sub1(int n)
{
int i;
#pragma omp parallel private(i) shared(n)
{
#pragma omp for
for (i=0; i<n; i++)
sub2(i);
}
}
void sub2(int k)
{
#pragma omp parallel shared(k)
sub3(k);
}
void sub3(int n)
{
work(n);
#pragma omp barrier
work(n);
}
A.21 Variáveis de escopo com a cláusula private
Os valores de i
e j
no exemplo a seguir são indefinidos na saída da região paralela:
int i, j;
i = 1;
j = 2;
#pragma omp parallel private(i) firstprivate(j)
{
i = 3;
j = j + 2;
}
printf_s("%d %d\n", i, j);
Para saber mais sobre a cláusula private
, consulte a seção 2.7.2.1.
A.22 A cláusula default(none)
O exemplo a seguir distingue as variáveis afetadas pela cláusula default(none)
das variáveis que não são:
// openmp_using_clausedefault.c
// compile with: /openmp
#include <stdio.h>
#include <omp.h>
int x, y, z[1000];
#pragma omp threadprivate(x)
void fun(int a) {
const int c = 1;
int i = 0;
#pragma omp parallel default(none) private(a) shared(z)
{
int j = omp_get_num_thread();
//O.K. - j is declared within parallel region
a = z[j]; // O.K. - a is listed in private clause
// - z is listed in shared clause
x = c; // O.K. - x is threadprivate
// - c has const-qualified type
z[i] = y; // C3052 error - cannot reference i or y here
#pragma omp for firstprivate(y)
for (i=0; i<10 ; i++) {
z[i] = y; // O.K. - i is the loop control variable
// - y is listed in firstprivate clause
}
z[i] = y; // Error - cannot reference i or y here
}
}
Para saber mais sobre a cláusula default
, consulte a seção 2.7.2.5.
A.23 Exemplos da diretiva ordered
É possível ter muitas seções ordenadas com uma for
especificada com a cláusula ordered
. O primeiro exemplo não está em conformidade porque a API especifica a seguinte regra:
“Uma iteração de um loop com um constructo for
não deve executar a mesma diretiva ordered
mais de uma vez e não deve executar mais de uma diretiva ordered
.” (Consulte a seção 2.6.6.)
Neste exemplo não conforme, todas as iterações executam duas seções ordenadas:
#pragma omp for ordered
for (i=0; i<n; i++)
{
...
#pragma omp ordered
{ ... }
...
#pragma omp ordered
{ ... }
...
}
O exemplo em conformidade a seguir mostra uma for
com mais de uma seção ordenada:
#pragma omp for ordered
for (i=0; i<n; i++)
{
...
if (i <= 10)
{
...
#pragma omp ordered
{ ... }
}
...
(i > 10)
{
...
#pragma omp ordered
{ ... }
}
...
}
A.24 Exemplo da cláusula private
A cláusula private de uma região paralela só está em vigor para a extensão lexical da região, não para sua extensão dinâmica. Portanto, no exemplo a seguir, qualquer uso da variável a dentro do loop for
na rotina f se refere a uma cópia private de a, enquanto um uso na rotina g se refere ao a global.
int a;
void f(int n)
{
a = 0;
#pragma omp parallel for private(a)
for (int i=1; i<n; i++)
{
a = i;
g(i, n);
d(a); // Private copy of "a"
...
}
...
void g(int k, int n)
{
h(k,a); // The global "a", not the private "a" in f
}
A.25 Exemplos da cláusula de atributo de dados copyprivate
Exemplo 1: A cláusula copyprivate pode ser usada para difundir valores adquiridos por um único thread diretamente para todas as instâncias das variáveis privadas nos outros threads.
float x, y;
#pragma omp threadprivate(x, y)
void init( )
{
float a;
float b;
#pragma omp single copyprivate(a,b,x,y)
{
get_values(a,b,x,y);
}
use_values(a, b, x, y);
}
Se a rotina init for chamada de uma região serial, seu comportamento não será afetado pela presença das diretivas. Depois que a chamada para a rotina get_values tiver sido executada por um thread, nenhum thread sairá do constructo até que os objetos privados designados por a, b, x e y em todos os threads tenham sido definidos com os valores lidos.
Exemplo 2: Em contraste com o exemplo anterior, suponha que a leitura deva ser executada por um thread específico, digamos, o thread mestre. Nesse caso, a cláusula copyprivate
não pode ser usada para fazer a transmissão diretamente, mas pode ser usada para fornecer acesso a um objeto compartilhado temporário.
float read_next( )
{
float * tmp;
float return_val;
#pragma omp single copyprivate(tmp)
{
tmp = (float *) malloc(sizeof(float));
}
#pragma omp master
{
get_float( tmp );
}
#pragma omp barrier
return_val = *tmp;
#pragma omp barrier
#pragma omp single
{
free(tmp);
}
return return_val;
}
Exemplo 3: Suponha que o número de objetos de bloqueio necessários em uma região paralela não possa ser determinado facilmente antes de inseri-lo. A cláusula copyprivate
pode ser usada para fornecer acesso a objetos de bloqueio compartilhados alocados nessa região paralela.
#include <omp.h>
omp_lock_t *new_lock()
{
omp_lock_t *lock_ptr;
#pragma omp single copyprivate(lock_ptr)
{
lock_ptr = (omp_lock_t *) malloc(sizeof(omp_lock_t));
omp_init_lock( lock_ptr );
}
return lock_ptr;
}
A.26 A diretiva threadprivate
Os exemplos a seguir demonstram como usar a diretiva threadprivate para dar a cada thread um contador separado.
Exemplo 1
int counter = 0;
#pragma omp threadprivate(counter)
int sub()
{
counter++;
return(counter);
}
Exemplo 2
int sub()
{
static int counter = 0;
#pragma omp threadprivate(counter)
counter++;
return(counter);
}
A.27 Matrizes de comprimento variável C99
O exemplo a seguir demonstra como usar VLAs (Matrizes de comprimento variável) C99 em uma diretiva firstprivate.
Observação
No momento, não há suporte para matrizes de comprimento variável no Visual C++.
void f(int m, int C[m][m])
{
double v1[m];
...
#pragma omp parallel firstprivate(C, v1)
...
}
A.28 A cláusula num_threads
O exemplo a seguir demonstra a cláusula num_threads. A região paralela é executada com um máximo de 10 threads.
#include <omp.h>
main()
{
omp_set_dynamic(1);
...
#pragma omp parallel num_threads(10)
{
... parallel region ...
}
}
A.29 Constructos de compartilhamento de trabalho em um constructo crítico
O exemplo a seguir demonstra o uso de um constructo de compartilhamento de trabalho dentro de um constructo critical
. Este exemplo está em conformidade porque o constructo de compartilhamento de trabalho e o constructo critical
não se associam à mesma região paralela.
void f()
{
int i = 1;
#pragma omp parallel sections
{
#pragma omp section
{
#pragma omp critical (name)
{
#pragma omp parallel
{
#pragma omp single
{
i++;
}
}
}
}
}
}
A.30 Reprivatização
O exemplo a seguir demonstra a reprivatização de variáveis. Variáveis privadas podem ser marcadas como private
novamente em uma diretiva aninhada. Você não precisa compartilhar as variáveis na região paralela delimitadora.
int i, a;
...
#pragma omp parallel private(a)
{
...
#pragma omp parallel for private(a)
for (i=0; i<10; i++)
{
...
}
}
A.31 Funções de bloqueio thread-safe
O exemplo de C++ a seguir demonstra como inicializar uma matriz de bloqueios em uma região paralela usando omp_init_lock.
// A_13_omp_init_lock.cpp
// compile with: /openmp
#include <omp.h>
omp_lock_t *new_locks() {
int i;
omp_lock_t *lock = new omp_lock_t[1000];
#pragma omp parallel for private(i)
for (i = 0 ; i < 1000 ; i++)
omp_init_lock(&lock[i]);
return lock;
}
int main () {}