Programação Paralela e Distribuída (DCC/UFRJ) Aula 7: Programação com memória compartilhada usando OpenMP 1, 8, 10 e 15 de abril de 2015
OpenMP (Open MultiProcessing) Projetado para sistemas de memória compartilhada usa instruções/diretivas especiais para o pre-processador (pragma) permite ações que não fazem parte da especificação básica da linguagem pragmas são ignorados caso não exista suporte do compilador além das diretivas, OpenMP inclui funções e macros, por isso incluir omp.h
ver exemplo de código: omp hello.c
Pragmas do OpenMP pragma omp parallel diretiva mais básica o bloco de código seguinte será executado em paralelo por um número de threads definida pelo sistema de execução há uma barreira impĺıcita quando o bloco de código que segue a diretiva é concluído são criadas (nthreads 1) threads após a diretiva (a última thread é a thread principal)
Cláusula (clause) exemplo texto que modifica uma diretiva a cláusula num threads pode ser adicionada para a diretiva parallel permite especificar o número de threads que deverão executar o bloco seguinte pragma omp parallel num threads ( thread count )
Observações o tamanho default de pragmas é uma linha, para saltar linha usar barra invertida (\) o sistema pode definir limitações no número de threads que um programa pode criar na terminologia OpenMP, a coleção de threads que executa um bloco paralelo (a thread original e as novas) é chamado team a thread original é chamada master as threads adicionais são chamadas slaves
Caso não existe suporte do compilador
Caso não existe suporte do compilador ver exemplo de código: omp hello err chk.c
Exemplo: cálculo da integral usando a soma de trapézios
Exemplo: cálculo da integral usando a soma de trapézios
Algoritmo sequencial
Versao OpenMP (1) Identifica-se dois tipos de tarefa: 1 computação das áreas dos trapézios isoladamente 2 adição de todas as áreas calculadas assume-se que há muito mais trapézios do que núcleos de CPU agrupa-se as tarefas designando blocos (trapézios) consecutivos para cada thread uma thread por núcleo
Agrupamento de tarefas
Seção crítica
Exercício Inclua tomadas de tempo nos códigos implementados nos arquivos trap.c e omp trap1.c para medir o tempo gasto para o cálculo da integral Execute as duas aplicações e compare os tempos de execução entre elas variando o número de trapézios para um intervalo fixo da função (por ex., entre -100 e 100) A versão OpenMP ficou mais rápida? A partir de qual número de trapézios?
Exercício: estimando o valor de π (Pi) Série para estimar o valor de π Exercício 1 implemente uma versão paralela para estimar o valor de π usando OpenMP (número de elementos (N) e de threads (M) na linha de comando) 2 verifique se a solução se aproxima da constante M PI (de math.h) 3 verifique qual solução ficou mais eficiente: PThreads ou OpenMP
Exercício pra casa :-) Procure na Internet mais informações sobre o OpenMP Implemente outras aplicações usando o OpenMP
A cláusula de redução
Usando o retorno da função...voltando ao problema do cálculo da integral usando a regra dos trapézios...
Usando o retorno da função Qual é o problema dessa solução?
Usando o retorno da função..força as threads a executarem sequencialmente
Usando o retorno da função Alternativa: criar uma variável local dentro do bloco paralelo e mover a seção crítica para fora da chamada da função
Exercício Veja a solução proposta no arquivo omp trap2a.c Inclua tomadas de tempo nesse código para medir o tempo de execução do cálculo da integral Execute as duas aplicações (omp trap1.c e omp trap2a.c) e compare os tempos de execução entre elas variando o número de trapézios para um intervalo fixo da função (por ex., entre -100 e 100)
Operadores de redução um operador de redução é um operador binário (como adição e multiplicação) uma redução é uma computação que aplica o mesmo operador de redução várias vezes sobre uma sequência de operandos para obter um valor de saída todos os resultados intermediários da operação devem ser armazenados na mesma variável: variável de redução exemplo de redução com o operador + int soma=0; for(i=0;i<n;i++) soma+=a[i];
Exemplo de uso de um operador de redução em OpenMP em OpenMP é possível especificar que o resultado de uma redução é uma variável de redução
Exemplo de uso de um operador de redução em OpenMP o OpenMP cria uma variável privada para cada thread e uma seção crítica para combinar os valores das variáveis privadas na variável de redução (quando o bloco parallel termina) atenção para operações com float e double (operações não associativas): (a + b) + c pode ser diferente de a + (b + c) as variáveis privadas são inicializadas com o valor identidade do operador (ex., 0 para adição e 1 para multiplicação)
Exercício Veja a solução proposta no arquivo omp trap2b.c Inclua tomadas de tempo nesse código para medir o tempo de execução do cálculo da integral Execute as três aplicações (omp trap1.c, omp trap2a.c e omp trap2b.c) e compare os tempos de execução entre elas variando o número de trapézios para um intervalo fixo da função (por ex., entre -100 e 100)
Diretiva parallel for
Diretiva parallel for similar à diretiva parallel, cria um conjunto de threads para executar o bloco de código seguinte a diferença é que esse bloco deve ser um loop com for divide as iterações do loop entre as threads
Exemplo de uso da diretiva parallel for
Formas permitidas para parallel for
Ressalvas o número de iterações deve ser determinado (ex., não funciona para loops infinitos ou com breaks internos) a variável index deve ser inteiro ou ponteiro (ex., não pode ser float) as expressões start, end e incr devem ter um tipo compatível (ex., se o índice é um ponteiro, então incr deve ser do tipo inteiro) e não devem ser modificadas durante a execução do loop a variável index só pode ser modificada pela expressão de incremento da sentença for
Exercício Veja a solução proposta no arquivo omp trap3.c Inclua tomadas de tempo nesse código para medir o tempo de execução do cálculo da integral Execute as duas aplicações (omp trap3.c e omp trap2b.c) e compare os tempos de execução entre elas variando o número de trapézios para um intervalo fixo da função (por ex., entre -100 e 100)
Outra ressalva: dependência de dados
Exercício Veja o código no arquivo omp fibo.c Execute o programa variando o número de threads e de elementos
Estimando o valor de Pi: loop sequencial
Estimando o valor de Pi: loop paralelo com parallel for
Escopo privado a cláusula private pode ser usada para alterar o escopo de variáveis compartilhadas (ex., definidas antes do bloco paralelo) para o escopo privado uma cópia da variável é feita para cada thread atenção: essa cópia local não é inicializada! (veja exemplo de uso no arquivo omp private.c)
Estimando o valor de Pi: solução correta
Exercício Inclua tomadas de tempo no código omp pi.c para medir o tempo de execução do loop principal Compare o desempenho dessa implementação com a solução que você implementou para o mesmo problema na aula anterior Qual solução ficou mais rápida?
Exercício Multiplicação de matrizes Implemente um algoritmo paralelo para multiplicar duas matrizes quadradas NxN usando OpenMP Avalie o ganho de desempenho da sua aplicação para valores de N acima de 2000, variando o número de threads de 1 ao número de processadores da sua máquina
Odd-Even sort sorted = false; while not sorted sorted = true; // odd-even for ( x = 1; x < list.length-1; x += 2) if list[x] > list[x+1] swap list[x] and list[x+1] sorted = false; // even-odd for ( x = 0; x < list.length-1; x += 2) if list[x] > list[x+1] swap list[x] and list[x+1] sorted = false;
Exemplo de execução do algoritmo
Exercício Odd-Even sort Implemente uma versão paralela para o algoritmo de ordenação odd-even usando OpenMP Avalie o ganho de desempenho da sua aplicação variando o número de elementos do vetor e o número de threads disparadas
Escalonamentos de loops
Escalonamentos de loops
Cláusula schedule
Cláusula schedule(static,?)
Cláusula schedule[type, chunksize] Type pode ser: static: as iterações são atribuídas para as threads antes do loop ser executado dynamic ou guided: as iterações são atribuídas para as threads durante a execução do loop auto: o compilador e/ou o sistema de execução determina o escalonamento runtime: o escalonamento é determinado em tempo de execução Chunksize deve ser um inteiro positivo
Cláusula schedule (dynamic,?) As iterações também são particionadas em chunks consecutivos de iterações Cada thread executa um chunk e quando termina requisita outro ao sistema de execução Esse processo se repete até todas as iterações serem executadas Quando chunksize é omitido, seu valor é 1
Cláusula schedule (guided,?) Cada thread executa um chunk de iterações e quando termina requisita outro Entretanto os tamanhos dos chunks decrecem Quando chunksize não é especificado, o tamanho dos chunks decrecem até 1 Se chunksize é especificado, ele descrece até chunksize (o último pode ser menor)
Exemplo: cláusula schedule (guided)
Cláusula schedule (runtime) O sistema usa a variável de ambiente OMP SCHEDULE para determinar em tempo de execução qual escalonamento usar A variável OMP SCHEDULE pode assumir os valores static, dynamic ou guided exemplo export OMP_SCHEDULE="static,1"
Barreira expĺıcita # pragma omp barrier Diretiva barrier Quando é necessário usar uma barreira expĺıcita para um grupo (team) de threads, OpenMP oferece o pragma barrier Depois que todas as threads alcançam a barreira, todas podem prosseguir
Seção atômica # pragma omp atomic Diretiva atomic Diferente da diretiva critical, só pode ser usada para proteger seções críticas que consistem de uma única sentaça C (para esses casos, pode ser mais eficiente que critical)
Seção crítica nomeada # pragma omp critical(name) OpenMP provê a opção de adicionar um nome par aa diretiva critical Permite que dois blocos protegidos com a diretiva critical com nomes diferentes sejam executados ao mesmo tempo
Locks Consiste de uma estrutura de dados e funções que permitem ao programador explicitar exclusão mútua
Locks
Exemplo de uso de locks omp_init_lock(&q_p->lock);...... omp_destroy_lock(&q_p->lock);
Exercício Números primos Escreva um programa multithreading (usando OpenMP) para encontrar os números primos de 1 a N. Utilize a rotina abaixo para determinar se um dado número n é primo (retorna 1) ou não (retorna 0). Experimente/compare as diferentes cláusulas de escalonamento da diretiva parallel for int ehprimo(long unsigned int n) { int i; if(n<=1) return 0; if(n==2) return 1; if(n%2==0) return 0; for(i=3; i< sqrt(n)+1; i+=2) { if(n%i==0) return 0; } return 1; }
Referências bibliográficas 1 An Introduction to Parallel Programming, Peter Pacheco, Morgan Kaufmann, 2011.