Ajude a manter o site livre, gratuito e sem propagandas. Colabore!
Em revisão
Em revisão
Vamos analisar o seguinte código.
Qual seria a saída esperada? Ao rodarmos este código, veremos uma saída da forma
Processo 0/4 Processo 2/4 Processo 3/4 Processo 3/4
Isto ocorre por uma situação de condição de corrida (race condition) entre os threads. As variáveis tid
e nt
foram declaradas antes da região paralela e, desta forma, são variáveis compartilhadas (shared variables) entre todos os threads na região paralela. Os locais na memória em que estas as variáveis estão alocadas é o mesmo para todos os threads.
A condição de corrida ocorre na linha 11. No caso da saída acima, as instâncias de processamento 1 e 3 entraram em uma condição de corrida no registro da variável tid
.
Devemos estar sempre atentos a uma possível condição de corrida. Este é um erro comum no desenvolvimento de códigos em paralelo.
Para evitarmos a condição de corrida, precisamos tornar a variável tid
privada na região paralela. I.e., cada thread precisa ter uma variável tid
privada. Podemos fazer isso alterando a linha 9 do código para
#pragma omp parallel private(tid)
Com essa alteração, a saída terá o formato esperado, como por exemplo
Processo 0/4 Processo 3/4 Processo 2/4 Processo 1/4
Faça a alteração e verifique!
A diretiva #pragma omp parallel
também aceita as instruções:
default(private|shared|none)
: o padrão é shared
;
shared(var1, var2, ..., varn)
: para especificar explicitamente as variáveis que devem ser compartilhadas.
Em revisão
Vamos considerar o problema de computar
(2.1) |
em paralelo com threads. Começamos analisando o seguinte código.
Ao executarmos este código com , vamos ter saídas erradas. Verifique! Qual o valor esperado?
O erro do código está na condição de corrida (race condition) na linha 21. Esta é uma operação, ao ser iniciada por um thread, precisa ser terminada pelo thread antes que outro possa iniciá-la. Podemos fazer adicionando o construtor
#pragma omp critical
imediatamente antes da linha de código s += 1;
. O código fica como segue, verifique!
Esta abordagem evita a condição de corrida e fornece a resposta esperada. No entanto, ela acaba serializando o código, o qual é será muito mais lento que o código serial. Verifique!
A utilização do construtor
#pragma omp critical
reduz a performance do código e só deve ser usada quando realmente necessária.
Uma alternativa é alocar as somas parciais de cada thread em uma variável privada e, ao final, somar as partes computadas. Isto pode ser feito com o seguinte código. Verifique!
Este último código pode ser simplificado usando o construtor
#pragma omp for
Com este construtor, o laço do somatório pode ser automaticamente distribuindo entre os threads. Verifique o seguinte código!
Mais simples e otimizado, é automatizar a operação de redução (no caso, a soma das somas parciais) adicionado
reduction(+: s)
ao construtor que inicializa a região paralela. Verifique o seguinte código!
A instrução de redução pode ser usada com qualquer operação binária aritmética (+
, -
, /
, *
), lógica (&
, |
) ou procedimentos intrínsecos (max
, min
).
Em revisão
A sincronização dos threads deve ser evitada sempre que possível, devido a perda de performance em códigos paralelos. Atenção, ela ocorre implicitamente no término da região paralela!
Em revisão
No seguinte código, o thread 1 é atrasado em 1 segundo, de forma que ele é o último a imprimir. Verifique!
Agora, podemos forçar a sincronização dos threads usando o construtor
#pragma omp barrier
em uma determinada linha do código. Por exemplo, podemos fazer todos os threads esperarem pelo thread 1 no código acima. Veja a seguir o código modificado. Teste!
Em revisão
O construtor sections
pode ser usado para determinar seções do código que deve ser executada de forma serial apenas uma vez por um único thread. Verifique o seguinte código.
No código acima, o primeiro thread que alcançar a linha 19 é o único a executar a seção 1 e, o primeiro que alcançar a linha 25 é o único a executar a seção 2.
Observe que ocorre a sincronização implícita de todos os threads ao final do escopo sections
. Isso pode ser evitado usando a cláusula nowait
, i.e. alterando a linha 16 para
# pragma omp sections nowait
Teste!
A clausula nowait
também pode ser usada com o construtor for
, i.e.
#pragma omp for nowait
Para uma região contendo apenas uma seção, pode-se usar o construtor
#pragma omp single
Isto é equivalente a escrever
#pragma omp sections #pragma omp section
Em revisão
Escreva um código MP para computar o produto escalar entre dois vetores de pontos flutuantes randômicos.
A solução é dada no código a seguir.
Faça um código MP para computar a multiplicação de uma matriz por um vetor de elementos (pontos flutuantes randômicos). Utilize o construtor omp sections
para distribuir a computação entre somente dois threads.
A solução é dada no código a seguir.
Em revisão
Considere o seguinte código
Qual o valor impresso?
Escreva um código MP para computar uma aproximação para
(2.2) |
usando a regra composta do trapézio com subintervalos uniformes.
Escreva um código MP para computar uma aproximação para
(2.3) |
usando a regra composta de Simpson com subintervalos uniformes. Dica: evite sincronizações desnecessárias!
Escreva um código MP para computar a multiplicação de uma matriz por um vetor de elementos (pontos flutuantes randômicos). Faça o código de forma a suportar uma arquitetura com threads.
Escreva um código MP para computar o produto de duas matrizes de pontos flutuantes randômicos. Utilize o construtor omp sections
para distribuir a computação entre somente dois threads.
Escreva um código MP para computar o produto de duas matrizes de pontos flutuantes randômicos. Faça o código de forma a suportar uma arquitetura com threads.
As informações preenchidas são enviadas por e-mail para o desenvolvedor do site e tratadas de forma privada. Consulte a Política de Uso de Dados para mais informações. Aproveito para agradecer a todas/os que de forma assídua ou esporádica contribuem enviando correções, sugestões e críticas!