INTRODUÇÃO AO OPENMP PROF. ANDRÉ LEON S. GRADVOHL, DR. [email protected] ARQUITETURAS PARALELAS Arquiteturas paralelas: • Single Instruction Multiple Data • Máquinas Vetoriais • Multiple Instruction Multiple Data • Memória distribuída • Memória compartilhada 2 Onde o OpenMP se encaixa MODELOS DE PROGRAMAÇÃO PARALELA Modelos de Programação Paralela • Multiprocessamento • Fork/Join • Passagem de Mensagens • Exemplo: PVM, MPI • Multithread • Exemplo: OpenMP, POSIX-Threads OpenMP (Open MultiProcessing): 3 • Interface de programação que suporta multiprocessamento em ambientes de memória compartilhada. INTRODUÇÃO AO OPENMP Estrutura de um programa OpenMP: • Em Fortran: 4 PROGRAM HELLO INTEGER VAR1, VAR2, VAR3 *** Código serial *** Início da seção paralela. “Fork” um grupo de “threads”. !$OMP PARALLEL PRIVATE(VAR1, VAR2) SHARED(VAR3) *** Seção paralela executada por todas as “threads” *** Todas as “threads” efetuam um “join” a thread mestre e finalizam !$OMP END PARALLEL *** Código serial END INTRODUÇÃO AO OPENMP Estrutura de um programa OpenMP: • Em C: 5 #include <omp.h> int main () { int var1, var2, var3; *** Código serial *** Início da seção paralela. “Fork” um grupo de “threads”. #pragma omp parallel private(var1, var2) shared(var3) { *** Seção paralela executada por todas as “threads” *** Todas as “threads” efetuam um “join” a thread mestre e finalizam } *** Código serial } INTRODUÇÃO AO OPENMP Observações Fork/Join: • Quando uma thread chega a uma definição de região paralela, ela cria um conjunto de threads e passa a ser a thread mestre. A thread mestre faz parte do conjunto de threads e possui o número de identificação “0”. • A partir do início da região paralela, o código é duplicado e todas as threads executarão esse código. • Existe um ponto de sincronização (“barreira”) no final da região paralela, sincronizando o fim de execução de cada thread. Somente a thread mestre continua desse ponto. 6 B A R R INTRODUÇÃO AO OPENMP Observações 7 • O número de threads • Em uma execução com o OpenMP, o número de “threads” é determinado pelos seguintes fatores, em ordem de precedência: 1. Utilização da função omp_set_num_threads() no código Fortran ou C/C++; 2. Definindo a variável de ambiente OMP_NUM_THREADS, antes da execução; 3. Implementação padrão do ambiente: número de processadores em um nó. • Restrições • Não é permitido caminhar para dentro ou fora (”branch”) de uma estrutura de blocos definida por uma diretiva OpenMP e somente um IF é permitido. EXEMPLO #include <stdio.h> #include <omp.h> Faz o fork dos threads e mantém suas próprias cópias de variáveis. Obtém o número do Thread. Região Paralela int main () Se tid == 0, então é o thread { int nthreads, tid; mestre. Obtém #pragma omp parallel private(nthreads, tid) a quantidade de { threads. tid = omp_get_thread_num(); printf(“Ola Mundo do thread = %d\n”, tid); if (tid == 0) { nthreads = omp_get_num_threads(); printf(“Numero de threads = %d\n”, nthreads); } } } CRIAÇÃO DE THREADS #include <stdio.h> #include <omp.h> int main() Criação de 4 threads. { double A[1000]; #pragma omp parallel num_threads(4) { Obtém o número do thread. int ID = omp_get_thread_num(); pooh(ID,A); } return 0; 9 } CRIAÇÃO DE THREADS – EXERCÍCIOS Matematicamente sabe-se que: 1 4 0 (1 x 2 ) Portanto, é possível aproximar esta integral como um somatório: n F ( x )x i 0 i 10 Onde cada retângulo tem largura x e altura F(xi) no meio do intervalo i. CRIAÇÃO DE THREADS – EXERCÍCIOS Solução Serial: static long num_steps = 100000; double step; int main () { int i; double x, pi, sum = 0.0; step = 1.0/(double) num_steps; for (i=0;i< num_steps; i++){ x = (i+0.5)*step; sum = sum + 4.0/(1.0+x*x); } pi = step * sum; 11 } CRIAÇÃO DE THREADS – EXERCÍCIOS 12 Solução Paralela static long num_steps = 100000000; double step; int main () { int i,j; double pi, full_sum = 0.0; double sum[MAX_THREADS]; step = 1.0/(double) num_steps; CRIAÇÃO DE THREADS – EXERCÍCIOS omp_set_num_threads(MAX_THREADS); full_sum=0.0; #pragma omp parallel { int i; int id = omp_get_thread_num(); int numthreads = omp_get_num_threads(); double x; Região Paralela sum[id] = 0.0; if (id == 0) printf(" num_threads = %d",numthreads); for (i=id;i< num_steps; i+=numthreads){ x = (i+0.5)*step; sum[id] = sum[id] + 4.0/(1.0+x*x); } 13 } CRIAÇÃO DE THREADS – EXERCÍCIOS for(full_sum = 0.0, i=0;i< MAX_THREADS;i++) full_sum += sum[i]; pi = step * full_sum; } 14 Agrupamento dos resultados. (Redução) Define uma região paralela DIRETIVA DO/FOR Com isso, o programador não precisa de preocupar com a divisão da carga de trabalho entre os threads Exemplo: #pragma omp parallel { #pragma omp for for (I=0;I<N;I++) Faca_algo(I); } O comando for será dividido igualmente entre os threads. Detalhe:a variável I é privativa. 15 A diretiva DO/for especifica que as iterações de um laço sejam distribuídas e executadas em paralelo pelo grupo de threads. A região paralela tem que ter sido identificada antes. DIRETIVA DO/FOR Cuidado com o uso de outras variáveis dento de um “laço paralelo”!! As variáveis do laço são privativas de cada thread. Exemplo: #pragma omp parallel for for (I=0;I<N;I++) Faca_algo(I); Exemplo: int i, j, A[MAX]; j = 5; for (i=0;i< MAX; i++) { j = j+ 2*i; A[i] = big(j); } 16 Alternativamente, podese utilizar a seguinte sintaxe: DIRETIVA DO/FOR REDUÇÃO Como resolver o caso ao lado? double ave=0.0, A[MAX]; int i; for (i=0;i< MAX; i++) { ave + = A[i]; } ave = ave/MAX; 17 Resposta: usando uma redução ! double ave=0.0, A[MAX]; int i; #pragma omp parallel for reduction (+:ave) for (i=0;i< MAX; i++) { ave + = A[i]; } ave = ave/MAX; DIRETIVA DO/FOR – REDUÇÃO – MAIS DETALHES Dentro de uma região paralela: 18 • É feita uma cópia local de cada variável e inicializada com um valor que depende do tipo de operação, e. g., na operação de + o valor inicial é zero. • As cópias locais são reduzidas (somadas, multiplicadas etc) em um único valor que, posteriormente, são combinados em uma única variável comum. Como fica o programa para calcular o com a diretiva for e a redução? DIRETIVA DO/FOR – REDUÇÃO – MAIS DETALHES 19 Solução Paralela static long num_steps = 100000000; double step; int main () { int i,j; double x, pi, sum = 0.0; double sum[MAX_THREADS]; step = 1.0/(double) num_steps; DIRETIVA DO/FOR – REDUÇÃO – MAIS DETALHES omp_set_num_threads(MAX_THREADS); sum=0.0; #pragma omp parallel for reduction (+:sum) for (i=1;i< num_steps; i++){ Região Paralela com redução x = (i-0.5)*step; sum= sum + 4.0/(1.0+x*x); } pi = step * sum; 20 } ATRIBUTOS DE VARIÁVEIS As variáveis no OpenMP podem ser compartilhadas ou não entre os threads. Esse controle é feito através de alguns atributos. São eles: 21 private: Declara que as variáveis listadas serão de uso específico de cada “thread”. Essas variáveis não são iniciadas. shared (default) Declara que as variáveis listadas compartilharão o seu conteúdo com todas as threads de um grupo. As variáveis existem em apenas um endereço de memória, que pode ser lido e escrito por todas as threads do grupo. ATRIBUTOS DE VARIÁVEIS 22 firstprivate: Define uma lista de variáveis com o atributo PRIVATE, mas sendo inicializadas automaticamente, de acordo com o valor que possuíam no thread antes de uma região paralela. lastprivate: Define uma lista de variáveis com o atributo PRIVATE e copia o valor da última iteração de um laço da última thread que finalizou. default: Permite que o programador defina o atributo “default” para as variáveis em uma região paralela (PRIVATE, SHARED ou NONE). ATRIBUTOS DE VARIÁVEIS EXEMPLOS void useless() { Cada thread possui sua própria cópia da variável tmp com o valor inicial zero. int tmp = 0; #pragma omp parallel for firstprivate(tmp) for (int j = 0; j < 1000; ++j) tmp += j; printf(“%d\n”, tmp); 23 } ATRIBUTOS DE VARIÁVEIS EXEMPLOS void useless() { int tmp = 0; #pragma omp parallel for firstprivate(tmp) lastprivate(tmp) for (int j = 0; j < 1000; ++j) tmp += j; } A variável tmp termina com o valor do calculado pelo último thread a terminar. 24 printf(“%d\n”, tmp); SEÇÕES 1. A diretiva sections define a seção do código sequencial onde será definida as seções independentes, através da diretiva section; 2. Cada section é executada por uma thread do grupo; 3. Existe um ponto de sincronização implícita no final da diretiva section, a menos que se especifique o atributo nowait; 4. Se existirem mais threads do que seções, o OpenMP decidirá, quais threads executarão os blocos de section, e quais, não executarão. 25 A diretiva sections divide o trabalho de forma não iterativa em seções separadas, aonde cada seção será executada por uma “thread” do grupo. Representa a implementação de paralelismo funcional, ou seja, por código. Algumas observações: SEÇÕES- EXEMPLO #include <omp.h> #define N 1000 Definição de uma área de seções. int main () { int i, n=N; float a[N], b[N], c[N]; for (i=0; i < N; i++) a[i] = b[i] = i * 1.0; #pragma omp parallel shared(a,b,c,n) private(i) { #pragma omp sections nowait { #pragma omp section Primeira seção for (i=0; i < n/2; i++) c[i] = a[i] + b[i]; #pragma omp section for (i=n/2; i < n; i++) Segunda seção c[i] = a[i] + b[i]; } /* fim parallel */ } 26 } /* fim seções*/ UNICIDADE A diretiva single determina que o código identificado seja executado por somente uma thread do grupo. Os threads do grupo que não executam a diretiva single, esperam o fim do processamento da thread que executa a diretiva, a menos que se especifique o atributo nowait. 27 A diretiva ordered determina que as iterações do laço na região paralela, sejam executados na ordem sequêncial. UNICIDADEEXEMPLOS #pragma omp parallel { do_many_things(); #pragma omp single Apenas um thread vai executar esse trecho de código. Detalhe: nesse caso todos os threads aguardarão até que o bloco single seja executado. { exchange_boundaries(); } do_many_other_things(); 28 } UNICIDADEEXEMPLOS #pragma omp parallel private (tmp) #pragma omp for ordered reduction(+:res) for (I=0;I<N;I++){ tmp = NEAT_STUFF(I); #pragma ordered res += consum(tmp); } 29 Os threads executarão os trechos de código um de cada vez, de forma sequencial.