Você está na página 1de 5

TEMA DA CAPA

Paralelizao de Aplicaes com OpenMP


usado um modelo fork & join, em que temos inicialmente
uma nica thread (a master, ou principal), e depois temos
pontos da aplicao em que um grupo de threads criado
(fork, no incio de uma regio paralela), e pontos da
aplicao onde as threads so terminadas voltando a haver
apenas uma thread (join, no final de uma regio paralela),
como mostrado na Figura 1. Ou seja, as regies paralelas
identificam os blocos de cdigo onde o trabalho a ser
executado atribudo a vrias threads. A norma
do OpenMP prev a possibilidade de termos regies
paralelas aninhadas, em que cada thread de um grupo
de threads d origem a um novo grupo de threads. No
entanto, nem todas as implementaes suportam regies
paralelas (e neste caso, cada regio paralela aninhada ser
apenas executada por uma thread).

Introduo
O OpenMP uma norma/API para programao paralela em
sistemas de memria partilhada para as linguagens de
programao C, C++ e Fortran, desenvolvida e mantida
pelo OpenMP Architecture Review Board. Disponibiliza uma
alternativa simples e portvel a solues de mais baixo nvel
como POSIX Threads, e suportado por vrios
compiladores como o GCC ou ICC, e dever chegar em
breve ao Clang/LLVM.
O OpenMP distingue-se de outras solues para suporte a
paralelismo em memria partilhada pelo seu nvel de
abstraco, na medida em que o essencial das suas
funcionalidades obtido atravs de um conjunto de
directivas do compilador que especificam de forma
declarativa como que diferentes partes do cdigo podem
ser executadas em paralelo. Adicionalmente, um compilador
que no suporte OpenMP pode simplesmente ignorar estas
directivas,
continuando
a
aplicao
a
funcionar
correctamente (embora de forma sequencial). Contudo,
aplicaes mais complexas podem tambm necessitar de
chamadas a funes de mais baixo nvel (disponibilizados
pela API do OpenMP), e que tornam a compilao da
aplicao dependente do OpenMP.

Figura 1: modelo fork & join do OpenMP

Neste artigo pretende-se mostrar como podem explorar


paralelismo em memria partilhada recorrendo a um
pequeno conjunto de funcionalidades do OpenMP, de modo
a tirarem partido dos processadores com mltiplos ncleos
disponveis na generalidade dos PCs hoje em dia. Utilizar-se
- o C com linguagem de suporte neste artigo. Os programas
sero compilados usando o GCC 4.7.2.

O acesso s funcionalidades do OpenMP feito atravs de


trs tipos de mecanismos:
directivas de compilador
permitem criar regies paralelas, distribuir o trabalho a ser
realizado pelas vrias threads, controlar o acesso a dados
(especificando se so privados ou partilhados, por exemplo),
assim como gerir a sincronizao entre threads;

Conceitos Bsicos

funes

O OpenMP usa threads para obter paralelismo. Todas


as threads usam o mesmo espao de endereamento de
memria, sendo que os dados podem ser partilhados por
todas
as
threads,
ou
privados
de
cada thread (cada thread tem uma cpia da varivel qual
s ela acede). De notar que o OpenMP utiliza um modelo de
memria de consistncia relaxada, na medida em que
cada threads pode ter uma cpia local temporria (por
exemplo, em registos ou cache) cujo valor no necessita de
ser constantemente consistente com o valor global visto por
todas as outras threads (vrios mecanismos so
disponibilizados para que o programador possa forar a
consistncia de memria em determinados pontos da
aplicao).

permitem definir o nmero de threads a usar, obter


informaes sobre quantas threads esto a correr ou qual o
identificador da thread em que estamos, determinar se se
est numa zona paralela, gerir locks, aceder a funes de
tempo (relgio), entre outras coisas;
variveis de ambiente
permitem tambm definir o nmero de threads a usar,
controlar o escalonamento, definir o mximo aninhamento de
regies paralelas, etc.
Nas prximas seces veremos como alguns alguns destes
mecanismos podem ser usados, sendo dado especial foco
s directivas de compilador.

