Baixe o app para aproveitar ainda mais
Prévia do material em texto
Interface Hardware-Software OpenMP Aumento de Desempenho dos Processadores Ao longo dos anos, processadores aumentaram o poder de processamento – Aumento da frequencia – Pipeline – Superescalar Mais recentemente, o aumento do desempenho foi alcançado através do suporte em HW à execução paralela (real) de diferentes tarefas – Hardware multithreading – Multicores Hardware Multi-Threading Abordagem multi-thread – Aumentar utilização de recursos de hardware permitindo que múltiplas threads possam executar virtualmente de forma simultânea em único processador – Compartilhamento de unidades funcionais Objetivos – Melhor utilização de recursos (cache e unidades de execução são compartilhadas entre threads) – Ganho de desempenho (em média 20%) – Baixo consumo de área (< 5% da área do chip) Simultaneous Multi-Threading (SMT) SMT é uma política de multi-threading para processadores superescalares com escalonamento dinâmico de threads e instruções – Escalonamento de instruções de threads diferentes – Instruções de threads diferentes podem executar paralelamente desde que haja unidades funcionais livres – Explora paralelismo ao nível de instrução (Instruction Level Parallelism - ILP) – Explora paralelismo ao nível de threads ( Thread Level Parallelism – TLP) Hyper-Threading é um caso de de SMT proposto pela Intel – Disponível em processadores Xeon, Pentium 4- HT, Atom, Intel i7 Multicores Microprocessadores multicores – Mais de um processador por chip Requer programação paralela para uma melhoria efetiva – Hardware executa múltiplas instruções paralelamente Transparente para o programador – Difícil de Programar visando desempenho Balancear carga de trabalho Chapter 7 — Multicores, Multiprocessors, and Clusters — 6 Processamento Paralelo - Hardware e Software Software sequencial/concorrente pode executar em hardware serial/paralelo – Desafio: fazer uso efetivo de hardware paralelo Software Sequencial Concorrente Hardware Serial Multiplicação de matrizes em Java no Intel Pentium 4 Windows Vista no Intel Pentium 4 Paralelo Multiplicação de matrizes em Java no AMD Athlon Phenom II Windows Vista no AMD Athlon Phenom II Programação Paralela Desenvolver software para executar em HW paralelo Necessidade de melhoria significativa de desempenho – Senão é melhor utilizar processador com único core rápido, pois é mais fácil de escrever o código Dificuldades – Particionamento de tarefas (Balanceamento de carga) – Coordenação – Comunicação Modelos de Comunicação Nos computadores atuais, 2 modelos de comunicação entre núcleos são utilizados: – Memória Compartilhada (Mais utilizada em arquiteturas convencionais) – Passagem de Mensagens (Memória Distribuída) Memória Compartilhada Passagem de Mensagens Chapter 7 — Multicores, Multiprocessors, and Clusters — 9 Memória Compartilhada Shared memory multiprocessor Mesmo espaço de memória é compartilhado pelos diferentes processadores Comunicação é feita através de variáveis compartilhadas Comunicação com Memória Compartilhada Variáveis compartilhadas contêm os dados que são comunicados entre um processador e outro Processadores acessam variáveis via loads/stores Acesso a estas variáveis deve ser controlado (sincronizado) – Uso de locks (semáforos) – Apenas um processador pode adquirir o lock em um determinado instante de tempo Chapter 7 — Multicores, Multiprocessors, and Clusters — 11 Passagem de Mensagens Message Passing Processadores compartilham dados enviando explicitamente os dados (mensagem) Comunicação é feita através de primitivas de comunicação (send e receive) Comunicação com Message Passing Cada processador tem seu espaço de endereçamento privado Processadores mandam mensagens utilizando esquema parecido ao de acessar um dispositivo de Entrada/Saída – Processador que aguarda mensagem é interrompido quando mensagem chega Sincronização de acesso aos dados é feita pelo próprio programa – Programador é responsável para estabelecer os pontos de comunicação com as primitivas send e receive OpenMP OpenMP( Open Multi-Processor) é uma API que dá suporte ao modelo de programação (comunicação) paralelo de memória compartilhada Padronizada pelo consórcio OpenMP ARB(OpenMP Architecture Review Board) – AMD, Intel, Cray, HP, Oracle, Nvidia, NEC, etc – Roda em Linux, Windows, Mac OS X, Solaris, etc Programação pode ser feita em C, C++ ou Fortran Camadas de SW e OpenMP Fonte: http://openmp.org/mp-documents/omp-hands-on-SC08.pdf Objetivos de OpenMP Padronização da utilização do modelo de programação de memória compartilhada para diferentes plataformas Provimento de uma camada de SW enxuta para programar plataformas com memória compartilhada – Poucas diretivas e funções para programação paralela Facilidade de uso deste modelo de programação – Permite a paralelização incremental de um código sequencial com poucas diretivas Portabilidade – API em C, C++ e Fortran – Maioria das plataformas no mercado aceitam Linux e Windows Suporte ao Modelo de Memória Compartilhada Threads tem acesso a mesma memória global compartilhada Dados podem ser compartilhados ou privados Dados privados podem ser apenas acessados pelas threads que são as donas Transferência de dados são transparentes ao programador Sincronização acontece, mas boa parte é implícita Paralelismo Suportado por OpenMP Paralelismo através de threads – Quantidade de threads não precisa ser igual a quantidade de cores Programador consegue explicitar que partes do código são paralelizados – Pode explicitar vários níveis de paralelismo Utiliza o modelo de execução paralela fork-join – Uma thread master pode criar várias threads para executar em paralelo – Quando todas as threads filhas completam as tarefas, sincronizam e terminam a execução, deixando a thread master Modelo Fork e Join Fonte: http://www.nic.uoregon.edu/iwomp2005/iwomp2005_tutorial_openmp_rvdp.pdf Estrutura Típica de Um Programa OpenMP Região Paralela Região Sequencial Pode vir seguido de mais regiões paralelas ou sequenciais Início Fim Thread master (inicial,0) é onde a execução do programa se inicia e termina Thread master cria um time (conjunto) de threads que vão executar algo em paralelo (fork) Quando todos os threads de um time chegam ao final da região paralela, todos menos o master terminam (join) Componentes de OpenMP Fonte: http://www.nic.uoregon.edu/iwomp2005/iwomp2005_tutorial_openmp_rvdp.pdf Exemplo de OpenMP: Hello World Paralelo #include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int nthreads, tid; /* Região Paralela, é dado um fork para um time de threads*/ #pragma omp parallel private(nthreads, tid) { /* Obtém o número da thread*/ tid = omp_get_thread_num(); printf("Hello World da thread = %d\n", tid); /* Somente o thread master faz isto */ if (tid == 0){ nthreads = omp_get_num_threads(); printf(“Quantidade de threads = %d\n", nthreads); } }/* No fim é dado um join */ } Diretiva que indica o começo de uma região paralela Função da API (omp.h) Saída da Execução do Hello World Paralelo 4 threads criadas que executam o mesmo código Algumas Considerações Sobre a Paralelização em OpenMP A quantidade de threads criadas pode ser configurada – Variáveis de ambiente, diretivas e chamadas de funções A ordem de execução das threads é determinada pelo S.O - A cada execução a ordem pode mudar Se threads diferentes acessam o mesmo dado, OpenMP não garante sozinho o acesso na ordem correta do dado – Programador deve explicitar onde haverá a sincronização Pode haver diferentes níveis de paralelização (aninhamento) O Que Deve Ser Feito para Usar OpenMP? No caso de C/C++ ter um compilador que possua a biblioteca libgomp – Importante ter também a biblioteca libpthread (Linux) ou libwinpthread (Windows) Colocar no código um #include <omp.h> Configurar a compilação e a link-edição – Colocar –fopenmp na opção de compilação do gcc Configurando a Compilação no CodeBlocks gcc deve compilar com esta opção Configurando a Link-Edição no CodeBlocks Deve incluir o caminho da biblioteca na link-edição Diretivas de OpenMP Boa parte das construções de OpenMP são diretivas de compilação – Instruem a forma de execução do código como por exemplo: paralelização, serialização e sincronização – Se não colocar a opção de compilação –fopenmp, a diretiva é simplesmente ignorada Uma região paralela sob a diretiva é delimitada por chaves { } – Possibilidade de haver outras diretivas dentro das chaves Forma Geral: #pragma omp diretiva [claúsula[cláusula]] Tipos de Diretivas de OpenMP Básica de Paralelismo – parallel Divisão de dados/tarefas entre threads (work-sharing) – for (loop) – section – single – task Sincronização – barrier – master – critical – atomic – ordered Exemplo de Diretiva Parallel : Hello World #include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int nthreads, tid; #pragma omp parallel private(nthreads, tid) { tid = omp_get_thread_num(); printf("Hello World da thread = %d\n", tid); if (tid == 0){ nthreads = omp_get_num_threads(); printf(“Quantidade de threads = %d\n", nthreads); } } } Cláusula que indica que as threads terão suas próprias cópias de variáveis nthreads e tid Delimitação da região paralela Cláusulas OpenMP Diretivas podem vir seguidas de cláusulas Cláusulas são usadas para especificar informações adicionais à diretiva utilizada Pode-se utilizar várias cláusulas em uma mesma instância de diretiva Certas cláusulas só podem ser utilizadas para algumas diretivas específicas Tipos de Cláusulas para a Diretiva Parallel Exemplo de Diretiva Parallel : Hello World #include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int nthreads, tid; #pragma omp parallel num_threads(5) private(nthreads, tid) { tid = omp_get_thread_num(); printf("Hello World da thread = %d\n", tid); if (tid == 0){ nthreads = omp_get_num_threads(); printf(“Quantidade de threads = %d\n", nthreads); } } } Cláusula que indica que as threads terão suas próprias cópias de variáveis nthreads e tid Cláusula que indica a quantidade de threads desejada Diretivas de Work-Sharing O processamento é distribuído entre as threads, ou seja o trabalho é realizado conjuntamente pelas threads Estas diretivas devem estar presentes dentro de regiões paralelas Não podem criar novas threads dentro das áreas limitadas por estas diretivas Tipos de Diretivas de OpenMP Básica de Paralelismo – parallel Divisão de dados/tarefas entre threads (work-sharing) – for (loop) – section – single – task Sincronização – barrier – master – critical – atomic – ordered Diretiva for Serve para distribuir as iterações de um laço entre as threads – Naturalmente deve existir um laço logo após este tipo de diretiva – O laço deve ser do tipo for A diretiva for não deve vir seguida de chaves { } Assim como outras diretivas, eles permitem o uso de cláusulas Forma Geral: #pragma omp for [claúsula[cláusula]] Exemplo de Diretiva for : Soma Matriz #include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int A[2][2] = {{1,2},{3,4}}; int B[2][2] = {{5,6},{7,8}}; int C[2][2]; int i,j,tid; #pragma omp parallel private(j,tid) { #pragma omp for for(i=0; i < 2; i++) { for(j=0; j < 2; j++){ tid = omp_get_thread_num(); C[i][j] = A[i][j] + B[i][j]; printf("thread %d calculou \ C[%d][%d]=%d\n",tid,i,j,C[i][j]); } } } } Variável de controle do loop (i) é privada por padrão, haverá uma thread alocada para calcular o valor de cada linha Importante para que uma thread não interfira no valor de j de outra thread Saída da Execução da Soma Matriz Combinando parallel e for São equivalentes As duas diretivas podem ser combinadas na mesma linha – Naturalmente deve existir um laço logo após este tipo de diretiva – O laço deve ser do tipo for Exemplo de parallel e for : Soma Matriz #include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int A[2][2] = {{1,2},{3,4}}; int B[2][2] = {{5,6},{7,8}}; int C[2][2]; int i,j,tid; #pragma omp parallel for private(j,tid) for(i=0; i < 2; i++) { for(j=0; j < 2; j++){ tid = omp_get_thread_num(); C[i][j] = A[i][j] + B[i][j]; printf("thread %d calculou \ C[%d][%d]=%d\n",tid,i,j,C[i][j]); } } } Não precisa de chaves para delimitar área Tipos de Diretivas de OpenMP Básica de Paralelismo – parallel Divisão de dados/tarefas entre threads (work-sharing) – for (loop) – section – single – task Sincronização – barrier – master – critical – atomic – ordered Diretivas sections e section A diretiva sections serve para listar blocos de código que serão processados por threads separadas – Cada bloco deve vir precedido da diretiva section As diretivas sections e section são delimitadas por chaves { } Caso uma thread seja muito rápida, ela pode executar mais de uma section – Ou ainda, se o número de threads definido pelo programador for menor do que o número de section Formas Gerais de sections e section #pragma omp sections [claúsula[cláusula]] { #pragma omp section {código} #pragma omp section {código} } Exemplo de sections : Quantidade de Pares #include <stdio.h> #include <stdlib.h> #include <omp.h> #define N 1000 int main(){ int matriz[N]; int i,tid, valorInicial = 1, contaPar = 0; //Inicializando a matriz for (i = 0; i < N; i++) { matriz[i] = valorInicial; valorInicial += 3; } Exemplo de sections : Quantidade de Pares #pragma omp parallel private(i,tid) num_threads(2){ #pragma omp sections reduction(+:contaPar) { #pragma omp section { for(i = 0; i < N/2; i++) if (matriz[i] % 2 == 0) contaPar++; } #pragma omp section { for(i = N/2; i < N; i++) if (matriz[i] % 2 == 0) contaPar++; } } } printf("\nQuantidade de pares = %d\n",contaPar); } Cláusula que garante que os valores das cópias locais de contaPar sejam depois combinadas em um único valor global Sincronização Em um ambiente onde o processamento é paralelo, muitas vezes precisamos de sincronização para garantir a corretude da execução do código – Evitar “race conditions” Algumas situações onde sincronização se faz necessário: – Acesso a variáveis compartilhadas, onde toda thread deve enxergar o valor atualizado da variável – Execução ordenada de trechos de código OpenMP possui várias diretivas que possibilitam a sincronização entre threads diferentes Devem ser usadas com cautela, pois sincronização é custosa e afeta o desempenho Formas de Sincronização As duas formas mais comuns de sincronização são: Barreira (Barrier): cada thread espera em um ponto de execução (barreira) até que as outras threads cheguem Exclusão Mútua : Define um bloco de código (região crítica) onde apenas uma thread pode executar em um instante de tempo Tipos de Diretivas de OpenMP Básica de Paralelismo – parallel Divisão de dados/tarefas entre threads (work-sharing) – for (loop) – section – single – task Sincronização – barrier – master – critical – atomic – ordered Quando Usar Barreiras (Barriers) Suponham que tivéssemos estes dois trechos de código e que o loop estivesse sendo executado em paralelo sobre i if (num_thread % 2 == 0) { fator1 = fator1 + 1; } else { fator2 = fator2 + 1; } for (i = 0; i < N; i++) a[i] = i * fator2 * fator1; Dependência de dados pode gerar erros Usando Barreiras (Barriers) for (i = 0; i < N; i++) a[i] = a[i]* fator1 * fator2 Espera! Todas as threads precisam saber o valor de fator, antes de proceder ao loop Barreira Cada thread espera no ponto da barreira e só continua quando todas as threads tiverem atingido este ponto if (num_thread % 2 == 0) { fator1 = fator1 + 1; } else { fator2 = fator2 + 1; } Diretiva barrier Serve para colocar uma barreira de sincronização – Cada thread deve esperar as outras quando encontrada esta diretiva Forma Geral: #pragma omp barrier Exemplo de barrier #include <omp.h> #define N 20 int main(){ int i, num_thread,a[N], fator1 =0,fator2=0; #pragma omp parallel private(num_thread)shared(fator1,fator2) { num_thread = omp_get_thread_num(); if (num_thread % 2 == 0) fator1++; else fator2++; #pragma omp barrier #pragma omp for for(i=0; i < N; i++) { a[i] = i * fator1 * fator2; printf("a[%d] = %d ",i,a[i]); } } Cláusula opcional que declara que fator1 e fator2 são compartilhadas entre threads Todas as threads devem chegar neste ponto para proceder ao resto do código Barreiras Implícitas Nem sempre precisamos da diretiva barrier para especificar uma barreira Em várias situações há barreiras implícitas consideradas pelo compilador: – Fim de regiões paralelas – Construções work-sharing como for possuem barreiras implícitas ao final do loop, excetuando-se quando se coloca a cláusula nowait na diretiva #pragma omp for for (i = 0; i < N; i++) { d[i] = a[i] + b[i]; } Barreira implícita Tipos de Diretivas de OpenMP Básica de Paralelismo – parallel Divisão de dados/tarefas entre threads (work-sharing) – for (loop) – section – single – task Sincronização – barrier – master – critical – atomic – ordered Diretiva critical Serve para colocar delimitar uma região crítica – Só uma thread por vez pode entrar na região crítica – As demais esperam para entrar (barreira implícita) Bastante útil para atualização de variáveis compartilhadas – Garante a consistência de dados Forma Geral: #pragma omp critical [name] {código} name é opcional, mas regiões críticas com o mesmo nome são tratados como a mesma região crítica Exemplo de critical #include <omp.h> int main() { int fator1 =0; int tid; #pragma omp parallel num_threads(5) private(tid) shared(fator1) { tid = omp_get_thread_num(); #pragma omp critical { fator1++; printf("\nThread %d executando\n",tid); printf("\nValor de fator1 = %d\n",fator1); } } return 0; } Variável compartilhada entre threads Garante a consistência na atualização do dado e fluxo correto de execução do código Saída do Programa SEM critical Não gera o resultado esperado Saída do Programa COM critical Tipos de Diretivas de OpenMP Básica de Paralelismo – parallel Divisão de dados/tarefas entre threads (work-sharing) – for (loop) – section – single – task Sincronização – barrier – master – critical – atomic – ordered Diretivas master e single Diretiva single especifica que somente uma thread executa o que está delimitada pela diretiva – Escolha da thread é aleatória – Restante das threads esperam enquanto a thread termina a execução do código delimitado (barreira implícita), exceto quando há uma cláusula nowait Diretiva master especifica que somente a thread master executa o código delimitado pela diretiva – Outras threads podem continuar a executar o restante do código (não há barreira implícita) Formas Gerais: #pragma omp master {código} #pragma omp single[cláusulas] {código} Comportamento de single Fonte: http://www.nic.uoregon.edu/iwomp2005/iwomp2005_tutorial_openmp_rvdp.pdf Funções da API de OpenMP OpenMP oferece várias funções que podem ser chamadas – Para controlar e ver o status do ambiente de execução paralela – Pode-se configurar a quantidade de threads dinamicamente além de sincronizar acesso a regiões críticas Estas funções tem precedência sobre as respectivas variáveis de ambiente Sumário das Funções na API Nome Funcionalidade omp_set_num_threads Estabelece a quantidade de threads omp_get_num_threads Retorna a quantidade de threads omp_get_thread_num Retorna o id da thread omp_get_num_procs Retorna a quantidade de processadores omp_get_wtime Retorna o tempo do “wall clock” omp_get_wtick Retorna o número de segundos entre os ticks do clock omp_init_lock Associa um lock (semáforo) com uma variável omp_destroy_lock Desassocia um lock de uma variável omp_set_lock Adquire o semáforo, senão bloqueia omp_unset_lock Libera o semáforo omp_test_lock Testa e adquire o semáforo Exemplo de Utilização das Funções da API #include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int A[4][2] = {{1,2},{3,4},{5,6},{7,8}};int B[4][2] = {{5,6},{7,8},{9,10},{11,12}}; int C[4][2]; int i,j,tid; omp_set_num_threads(2); #pragma omp parallel for private(j) for(i=0; i < 4; i++) { for(j=0; j < 2; j++){ tid = omp_get_thread_num(); C[i][j] = A[i][j] + B[i][j]; printf("thread %d calculou \ C[%d][%d]=%d\n",tid,i,j,C[i][j]); } } } Estabelece 2 threads Retorna o id da thread Saída do Programa com 2 threads Precedência de Diretivas #include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int A[4][2] = {{1,2},{3,4},{5,6},{7,8}}; int B[4][2] = {{5,6},{7,8},{9,10},{11,12}}; int C[4][2]; int i,j,tid; omp_set_num_threads(2); #pragma omp parallel for private(j) num_threads(4) for(i=0; i < 4; i++) { for(j=0; j < 2; j++){ tid = omp_get_thread_num(); C[i][j] = A[i][j] + B[i][j]; printf("thread %d calculou \ C[%d][%d]=%d\n",tid,i,j,C[i][j]); } } } Estabelece 2 threads Neste caso, a precedência é da diretiva com 4 threads Saída do Programa com 4 threads Funções de Lock Locks oferecem uma flexibilidade maior em relação a regiões críticas (diretiva critical) – Permitem que as threads que não adquiriram o lock façam outras coisas enquanto esperam Variáveis de lock devem ser manipuladas através das funções apropriadas de OpenMP Locks devem ser inicializados – Comportamento indefinido caso não sejam inicializados Exemplo de Utilização de Funções de Locks #include <omp.h> #include <windows.h> int main(){ int tid; omp_lock_t lck; omp_init_lock(&lck); omp_set_num_threads(3); #pragma omp parallel shared(lck) private(tid) { tid = omp_get_thread_num(); while (!omp_test_lock(&lck)) { printf("\nThread %d esperando lock \n",tid); Sleep(3); } printf("\nThread %d adquiriu lock\n",tid); Sleep(5); printf("\nThread %d liberou lock\n",tid); omp_unset_lock(&lck); } } Lock deve ser inicializado Testa e adquire o lock Enquanto não adquire, faz outra coisa Libera o lock Saída do Programa com Locks Thread 0 adquiriu o lock, e threads 1 e 2 fazem outra coisa enquanto esperam Algumas Variáveis de Ambiente OpenMP Nome Funcionalidade OMP_NUM_THREADS Estabelece a quantidade de threads OMP_DYNAMIC Habilita/Desabilita se a quantidade de threads pode ser ajustada dinamicamente pelo ambiente de execução OMP_NESTED Habilita/Desabilita o aninhamento de paralelismo Existem muitas variáveis de ambiente que podem modificar o ambiente de execução Quando há uma cláusula de diretiva ou função da API que tem a mesma funcionalidade, estes tem maior precedência sobre as variáveis de ambiente – Cláusulas > Funções > Variáveis de ambiente Alguns exemplos de variáveis de ambiente:
Compartilhar