Вы находитесь на странице: 1из 8

Exemplos de uso das diretivas do OpenMP

Emerson C. Lima
27 de Outubro de 2016

Clusula nowait

Nos cdigos a seguir exemplificaremos o uso da clusula nowait quando aplicada aos construtores
de trabalho sections, single e for.
Listing 1: Construtores de trabalho sections sem a clusula nowait
1
2
3
4

# include
# include
# include
# include

< stdlib .h >


< stdio .h >
< stdbool .h >
< omp .h >

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

int main ( int argc , char * argv []) {


# pragma omp parallel
{
# pragma omp sections
{
# pragma omp section
{
printf ( " se o A na thread % d \ n " , om p_ ge t_ th re ad _nu m () ) ;
}
# pragma omp section
{
printf ( " se o B na thread % d \ n " , om p_ ge t_ th re ad _nu m () ) ;
}
# pragma omp section
{
printf ( " se o C na thread % d \ n " , om p_ ge t_ th re ad _nu m () ) ;
}
}
# pragma omp single
{
printf ( " trabalho single na thread % d ~\ n " , om p_ ge t_ th re ad _n um () ) ;
}
}
return EXIT_SUCCESS ;
}

Listing 2: Construtores de trabalho sections com a clusula nowait


1
2

# include < stdlib .h >


# include < stdio .h >

3
4

# include < stdbool .h >


# include < omp .h >

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

int main ( int argc , char * argv []) {


# pragma omp parallel
{
# pragma omp sections nowait
{
# pragma omp section
{
printf ( " se o A na thread % d \ n " , om p_ ge t_ th re ad _nu m () ) ;
}
# pragma omp section
{
printf ( " se o B na thread % d \ n " , om p_ ge t_ th re ad _nu m () ) ;
}
# pragma omp section
{
printf ( " se o C na thread % d \ n " , om p_ ge t_ th re ad _nu m () ) ;
}
}
# pragma omp single
{
printf ( " trabalho single na thread % d ~\ n " , om p_ ge t_ th re ad _n um () ) ;
}
}
return EXIT_SUCCESS ;
}

No Cdigo 1 e 2 estamos construindo na linha 9 uma regio paralela cujo trabalho ser delimitado por sees. Cada seo determina ento o trabalho que ser alocado em threads e executados
em paralelos. Porm a execuo da linha 26, contida em um construtor de trabalho single que
determina que essa linha ser executada uma nica vez, ser adiada at que todas as threads
expandidas pelo construtor de trabalho single seja concluida. Isso uma caracterstica da filosofia
do paralelismo do omp expandir (trabalho em paralelo) e retrair (a execuo paralela).
O uso da clusula nowait permite portanto para modificar esse comportamento padro do omp.
Ela indica ao compilador que essa sincronizao no necessria. Isso acarreta que a execuo da
linha 26 poder ser iniciada logo aps o incio de cada seo de trabalho paralel.
Listing 3: Construtores de trabalho for sem a clusula nowait
1
2
3
4

# include
# include
# include
# include

< stdlib .h >


< stdio .h >
< stdbool .h >
< omp .h >

5
6
7
8
9
10
11
12
13
14