TEMA DA CAPA
PARALELIZAO DE APLICAES COM OPENMP
exemplo, podem simplesmente acrescentar a opo num_threads(3) no final do omp parallel (ficando
com #pragma omp parallel num_threads(3)) para indicar que
querem 3 threads.

Mecanismo do OpenMP
Nesta seco veremos como podemos usar as
funcionalidades do OpenMP. O exemplos disponibilizados
podem ser compilados usando a opo de compilao fopenmp do GCC (caso estejam a usar outro compilador,
devem recorrer documentao do mesmo para determinar
como activar as funcionalidades do OpenMP).

Distribuio de Trabalho
O exemplo anterior no particularmente til na maior parte
dos casos, na medida em que todas as threads executam o
mesmo trabalho de forma redundante. Em exemplos reais,
normalmente o que queremos que cada thread execute
uma parte diferente do trabalho. O OpenMP disponibiliza
diversos mecanismos para tal.

Criao de Threads
Na base do OpenMP est a directiva omp parallel. Esta
directiva cria uma regio paralela, i.e., indica que um
determinado bloco de cdigo ser executada por
vrias threads (caso o OpenMP esteja activo). Como
exemplo, considere o seguinte cdigo.

Uma forma simples de distribuir trabalho indicar que diferentes partes da regio paralela podem ser executadas em
paralelo, mas cada parte s dever ser executada por
uma thread. Para obter este comportamento podemos usar a
directiva omp sections. Considere o seguinte bloco de cdigo.

int main(void) {
#pragma omp parallel
{
printf("Hello world!\n");
}
return 0;
}

#pragma omp parallel


{
#pragma omp sections
{
#pragma omp section
{
task11(); // apenas uma thread
task12(); // apenas uma thread
}
#pragma omp section
task2(); // apenas uma thread
#pragma omp section
task3(); // apenas uma thread
}
taskAll(); // todas as threads
}

Caso execute este cdigo (compilado com a opo


do OpenMP activa), dever ver a mensagem Hello
world! aparecer no ecr tantas vezes quanto o nmero de
ncleos do CPU da sua mquina. Tendo em conta que no
h sincronizao entre as threads, possvel que o texto
impresso pelas vrias threads aparea misturado.
Aquilo que executado em paralelo delimitado pelo bloco
que se segue directiva omp parallel. Por exemplo, no
seguinte cdigo, temos mensagens que so impressas por
todas as threads, e mensagens que so impressas apenas
pela thread principal.

Neste bloco de cdigo teremos uma thread a executar tast11 e task12, outra thread a executar task2, outra thread a executar task3, e todas as threads a executar taskAll (visto que esta funo j se encontra fora do bloco omp sections). Ou seja, a directiva omp sections define
um conjunto de blocos de cdigo em que cada um deles
deve ser executado por apenas uma thread. Cada um dos
blocos desse conjunto identificado pela directiva omp section. Se houver mais blocos do que threads, uma mesma thread ir executar mais do que uma seco (reala-se,
contudo, que a diviso de trabalho entre as threads nem
sempre equilibrada, podendo uma thread ficar com a maioria das tarefas). No final do bloco omp sections existe mais
uma vez uma barreira implcita aplicada a todas as threads.

int main(void) {
#pragma omp parallel
{
printf("Hello world!\n"); // todas as threads
printf("All threads.\n"); // todas as threads
}
printf("Only master.\n"); // apenas thread
//principal
return 0;
}
De notar que a mensagem Only master., que apenas impressa pela thread principal, dever ser sempre a ltima
mensagem a ser mostrada, na medida em h uma barreira
implcita no final do bloco paralelo (a generalidade dos construtores tm barreiras implcitas no final, sendo que alguns
mecanismos suportam a opo nowait para evitar estas barreiras; consulte a documentao para mais detalhes), i.e.,
a thread principal no avana enquanto ainda houver threads a executar a regio paralela.

Uma outra forma muito til de distribuir trabalho entre threads dividindo as iteraes de um ciclo for. Para tal
temos a directiva omp for, usada no prximo exemplo.
double nums[10] = {1,2,3,4,5,6,7,8,9,10};
#pragma omp parallel for
for(int i = 0; i < 10; i++) {
nums[i] = sqrt(nums[i]);
}

Existem diversas formas de controlar explicitamente o nmero de threads que so criadas por uma regio paralela. Por

TEMA DA CAPA
PARALELIZAO DE APLICAES COM OPENMP
Este cdigo computa a raiz quadrada dos elementos de
um array. As iteraes no tm dependncias, pelo que podemos ter as vrias iteraes a serem executados em paralelo. precisamente isso que a directiva omp for est a fazer. De referir que estamos a juntar as directivas omp parallel e omp for numa s, na medida em que ambas se aplicam
ao mesmo bloco de cdigo.

// varivel que ir a
double sum = 0;
double nums[SIZE];
// ...
// inicializar nums
// ...
// criao de uma regio paralela
#pragma omp parallel
{
// declarada dentro da regio paralela, logo
privada thread
double sumt = 0;
// obter o id desta thread
int id = omp_get_thread_num();
// obter o nmero de threads
int nthreads = omp_get_num_threads();
for(int i = id; i < 100; i += nthreads) {
sumt += nums[i];
}
// regio crtica; apenas uma thread a executa
em cada momento
#pragma omp critical
sum += sumt;
}

Mas nem sempre as iteraes do ciclo so completamente


independentes, o que pode requerer a utilizao de funcionalidades mais complexas do OpenMP. Por exemplo, suponhamos que se pretende somar todos os elementos de
um array. Tal pode ser feito de forma sequencial com o seguinte cdigo.
double sum = 0;
double nums[SIZE];
// ...
// inicializar nums
// ...
for(int i = 0; i < SIZE; i++) {
sum += nums[i];
}

Neste exemplo temos novamente todas as threads a executar a regio paralela. Aqui a distribuio do trabalho feita
recorrendo API do OpenMP. Obtendo o identificador de
cada thread, e o nmero de threads a serem executados,
podemos usar as estruturas condicionais para que cada thread execute trabalho diferente. No caso, for(int i = id; i
< 100; i += nthreads) faz com que a thread id some os valores dos ndices i tal que i % nthreads == id (sendo nthreads o
nmero de threads a executar). Desta forma, temos as vrias
posies do array divididas, de forma cclica, por todas
as threads. A varivel sumt est declarada dentro da regio
paralela, pelo que a mesma local a cada thread. No final,
todas as threads adicionam a sua soma varivel sum, que
contm a soma total. Mais uma vez necessrio cuidado
com as race conditions, visto que todas as threads escrevem
na varivel sum. A directiva omp critical usada para evitar
problemas. Esta garante que apenas uma thread est a executar aquele bloco da regio paralela num determinado momento. De notar tambm que o acesso s funes
da API do OpenMP requer a utilizao do header omp.h.

Todas as iteraes escrevem na varivel sum, pelo que no


caso de executarmos vrias iteraes em paralelo, teremos
um race condition, que pode levar a resultados incorrectos.
Existem vrias maneiras de contornar este problema, nomeadamente atravs da utilizao de diferentes variveis para
cada thread, como veremos mais adiante. Contudo,
o OpenMP disponibiliza um mecanismo de alto nvel para
este tipo de situaes, em que feita uma operao de reduo de um array (i.e., os elementos do array so agrupados num s elemento, neste caso, a soma de todos os elementos). O mecanismo em causa o reduction(<op>:<var>).
Este mecanismo torna a varivel <var> privada a cada thread dentro da regio paralela (na generalidade dos
casos, variveis declaradas fora do bloco so partilhadas por
omisso), e no final realiza uma juno de todas as cpias
privadas da varivel aplicando a operao <op>. Para o nosso caso, queremos que a varivel sum seja privada a cada thread, e queremos que no final todas as cpias privadas
sejam somadas. Assim, a verso paralela pode ser obtida
como exemplificado no seguinte cdigo.

Penso que todos os leitores iro concordar que a utilizao


do omp for e do reduction permite um cdigo muito mais
simples, e a verso que se recomenda que seja utilizada.
Por norma, apenas se recomenda fazer este tipo de diviso
de trabalho manual quando no possvel faz-lo usando
apenas as directivas do compilador (o que pode ocorrer em
casos mais complexos).

double sum = 0;
double nums[SIZE];
// ...
// inicializar nums
// ...
#pragma omp parallel for reduction(+:sum)
for(int i = 0; i < SIZE; i++) {
sum += nums[i];
}

Sincronizao de Threads
No exemplo anterior j vimos como podemos sincronizar a
execuo de threads atravs do omp critical. Existem mais
alguns mecanismos que podem ser usados para sincronizao:

Existem formas mais rudimentares de distribuir o trabalho


por threads, usando a API do OpenMP. Pegando no exemplo anterior, a sua verso paralela pode ser tambm obtida
atravs do cdigo que se segue.

omp atomic

TEMA DA CAPA
PARALELIZAO DE APLICAES COM OPENMP
uma verso mais bsica (mas tambm mais eficiente)
do omp critical, que permite tornar as actualizaes de variveis atmicas (no exemplo anterior seria mais eficiente utilizar o omp atomic do que o omp critical);

}
omp_destroy_lock(&sumlock);
Mais uma vez, sempre que possvel, aconselha-se a utilizao
dos
mecanismos
abstractos
disponibilizadas OpenMP (critical, atomic, barrier, etc.) em vez da utilizao das funes de mais baixo nvel disponibilizados pela API.

