30 ERAD 2018- Porto Alegre, 4 a 6 de abril de 2018
O OpenMP utiliza o recurso de pragma, reconhecidos apenas por compiladores compatíveis. Com ospragma, os programadores indicam ao compilador as opções de pa-ralelismo e de controle, e o compilador fica responsável por “embutir” o código necessário para a realização dessas tarefas. Além dospragma, um conjunto de funções auxiliam o programador a desenvolver seu código.
Em Fortran os pragma OpenMP são identificados por “!$”, e servem para defi-nir o início de uma diretiva OpenMP, neste trabalho serão mostrados algumas diretivas consideradas básicas, explicando seu funcionamento.
Para compilar um programa fazendo uso de OpenMP, é necessário utilizar a flag identificando o uso da API, que varia de acordo com o compilador utilizado. Para o compilador gfortran, a flag que indica o uso de OMP é “-fopenmp”, exemplificado na Figura 4.2.
Figura 4.2: Exemplo de compilação utlilizando OpenMP com o compilador GFortran..
Tem-se na Figura 4.2a o esquema geral de compilação de um programa utilizando OpenMP, os únicos argumentos obrigatórios são a flag que indica o uso de OpenMP e o nome do código fonte. O uso de ‘-o’ é opcional e serve para alterar o nome do arquivo de saída. A Figura 4.2b mostra um exemplo de uso comum do compilador com a flag OpenMP de compilação ativada. Como apresentado na figura 4.2c, pode-se incluir outros parâmetros caso desejado. A execução é igual a qualquer outro programa.
4.3.1. Funções básicas do OpenMP
OpenMP possui quatro funções básicas que são utilizadas para controle de threads, são elas:
• OMP_GET_NUM_THREADS(): Retorna o número de threads utilizadas em uma seção paralela;
• OMP_GET_MAX_THREADS(): Retorna o número de threads disponível;
• OMP_SET_NUM_THREADS(nthreads): Define o número de threads a ser usado pelas seções paralelas,
• OMP_GET_THREAD_NUM(): Retorna o número identificador(ID) da thread. as IDs variam de 0(thread master) até OMP_GET_NUM_THREADS() -1.
A Figura 4.3 ilustra o funcionamento das funções descritas acima. É importante notar que as funções OMP_GET_THREAD_NUM(), OMP_GET_NUM_THREADS() e OMP_GET_MAX_THREADS() precisam ser declaradas como EXTERNAL (linha 4).
Minicursos 31
Figura 4.3: Exemplo de utilização da funções básicas..
Na linha 5 da Figura 4.3a, é utilizada a função OMP_GET_MAX_THREADS(), que como podemos ver na figura 4.3b, tem como resultado o valor oito, significando que o processador utilizado possui oito threads. Em seguida (linha 7 da figura 4.3a) é definido o máximo de threads a serem usadas como cinco, assim, a seção paralela a seguir será executada por cinco threads, com IDs variando de zero a quatro, como mostrado na figura 4.3b. Na linha 11 da figura 4.3a, é selecionado o processo de ID zero para calcular e mostrar na tela o número de threads utilizadas.
4.3.2. Blocos de código para execução em paralelo
O objetivo principal do OpenMP é realizar a execução em paralelo de programas.
Isso pode ser realizado com o par!$OMP PARALLEL e !$OMP END PARALLEL, que marca, respectivamente, o início e o fim de um bloco a ser processado em paralelo.
É importante definir quais variáveis serão privadas (PRIVATE) ou compartilhadas (SHA-RED) entre as threads.
As variáveis privadas possuem valor indefinido ao iniciar o processamento da se-ção paralela, onde é criada uma cópia das mesmas para cada thread. Ao terminar a sese-ção paralela, a variável privada não mantém o valor da seção paralela, pois como possui um valor único para cada thread não é possível definir o valor que deveria ser atribuído. Há também outros dois tipos de variáveis privadas, são eles FIRSTPRIVATE e LASTPRI-VATE. FIRSTPRIVATE inicia a seção paralela com o valor que possuía na sequencial,
32 ERAD 2018- Porto Alegre, 4 a 6 de abril de 2018 enquanto o LASTPRIVATE mantém ao sair da seção paralela o valor que teria caso a mesma fosse executada de forma sequencial, como por exemplo, a última iteração de um laço de repetição. A diferença entre os tipos PRIVATE e FIRSTPRIVATE pode ser notada na linha 13 da figura 4.4a e na linha correspondente da figura 4.4b.
Figura 4.4: Exemplo do uso de variáveis PRIVATE e FIRSTPRIVATE.
Variáveis do tipo PRIVATE iniciam a seção paralela sem um valor definido, como pode ser visto na Figura 4.4b, onde tem seu valor alterado para 10, e o mesmo é perdido ao sair da seção. No entanto, a variável FIRSTPRIVATE inicia o processamento paralelo com o valor que possuía anteriormente, porém o mesmo é perdido ao terminar a seção.
Quando for necessário que apenas uma thread execute um deteminado código, pode-se utilizar um dos seguintespragma:
• !$OMP MASTER e !$OMP END MASTER;
• !$OMP SINGLE e !$OMP END SINGLE.
Com uso do par depragma!$OMP MASTERe!$OMP END MASTERé defi-nida uma seção a ser executada apenas pela thread master, ou seja, a que possui ID igual a zero( OMP_GET_THREAD_NUM()==0). Um exemplo de seu uso pode ser observado nas linhas 11 e 14 da figura 4.3a.
Ao ser utilizado o par de pragma!$OMP SINGLEe!$OMP END SINGLE, o funcionamento e sintaxe é semelhante a!$OMP MASTER, porém o trecho de código é executado apenas pela primeira thread a alcançar a seção, independentemente de sua ID.
4.3.3. Divisão do processamento entre as threads
A divisão do processamento a ser realizado pelas threads pode ser controlada usando um dos seguintespragma:
Minicursos 33
• !$OMP DO e !$OMP END DO;
• !$OMP SECTIONS e !$OMP END SECTIONS.
Pode ser usado o par !$OMP DOe !$OMP END DOpara delimitar um trecho de código, composto da iteração do loop DO. Os índices do laço são divididos entre as threads. É importante notar que a variável de controle de um loop (step), é definida como PRIVATE por padrão, não sendo necessário especificar seu escopo de compartilhamento.
Um exemplo de uso do!$OMP DOe !$OMP END DO pode ser observada das linhas 12 a 17 da figura 4.5a.
Figura 4.5: Exemplo do uso de !$OMP DO e !$OMP CRITICAL.
Pode ser usado a união dospragma !$OMP PARALLEL e!$OMP DO(e seus delimitadores finais) para declarar de uma só vez seção paralela e o laço de repetição na mesma linha.
Com o objetivo de criar uma seção crítica para as threads em execução num ponto do código, pode-se utilizar o par de diretivas!$OMP CRITICAL <nome> e !$OMP END CRITICAL <nome>para indicar que as threads devem executar a seção uma de cada vez, ou seja, nunca haverá duas threads executando ao mesmo tempo aquele código.
O campo <nome> não é necessário, porém é recomendado seu uso, uma vez que, caso di-ferentes seções críticas possuírem o mesmo nome, serão tratadas como uma só, o mesmo se aplica caso o nome não seja definido. Um exemplo onde uma seção crítica é neces-sária é onde o resultado de uma operação é acumulado em uma variável compartilhada, como pode ser visto na linha 19 da figura 4.5a, evitando que haja conflito entre diferentes threads atualizando a mesma variável simultaneamente.
O par de diretivas !$OMP SECTIONS e !$OMP END SECTIONSé utilizado para definir tarefas diferentes a serem executadas por cada thread. O código a ser excecu-tado em cada thread deve ser precedido por!$OMP SECTION sendo finalizado por outra diretiva igual ou por um !$OMP END SECTIONS. Podem ser especificadas quantas seções for desejado, sendo cada uma delas executada numa thread diferente.
34 ERAD 2018- Porto Alegre, 4 a 6 de abril de 2018