int main ( int argc , char * argv []) {


# pragma omp parallel
{
# pragma omp for
for ( int i = 0; i < 3; i += 1) {
printf ( " itera o % d na thread % d \ n " , i , om p_ ge t_ th re ad _n um () ) ;
}
# pragma omp single
{

printf ( " trabalho single na thread % d ~\ n " , om p_ ge t_ th re ad _n um () ) ;

15

16

}
return EXIT_SUCCESS ;

17
18
19

Listing 4: Construtores de trabalho for com a clusula nowait


1
2
3
4

# include
# include
# include
# include

< stdlib .h >


< stdio .h >
< stdbool .h >
< omp .h >

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

int main ( int argc , char * argv []) {


# pragma omp parallel
{
# pragma omp for nowait
for ( int i = 0; i < 3; i += 1) {
printf ( " itera o % d na thread % d \ n " , i , om p_ ge t_ th re ad _n um () ) ;
}
# pragma omp single
{
printf ( " trabalho single na thread % d ~\ n " , om p_ ge t_ th re ad _n um () ) ;
}
}
return EXIT_SUCCESS ;
}

A clusula for utilizada para construo de trabalho paralelo pela iteraes do loop for.
Quando utilizada, cada iterao ser colocada em um thread para execuo que ocorrer concorrentemente. construtor de trabalho for tambm possui o comportamento padro de aguardar
todo o trabalho iniciado em paralelo ser concludo para poder avanar a execuo do cdigo. O
nowait portanto atua como um modificador desse comportamento padro permitindo que novas
expanses possam ser realizadas logo aps a criao das threads do construtor de trabalho atual.

Clusula private, firstprivate e lastprivate

Uma thread pode ter suas prprias variveis locais cujos valores e acessos no so visveis outras
threads. Esse tipo de varivel contrasta com variveis compartilhadas que por serem utilizadas
por duas ou mais threads demandam sincronizao para leitura e escrita. Essa sincronizao entre
threads quase sempre demanda cdigo mais complexo, difcil depurao e reduo de desempenho.
Para evitar esses nus desejvel sempre que possvel trabalhar com variveis locais em threads.
As clusulas private, firstprivate e lastprivate podem ser utilizadas pelo programador para
um maior controle na forma como as variveis locais das threads so inicializadas. Nesta seo
os cdigos a seguir nos ajuda a elucidar as diferentes sutilezas nas funcionalidades das clusulas
private, firstprivate e lastprivate.
Listing 5: Exemplo de uso da clusula private
1
2
3

# include < stdlib .h >


# include < stdio .h >
# include < stdbool .h >

4
5
6

# include < omp .h >


# include < unistd .h >
# include < limits .h >

7
8
9
10
11
12
13
14
15
16
17

int main ( int argc , char * argv []) {


int a = 0;
# pragma omp parallel
{
# pragma omp master
{
while ( a < 1 e6 ) {
a += 2;
}
}

18

# pragma omp single


{
for ( int i = 0; i < 8; i += 1) {
# pragma omp task private ( a )
printf ( " thread % d i = % d \ n " , om p_ ge t_ th re ad _nu m () , a ) ;
}
}

19
20
21
22
23
24
25

}
return EXIT_SUCCESS ;

26
27
28

O comportamento padro do omp sincronizar acesso de leitura e escritas em variveis no


locais. Como comentado isso nem sempre desejvel pois o trabalho extra necessrio para sincronizar o acesso varivel pode reduzir o desempenho de execuo. Por isso desejvel utilizar
variveis locais. Isso obtido pela clusula private mostrada no Cdigo 5.
Listing 6: Exemplo de uso da clusula firstprivate
1
2
3
4
5
6

# include
# include
# include
# include
# include
# include

< stdlib .h >


< stdio .h >
< stdbool .h >
< omp .h >
< unistd .h >
< limits .h >

7
8
9
10
11
12
13
14
15
16
17

int main ( int argc , char * argv []) {


int a = 0;
# pragma omp parallel
{
# pragma omp master
{
while ( a < 1 e6 ) {
a += 2;
}
}

18
19
20
21
22
23

# pragma omp single


{
for ( int i = 0; i < 8; i += 1) {
# pragma omp task firstprivate ( a )
printf ( " thread % d i = % d \ n " , om p_ ge t_ th re ad _nu m () , a ) ;

24

25

}
return EXIT_SUCCESS ;

26
27
28

Nele, a varivel no-local a ser tratada como uma varivel local e ser inicializada com o valor
0 para cada thread. Cada thread ter sua prpria cpia de a e, portanto, modificaes no valor da
varivel feitas por uma thread s sero visveis por ela mesmo.
A clusula firstprivate sutilmente diferente. Ela tambm torna a varivel a local, porm a
inicializao ser feita a partir de uma cpia sincronizada do valor de a que ela possuir no momento
de incio de sua execuo. Ou seja, ser feita somente uma primeira cpia sincronizada. Aps isso,
qualquer modificao ser local prpria thread.
Listing 7: Exemplo de uso da clusula lastprivate
1
2
3
4
5
6

# include
# include
# include
# include
# include
# include

< stdlib .h >


< stdio .h >
< stdbool .h >
< omp .h >
< unistd .h >
< limits .h >

7
8
9
10
11
12
13
14
15

int main ( int argc , char * argv []) {


int a = 0;
# pragma omp parallel for lastprivate ( a )
for ( int i = 0; i < 10; i += 1) {
a = i;
printf ( " thread % d : i = %d , a = % d \ n " , om p_ ge t_ th re ad _n um () , i , a ) ;
}
printf ( " a = % d \ n " , a ) ;

16

return EXIT_SUCCESS ;

17
18

O lastprivate semelhante ao firstprivate, mas ao invs de uma sincronizao num primeiro


momento para leitura e inicializao da varivel local a ser feita um ltima sincronizao para
escrita na varivel global a. Esse comportamento interessante para se propagar o resultado de uma
computao em uma thread s demais. J que sincronizaes intermedirias so desnecessrias.
Reduzindo assim o nmero de sincronizaes necessrias e minimizando o desempenho.

O construtor de trabalho task

O cenrio formado por uma tarefa custosa sem dependncia de dados o ideal para a utilizao
de tasks. Essa tarefa demorada colocada em um construtor de trabalho task que inicializa um
thread com o trabalho a ser realizado e o coloca em uma fila para posterior execuo. Uma vez
selecionada pelo escalonador, a thread realizar seu trabalho e destruda. Em um loop com
iteraes custosas, isso permite que ele crie uma task para cada iterao acelerando a iteraes o
loop.
O cdigo a seguir apresenta um exemplo que que utiliza o construtor de trabalho task para
executar tarefas dentro de um loop.
5

Listing 8: Exemplo de uso do construtor de trabalho task


1
2
3
4

# include
# include
# include
# include

< stdlib .h >


< stdio .h >
< stdbool .h >
< omp .h >

5
6
7
8
9
10
11
12
13
14
15
16

int main ( int argc , char * argv []) {


int a = 0;
{
# pragma omp for private ( a )
for ( int i = 0; i < 3; i += 1) {
# pragma omp task
printf ( " tarefa % d ...\ n " , i ) ;
}
}
return EXIT_SUCCESS ;
}

O Cdigo reftask mostra a inicializao de diversas unidades de trabalho em tasks. O task


til quando no se tem certeza da quantidade de trabalhos paralelos que precisam ser executados.
Desse modo o trabalho ser enfileirado e executado segundo a vazo disponvel de processamento.

Clusulas dynamic, static e guided

Nesta seo comparamos o tempo de execuo do cdigo x para a execuo com 1thread e para a
execuo com 2 threads para as clusulas dynamic, static e guided.
Listing 9: Exemplo de uso da clusula dynamic
1
2
3

# include < stdio .h >


# include < omp .h >
# include < limits .h >

4
5
6
7

int main ( int argc , char * argv [])


{
int n , id , i ;

n =14;

9
10

# pragma omp parallel


{
# pragma omp for schedule ( dynamic )
for ( i = 0; i < n ; i ++) {
id = omp _g et _t hr ea d_ nu m () ;
printf ( " Thread = %d , i = % d \ n " ,id , i ) ;
for ( int foo = 0; foo < 1 e6 ; foo += 1) ;
}
}

11
12
13
14
15
16
17
18
19
20

Listing 10: Exemplo de uso da clusula dynamic


1

# include < stdio .h >

2
3

# include < omp .h >


# include < limits .h >

4
5
6
7

int main ( int argc , char * argv [])


{
int n , id , i ;

n =14;

9
10

# pragma omp parallel


{
# pragma omp for schedule ( static )
for ( i = 0; i < n ; i ++)
{
id = omp _g et _t hr ea d_ nu m () ;
printf ( " Thread = %d , i = % d \ n " ,id , i ) ;
for ( int foo = 0; foo < 1 e7 ; foo += 1) ;
}
}

11
12
13
14
15
16
17
18
19
20
21

Listing 11: Exemplo de uso da clusula dynamic


1
2
3

# include < stdio .h >


# include < omp .h >
# include < limits .h >

4
5
6
7

int main ( int argc , char * argv [])


{
int n , id , i ;

n =14;

9
10

# pragma omp parallel


{
# pragma omp for schedule ( guided )
for ( i = 0; i < n ; i ++)
{
id = omp _g et _t hr ea d_ nu m () ;
printf ( " Thread = %d , i = % d \ n " ,id , i ) ;
for ( int foo = 0; foo < 1 e6 ; foo += 1) ;
}
}

11
12
13
14
15
16
17
18
19
20
21

A clusula schedule descreve quantas iteraes do loop (trabalho) sero divida entre os ncleos
de processamento do conjunto total de trabalho. Diversos esquemas para realizar essa diviso de
trabalho so possveis. Na diviso de trabalho static as interaes de um loop so divididas em
pores de tamanho n e atribuidas quando possveis s threads. Na diviso de trabalho dynamic
as interaes do lao so dinamicamente agendadas para execuo nas threads. Assim que um
ncleo termina seu trabalho uma nova poro de trabalho escalonada para execuo. Apesar
das iteraes/trabalho serem atribudas dinamicamente o tamanho da poro de trabalho continua
fixa. Para mud-lo, usa-se a clusula guided que estende o comportamento do dynamic porm a
poro de trabalho agora ser recalculada considerando a quantidade de trabalho restante. O que
7

leva a um decaimento do tamanho da poro de trabalho.


Esses esquemas de escalonamento demandam computao e deve ser bem avaliados. A seguir
mostramos uma tabela com diversos tempos de execuo para avaliar o desempenho da execuo.
1 thread 2 threads
static
0m0.692s 0m0.367s
dynamic 0m0.071s 0m0.039s
guided
0m0.072s 0m0.038s
Tabela 1: Tempos de execuo entre diferentes usos das clusulas static, dynamic e guided
Os valores de tempo da Tabela 1 foram obtidos atravs da sada com comando unix time.
Eles nos mostram que no houve muito realce de desempenho entre as clusulas e isso pode ser
atribudo ao custo computacional uniforme das tarefas. Podemos notar tambm que a incluso
de mais uma thread reduz o tempo de execuo quase pela metade, porm h uma sobrecarga
adicional necessria de chaveamento das threads.