omp master
indica que um bloco de cdigo s ser executado pela thread principal (de referir que, ao contrrio da generalidade dos mecanismos do OpenMP, no existe uma barreira
implcita no final destes blocos);

Para terminar, convm referir que os mecanismos de sincronizao devem ser evitados, pois os mesmos implicam overheads, muitas vezes ao ponto impedirem que a
paralelizao produza qualquer ganho de desempenho. Em
geral recomendvel que se tente reorganizar o algoritmo
de modo a evitar a necessidade destes mecanismos.

omp barrier
permite definir barreiras explcitas, onde as threads de uma
regio bloqueiam at que todas elas tenham atingido aquele
ponto de execuo;

Tarefas Paralelas

omp flush

Uma outra forma de definir blocos de cdigo que podem ser


executados em paralelo (por uma thread cada) usando a
directiva omp task. Esta directiva permite definir tarefas cuja
a execuo pode ser feita de forma assncrona. A directiva
de sincronizao omp taskwait deve depois ser usada para
bloquear a execuo da aplicao at que a tarefa tenha
sido concluda (quando necessrio). Por exemplo, o seguinte
cdigo implementa o clculo de um nmero de Fibonacci em
paralelo.

usado para garantir que todas as threads tm uma viso


consistente da memria naquele ponto (como indicado anteriormente, o OpenMP usa um modelo de memria de consistncia relaxada);
omp ordered
permite definir que um determinado bloco dentro de um ciclo
que est a correr em paralelo dever ser executado na mesma ordem em que seria executado se estivesse a correr de
forma sequencial.

