Tópicos Especiais em Algoritmos (Programação paralela com GPU) (DCC/UFRJ) Aula 2: Conceitos fundamentais da programação paralela Profs. Silvana Rossetto 2 de setembro de 2016 Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Fundamentos da programação paralela A programação paralela engloba o uso de: técnicas para dividir uma tarefa em subtarefas menores ...e mecanismos para coordenar a execução dessas subtarefas por processadores distintos Dependendo das caracterı́sticas do problema, e do ambiente de execução paralela alvo (CPU multinúcleo ou GPU), o programador deve selecionar as alternativas mais adequadas Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Exemplo inicial Considere a operação C = A * k + B, sendo A, B e C vetores de tamanho N e k um número (operação que aparece com frequência em problemas de álgebra linear computacional) Para encontrar o vetor de saı́da C , precisamos calcular cada elemento desse vetor fazendo: C[i] = A[i] * k + B[i], 0<=i<N Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Algoritmo sequencial void somaVetoresSeq(const float a[], const float b[], float c[], float k, int n) { for(int i=0; i<n; i++) { c[i] = a[i] * k + b[i]; } } void main() { float a[N], b[N], c[N]; //inicializa os vetores a e b ... somaVetoresSeq(a, b, c, 2.0, N); } Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Algoritmo paralelo void calculaElementoVetor(const float a[], const float b[], float c[], float k, int pos) { c[pos] = k * a[pos] + b[pos]; } void main() { float a[N], b[N], c[N]; //inicializa os vetores a e b ... //faz C = k * A + B for(int i=0; i<N; i++) { //dispara um fluxo de execuç~ ao f para executar: // calculaElementoVetor(a, b, c, 2.0, i) } } Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Execução sequencial versus execução paralela O que fizemos foi reduzir a complexidade de processamento de O(N) para O(1), assumindo que N fluxos de execução poderão estar ativos ao mesmo tempo Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Exemplo inicial ..mas se não tivermos processadores em número suficiente para permitir que todos os fluxos de execução estejam ativos ao mesmo tempo? vale a pena pensar em outras formas de dividir a tarefa principal? quais seriam essas outras formas? Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Algoritmo paralelo (alternativa 1) void calculaElementosConsVetor(const float a[],const float b[], float c[], float k, int inicio, int fim) { for(int i=inicio; i<fim; i++) c[i] = k * a[i] + b[i]; } void main() { float a[N], b[N], c[N]; int i, inicio, fim; //inicializa os vetores a e b ... //faz C = k * A + B for(i=0; i<P; i++) { inicio = i * (N/P); //o último fluxo trata os elementos restantes if (i<P-1) fim = inicio + (N/P); else fim = N; //dispara um fluxo de execuç~ ao f para executar: //calculaElementosConsVetor(a, b, c, 2.0, inicio, fim); } } Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Algoritmo paralelo (alternativa 2) void calculaElementosInterVetor(const float a[], const float b[] float c[], float k, int inicio, int salto, int n) { int i; for(i=inicio; i<n; i=i+salto) { c[i] = k * a[i] + b[i]; } } void main() { float a[N], b[N], c[N]; //inicializa os vetores a e b ... //faz C = k * A + B for(int i=0; i<P; i++) { //dispara um fluxo de execuç~ ao f para executar: //calculaElementosInterVetor(a, b, c, 2.0, i, P, N); } } Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Balanceamento de carga entre as threads Uma preocupação quando projetamos um algoritmo paralelo é garantir que todas as unidades de processamento recebam a mesma carga de trabalho para aproveitar todos os recursos de processamento disponı́veis As alternativas de paralelização mostradas garantem o balanceamento de carga entre as unidades de processamento? Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Padrão de acesso à memória O padrão de acesso à memória varia de uma alternativa para a outra, o que poderá fazer com que o desempenho de cada uma delas — em termos de tempo de acesso à memória — varie de forma significativa Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Outro exemplo de problema Considere agora o problema de somar todos os elementos de um vetor: float somaElemVetorSeq(const float a[], int n) { float soma = 0; for(int i=0; i<n; i++) { soma = soma + a[i]; } return soma; } void main() { float a[N], soma_total; //inicializa o vetor a ... soma_total = somaElemVetorSeq(a, N); printf("soma total = %f", soma_total); } Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Soma os elementos de um vetor Exercı́cio Escreva um algoritmo para paralelizar esse problema. Profs. Silvana Rossetto Conceitos fundamentais da programação paralela uma proposta de algoritmo paralelo (para CPU) float soma_total = 0; //Soma uma parte dos n elementos do vetor a void somaGrupoConsElemVetor(const float a[], int inicio, int fim) { float soma_local = 0; for(int i=inicio; i<fim; i++) soma_local = soma_local + a[i]; // !!! solicita exclus~ ao mútua !!! soma_total = soma_total + soma_local; // !!! libera a exclus~ ao mútua !!! } ... Profs. Silvana Rossetto Conceitos fundamentais da programação paralela uma proposta de algoritmo paralelo (para CPU) ... void main() { float a[N]; int i, inicio, fim; //inicializa o vetor a ... //soma os n elementos de a for(i=0; i<P; i++) { inicio = i * (N/P); //o último fluxo trata os elementos restantes if (i<P-1) fim = inicio + (N/P); else fim = N; //dispara um fluxo de execuç~ ao f para executar: //somaGrupoConsElemVetor(a, inicio, fim); } //!!!espera todos os fluxos de execuç~ ao terminarem !!! printf("soma total = %f", soma_total); } Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Sincronização das threads sincronização por exclusão mútua sincronização por condição Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Sincronização por barreira tipo particular de sincronização por condição que aparece em problemas que requerem que todos os fluxos de execução (ou um grupo deles) sicronizem suas ações a cada passo do algoritmo Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Definição de programação paralela Navarro et al. definem a programação paralela como a tarefa de resolver um problema de tamanho n dividindo o seu domı́nio em k ≥ 2 (k ∈ N) partes que deverão ser executadas em p processadores fı́sicos simultaneamente Seguindo essa definição, um problema PD com domı́nio D é dito paralelizável se for possı́vel decompor PD em k subproblemas: D = d1 ⊕ d2 ⊕ · · · ⊕ dk = k X di i=1 Dependendo das caracterı́sticas do problema, há diferentes formas de realizar essa decomposição Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Paralelismo de dados ou paralelismo de tarefas Dizemos que o problema PD é um problema com paralelismo de dados se D é composto de elementos de dados e é possı́vel aplicar uma função f (· · · ) para todo o domı́nio: f (D) = f (d1 ) ⊕ f (d2 ) ⊕ · · · ⊕ f (dk ) = k X f (di ) i=1 Por outro lado, se D é composto por funções e a solução do problema consiste em aplicar cada função sobre um fluxo de dados comum, dizemos que o problema PD é um problema com paralelismo de tarefas: D(S) = d1 (S) ⊕ d2 (S) ⊕ · · · ⊕ dk (S) = k X di (S) i=1 Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Metodologia para projetar algoritmos paralelos Projetar e implementar algoritmos paralelos não é uma tarefa simples e não há uma regra geral para desenvolver algoritmos paralelos perfeitos Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Etapas propostas por Ian Foster Particionamento: a tarefa que deve ser executada e o conjunto de dados associado a ela são decompostos em tarefas menores o objetivo é identificar oportunidades de execução paralela caracterizar o domı́nio do problema (com “paralelismo de dados” ou “paralelismo de tarefas”) é fundamental para escolher uma boa estratégia de particionamento Comunicação: nessa etapa, determina-se a comunicação requerida para coordenar a execução da tarefa e define-se as estruturas e algoritmos de comunicação mais apropriados Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Etapas propostas por Ian Foster Aglomeração: as subtarefas e estruturas de comunicação definidas nas etapas anteriores são avaliadas com respeito aos requisitos de desempenho e custos de implementação e, se necessário, as subtarefas são combinadas em tarefas maiores para melhorar o desempenho ou reduzir os custos de desenvolvimento Mapeamento: cada tarefa é designada para uma unidade de processamento de uma forma que satisfaça a meta de maximizar o uso da capacidade de processamento paralela disponı́vel e minimizar os custos de comunicação e gerência o mapeamento pode ser feito de forma estática ou determinado em tempo de execução Profs. Silvana Rossetto Conceitos fundamentais da programação paralela GPGPU versus GPU Computing “GPGPU”(General-Purpose computing on GPUs) Até 2006, os chips gráficos eram muito difı́ceis de programar por conta da necessidade de usar a API gráfica para acesso ao processadores (técnicas OpenGL e Direct3D) (ver http://gpgpu.org) “GPU Computing” Em 2007, com o lançamento de CUDA e mudanças no hardware (chips G-80 em diante), a interface de programação passou a não depender mais da API gráfica Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Sistemas de “computação massiva” Combinam CPUs multicore e GPUs manycore Profs. Silvana Rossetto Conceitos fundamentais da programação paralela CPUs multicores X GPUs manycores CPU multicore Projeto otimizado para melhorar o desempenho de código sequencial Lógica de controle sofisticada para que instruções de uma única thread executem em paralelo (ou fora da sua ordem sequencial) enquanto mantém a aparência de execução sequencial Memória cache grande para reduzir a latência de acesso aos dados e instruções das aplicações mais complexas (largura de banda de acesso à memória <20GB/s) Dificuldade de alterar o modelo de acesso à memória (para atender requisitos de sistemas operacionais e sistemas legados) Profs. Silvana Rossetto Conceitos fundamentais da programação paralela CPUs multicores X GPUs manycores GPU manycore Projeto otimizado para execução de um número muito grande de threads Lógica de controle simplificada na execução de cada thread Memória cache pequena (apenas para ajudar nos requisitos de largura de banda i.e., threads acessando os mesmos dados não precisam ir todas a DRAM) Largura de banda de acesso a DRAM 80GB/s, e para comunicação com a CPU 4GB/s (em cada direção) O hardware aproveita o grande número de threads para encontrar trabalho para fazer quando parte das threads estão esperando pela latência de acesso à memória Profs. Silvana Rossetto Conceitos fundamentais da programação paralela CPUs multicores X GPUs manycores Figura: Programming Massively Parallel Processors: A Hands-on Approach, 2010 David B. Kirk/NVIDIA Corporation and Wen-mei Hwu. Elsevier Inc. Profs. Silvana Rossetto Conceitos fundamentais da programação paralela Referências bibliográficas 1 C. A. Navarro, N. Hitschfeld-Kahler e L. Mateu, A Survey on Parallel Computing and its Applications in Data-Parallel Problems Using GPU Architectures, Communications in Computational Physics, vol 15, 2014 2 I. Foster, Designing and building parallel programs : concepts and tools for parallel software engineering, Addison-Wesley, 1995 Profs. Silvana Rossetto Conceitos fundamentais da programação paralela