int fibonacci(int n) {
int fib1, fib2, result;
if(n == 0 || n == 1) {
result = n;
}
else {
// cria uma tarefa assncrona
#pragma omp task shared(fib1)
fib1 = fibonacci(n - 1);
// cria uma tarefa assncrona
#pragma omp task shared(fib2)
fib2 = fibonacci(n - 2);
// espera pelas tarefas assncronas lanadas
#pragma omp taskwait
result = fib1 + fib2;
}
return result;
}

Adicionalmente, podemos ainda sincronizar a execuo


de
threads
atravs
das
funes
de
gesto
de locks disponibilizados pela API do OpenMP, que incluem
o
omp_init_lock
(inicializao
de
um lock), omp_set_lock (adquire um lock, esperando at que
tal
seja
possvel),
omp_unset_lock
(liberta
um lock), omp_test_lock (tenta adquirir um lock, mas no
bloqueia
caso
no
seja
possvel),
e omp_destroy_lock (destri o lock). Por exemplo, o omp
critical usado no exemplo anterior podia ser substitudo pela
utilizao de locks, como mostrado no prximo exemplo.
double sum = 0;
double nums[SIZE];
// ...
// inicializar nums
// ...
omp_lock_t sumlock;
omp_init_lock(&sumlock);
#pragma omp parallel
{
// declarada dentro da regio paralela, logo
//privada thread
double sumt = 0;
// obter o id desta thread
int id = omp_get_thread_num();
// obter o nmero de threads
int nthreads = omp_get_num_threads();
for(int i = id; i < SIZE; i += nthreads)
{
sumt += nums[i];
}
omp_set_lock(&sumlock);
sum += sumt;
omp_unset_lock(&sumlock);

Neste cdigo indicado que as chamadas recursivas funo fibonacci podem ser executadas de forma assncrona.
Antes de precisarmos de utilizar o resultados dessas chamadas recursivas, utilizamos o omp taskwait para esperarmos
pela concluso das tarefas.
Neste exemplo podemos tambm ver mais uma vez a utilizao de mecanismos de controlo de escopo/partilha (j anteriormente tnhamos visto que o reduction podiam ser usado
para tornar variveis privadas s threads). Ao contrrio da
generalidade dos construtores, no omp task as variveis
declaradas anteriormente so privadas task por omisso.
Assim, se precisarmos de aceder ao seu valor mais tarde,
como no caso das variveis fib1 e fib2, necessrio tornar
as variveis partilhadas. Tal feito atravs das op-

TEMA DA CAPA
PARALELIZAO DE APLICAES COM OPENMP
es shared(fib1) e shared(fib2).

Referncias

Concluso

Website do OpenMP: http://www.openmp.org/

Neste artigo procurou-se mostrar como podem paralelizar


aplicaes de forma simples usando o OpenMP. Contudo,
este disponibiliza apenas uma introduo ao tema, sendo
que as funcionalidades do OpenMP vo muito para alm das
aqui abordadas, nomeadamente no que diz respeito ao escalonamento de tarefas, ou s formas de controlo de escopo/
partilha de variveis. Sugere-se ao leitor interessado que
consulte a lista de referncias disponibilizada no final caso
deseje aprofundar os seus conhecimentos.

Especificao do OpenMP:
documents/OpenMP4.0.0.pdf
Tutorial de
openMP/

OpenMP:

http://www.openmp.org/mp-

https://computing.llnl.gov/tutorials/

Tuturial prtico de OpenMP:


documents/omp-hands-on-SC08.pdf

Para alm das linguagens


suportadas
oficialmente
(C, C++ e Fortran),
existem soluo similares para outras linguagens como o Java
Neste artigo tambm no houve grande preocupao com o
desempenho da aplicao. Tirar partido da paralelizao
requer cuidados adicionais no que diz respeito performance, nomeadamente para se fazer um bom uso da hierarquia
de memria (por exemplo, para se evitarem problemas
de false sharing), para se conseguir um bom balanceamento
de carga, ou para evitar overheads excessivos com sincronizao (como referido anteriormente).
Apesar do OpenMP ser destinado a sistemas memria partilhada, pode ser usado em conjunto com solues de memria distribuda (como o MPI). Para alm das linguagens suportadas oficialmente (C, C++ e Fortran), existem soluo
similares para outras linguagens como o Java, por exemplo.

AUTOR
Escrito Por Rui Carlos Gonalves

10

http://openmp.org/mp-