Baixe o app para aproveitar ainda mais
Prévia do material em texto
3 PROJETO DE ALGORITMOS PARALELOS O desenvolvimento de um algoritmo é um componente crítico na solução de proble- mas usando computadores. Um algoritmo seqüencial pode ser definido como uma seqüên- cia de passos básicos para resolver um dado problema usando um computador seqüencial. Já um algoritmo paralelo pode ser visto como uma receita que diz como resolver um dado problema usando múltiplos processadores. Entretanto, especificar um algoritmo paralelo envolve mais do que especificar os passos. Um algoritmo paralelo tem a dimensão de concorrência e o desenvolvedor do algoritmo deve especificar um conjunto de passos que podem ser executados simultaneamente. É essencial obter algum benefício de desempe- nho ao utilizar um computador paralelo. Na prática, especificar um algoritmo paralelo pode envolver todos ou alguns dos seguintes passos: • Identificar porções de trabalho que podem ser realizadas concorrentemente; • Mapear as porções concorrentes de trabalho em múltiplos processos para execu- ção em paralelo; • Distribuir entrada, saída e dados intermediários associados ao programa; • Gerenciar o acesso aos dados compartilhados por múltiplos processadores; • Sincronizar os processadores em vários estágios de execução do programa para- lelo. Geralmente, há diversas escolhas para cada um dos passos citados anteriormente. Porém, poucas combinações de escolhas levam a um algoritmo paralelo que produza de- sempenho compatível com os recursos computacionais e de armazenamento usados para resolver o problema. Diversas escolhas produzem o melhor desempenho em diferentes arquiteturas paralelas ou sob diferentes paradigmas de programação paralela. O objetivo deste capítulo é discutir o processo de projeto e implementação de algorit- mos paralelos. 58 DCC/UFLA - Programação Paralela e Distribuída 3.1 CONCEITOS INICIAIS Dividir a computação em computações menores e atribuí-las a diferentes processado- res para execução paralela são os dois passos chave no projeto de algoritmos paralelos. O processo de dividir uma computação em partes menores, sendo que algumas ou todas as partes podem ser potencialmente executadas em paralelo, é denominado decom- posição. As tarefas são as unidades de computação definidas pelo programador onde a computação principal é subdividida por meio da decomposição. A execução simultânea de múltiplas tarefas é a chave para reduzir o tempo necessário para resolver o problema inteiro. As tarefas podem ser de tamanho arbitrário, mas uma vez definidas, elas são consi- deradas como unidades de computação granularidade. As tarefas nas quais um problema é decomposto pode não ser todas do mesmo tamanho. Exemplo 1: Considere uma multiplicação de uma matriz A densa, N x N, por um vetor b para produzir outro vetor y. O i-ésimo elemento y[i] do vetor produto é o pro- duto vetorial da i-ésima linha de A pela entrada do vetor b; isto é, y[i] = ∑N−1j=0 A[i, j].b[ j]. Como apresenta a Figura 3.1, a computação de cada y[i] pode ser considerada uma tarefa. Alternativamente, como mostra a Figura 3.2, a computação pode ser decomposta em um número menor de tarefas, onde cada tarefa computa n/3 das entradas do vetor y. Assim, a tarefa 1 é composta pelas computações y[0] = ∑N−1j=0 A[0, j].b[ j], y[1] = ∑ N−1 j=0 A[0, j].b[ j] e y[2] = ∑N−1j=0 A[0, j].b[ j]. Figura 3.1: Decomposição da multiplicação da matriz por vetor em N tarefas Note que na Figura 3.1 todas as tarefas são independentes e podem ser executadas todas juntas ou em qualquer sequencia. Entretanto, em geral, algumas tarefas podem usar dados produzidos por outras tarefas e precisar esperar tais resultados para para iniciar sua execução. O grafo de dependência de tarefas é a abstração usada para expressar as dependências entre as tarefas e sua ordem relativa de execução. Em um grafo orientado Projeto de Algoritmos Paralelos 59 Figura 3.2: Decomposição da multiplicação da matriz por vetor em N/3 tarefas. e acíclico, os nós representam as tarefas e as arestas orientadas indicam a dependência entre os nós. Uma tarefa correspondente a um nó e só pode ser executada quando todas as tarefas conectadas a este nó por arestas incidentes tenham completado sua execução. Note que o grafo de dependência de tarefas pode ser desconectado e o conjunto de arestas do grafo de dependência de tarefas pode ser vazio. É o caso da multiplicação de matriz por vetor, onde cada tarefa computa um subconjunto de entradas do vetor produto. Exemplo 2: Considere o banco de dados relacional de veículos da Figura 3.3. Cada linha da tabela é um registro que contém dados correspondentes a um determinado veí- culo, tal como seu ID, modelo, ano, cor, etc. Considere as computações realizadas no processamento da consulta: MODEL="Civic"AND Year="2001"AND (COLOR="Green"OR COLOR="White") Esta consulta procura todos os Civics 2001 cuja cor seja Green ou White. No banco de dados relacional, esta consulta é processada criando tabelas intermediárias. Uma possível forma é primeiro criar as quatro tabelas da Figura 3.4: uma tabela contendo todos os Civics, uma tabela contendo todos os carros modelo 2001, uma tabela contendo todos os carros de cor Green e uma tabela contendo todos os carros de cor White. A seguir, a computação continua combinando estas tabelas pela computação de suas interseções ou uniões em pares. Em particular, ele computa a interseção da tabela Civic com a tabela ano modelo 2001, para construir uma tabela de todos os modelos Civic 2001. Similarmente, ele computa a união de tabelas cores Green e White para computar uma tabela armazenando todos os carros cuja cor é Green ou White. Finalmente, ele computa a interseção da tabela contendo todos os Civics 2001 com a tabela contendo todos os veículos Green ou White e retorna a lista desejada. Para este exemplo, cada nó na Figura 3.4 é uma tarefa que corresponde a uma tabela intermediária que precisa ser computada e as setas entre os nós indicam dependências 60 DCC/UFLA - Programação Paralela e Distribuída Figura 3.3: Banco de dados de armazenamento de informação sobre veículos usados Figura 3.4: As diferentes tabelas e suas dependências em uma operação de processamento de consulta entre as tarefas. Por exemplo, antes de computar a tabela que corresponde aos Civics 2001, deve-se computar a tabela de todos os Civics e a tabela de todos os carros modelo 2001. Note que há múltiplas formas de expressar certas computações, especialmente aque- las envolvendo operações associativas tais como adição, multiplicação e AND ou OR ló- Projeto de Algoritmos Paralelos 61 gicos. Diferentes formas de arranjar computações podem levar a diferentes grafos de de- pendência de tarefas com diferentes características. Por exemplo, o banco de dados do Exemplo 2 pode ser resolvido computando primeiro a tabela de todos os carros GREEN ou WHITE, e então realizar uma interseção com a tabela de todos os carros modelo 2001, e finalmente combinar os resultados com a tabela de todos os Civics. Esta seqüência pode ser visualizada na Figura 3.5. Figura 3.5: Um grafo alternativo de dependência de dados para a operação de processamento de consulta A taxa entre a quantidade de computação e de comunicação dentro de um programa paralelo ou distribuído é conhecida como granularidade. O número e o tamanho das tarefas em que um problema pode ser decomposto determina a granulosidade ou granularidade da decomposição. Uma decomposição em um número grande de pequenas tarefas é chamada granulo- sidade fina (fine-grained), pois há relativamente menos execuções de instruções de CPU produtivas comparadas com o número de vezes que os processadores comunicam via me- mória compartilhada ou passagem de mensagens e esperam para sincronizar com outros processadores.Já uma decomposição em um número menor de tarefas grandes é chamada granu- losidade grossa (coarse-grained), pois ocorrerá um maior número de execuções de CPU do que de comunicação entre os processadores. Por exemplo, a decomposição da multiplicação matriz por vetor mostrada na Figura 3.1 é considerada de granulosidade fina porque há um grande número de tarefas que realizam 62 DCC/UFLA - Programação Paralela e Distribuída um único produto. A Figura 3.2 mostra uma decomposição do mesmo problema em N/3 tarefas, onde cada tarefa computa 3 das entradas do vetor resultante de tamanho N. Um conceito relacionado a granulosidade é o grau de concorrência. O grau máximo de concorrência é o número máximo de tarefas que podem ser execu- tadas simultaneamente em um programa paralelo em um instante de tempo. Normalmente, o grau máximo de concorrência é menor do que o número total de tarefas devido às de- pendências entre as tarefas. Por exemplo, o grau máximo de concorrência nos grafos de tarefas das Figuras 3.4 e 3.5 é 4. O grau de concorrência também depende da forma do grafo de dependência de tarefas e a mesma granulosidade, em geral, não garante o mesmo grau de concorrência. Por exemplo, considere o os grafos de tarefas da Figura 3.6, são abstrações dos grafos das Figuras 3.4 e 3.5. O número em cada nó representa a quantidade de trabalho requerido para completar a tarefa correspondente àquele nó. Figura 3.6: Abstrações dos grafos de tarefas das Figuras 3.4 e 3.5 Em um grafo de dependência de tarefas, vamos nos referir aos nós que não possuem arestas incidentes a eles como sendo os nós iniciais e os nós que possuem arestas que saem deles como os nós finais. O mais longo caminho orientado entre um par de nós iniciais e finais é conhecido como caminho crítico. A soma dos pesos dos nós ao longo deste caminho é conhecido como tamanho de caminho crítico, onde o peso de um nó é o tamanho ou a quantidade de trabalho associado com a tarefa correspondente. No grafo de dependência de tarefas da Figura 3.6(a) o tamanho do caminho crítico é 27. Na Figura 3.6(b) é 34. 3.1.1 Processo e Mapeamento Um processo refere-se ao agente de processamento ou computação que desempenha tarefas. Os processadores são unidades de hardware que fisicamente realizam computa- Projeto de Algoritmos Paralelos 63 ções. Há uma correspondência um-para-um entre processos e processadores e é apro- priado assumir que há tantos processos quanto o número de CPUs físicas do computador paralelo. O termo processo não é exatamente igual à definição usada em sistemas opera- cionais. Um processo é uma entidade abstrata que usa o código e dados correspondentes a uma tarefa para produzir a saída daquela tarefa dentro de uma quantidade de tempo fi- nita, depois que a tarefa é ativada pelo programa paralelo. Durante este tempo, além de realizar computações, um processo pode sincronizar ou comunicar com outros processos, se for necessário. Para obter speedup sobre a implementação seqüencial, um programa paralelo deve ter diversos processos ativos simultaneamente, trabalhando sobre diferentes tarefas. O mecanismo pelo qual as tarefas são atribuídas aos processos para execução é denominado mapeamento. Um bom mapeamento deve buscar maximizar o uso da concorrência pelo mapea- mento independente das tarefas em diferentes processos e minimizar o tempo de execução total, garantindo que processos estejam disponíveis para executar as tarefas no caminho crítico o mais breve que estas tarefas tornem-se executáveis. Além disso, deve minimi- zar a comunicação entre os processos pelo mapeamento de tarefas com um alto grau de comunicação mútua sobre o mesmo processo. Figura 3.7: Mapeamento dos grafos de tarefas da Figura 3.6 em 4 processos A Figura 3.7 apresenta um mapeamento das tarefas da Figura 3.6 em 4 processos. Note que, neste caso, um máximo de 4 processos pode ser usado de forma útil, embora o número total de tarefas seja 7. Isto ocorre porque o grau máximo de concorrência é 4. As 3 últimas tarefas podem ser mapeadas arbitrariamente entre os processos para satisfazer as restrições do grafo de dependência de tarefas. Entretanto, faz mais sentido mapear as tarefas conectadas por um aresta sobre o mesmo processo porque isso previne uma inte- ração entre tarefas que se tornaria uma interação entre processos. Por exemplo, na Figura 3.7(b), se Task 5 for mapeada no processo P2, então os processos P0 e P1 precisarão se 64 DCC/UFLA - Programação Paralela e Distribuída comunicar com P2. No mapeamento atual, somente uma única comunicação entre P0 e P1 é suficiente. 3.2 TÉCNICAS DE DECOMPOSIÇÃO As técnicas de decomposição podem ser classificadas em: decomposição recursiva, decomposição de dados, decomposição exploratória e decomposição especulativa. As téc- nicas de decomposição recursiva e de dados são consideradas de propósito geral quando elas podem ser usadas para decompor uma grande variedade de problemas. Por outro lado, as técnicas de decomposição especulativa e exploratória são de natureza mais específica porque elas aplicam a classes específicas de problemas. 3.2.1 Decomposição recursiva É um método para induzir concorrência em problemas que podem ser resolvidos usando a estratégia de divisão e conquista. O problema é resolvido pela sua divisão em um conjunto de subproblemas independentes. Cada subproblema é resolvido recursivamente aplicando uma divisão similar em subproblemas menores seguido por uma combinação de seus resultados. A estratégia de divisão e conquista resulta em concorrência natural, quando diferentes subproblemas podem ser resolvidos concorrentemente. Exemplo 3: Considere o problema de ordenação de uma seqüência A de n elementos usando o algoritmo quicksort. Quicksort é um algoritmo de divisão e conquista que inicia selecionando um elemento pivô x e então particiona a seqüência A em 2 subseqüências A0 e A1 tal que todos os elementos em A0 sejam menores que x e todos elementos em A1 sejam maiores ou iguais a x. Este passo de particionamento forma o passo de divisão do algoritmo. Cada uma das subseqüências A0 e A1 é ordenada recursivamente chamando quicksort. Cada uma das chamadas recursivas particiona as seqüências. A recursão termina quando cada subseqüência contém somente um único elemento. Isto é ilustrado na Figura 3.8 que apresenta uma seqüência de 12 números e o grafo de dependência de tarefas do quicksort baseado na decomposição recursiva. 3.2.2 Decomposição de dados A decomposição de dados é um método poderoso e muito utilizado para derivar con- corrência em algoritmos que operam sobre estruturas de dados grandes. Neste método, a decomposição de computações é feita em dois passos. No primeiro passo, os dados sobre os quais as computações são realizadas são particionados. No segundo passo, este parti- cionamento de dados é usado para induzir um particionamento de computações em tarefas. As operações que estas tarefas realizam em diferentes partições de dados são geralmente similares ou são escolhidas de um pequeno conjunto de operações. Projeto de Algoritmos Paralelos 65 Figura 3.8: Grafo de dependência de tarefas do quicksort baseado na decomposição recursiva para ordenação da seqüência de 12 números O particionamento de dados pode ser feito de várias maneiras: particionamento de dados de saída, de dados de entrada, dados de entrada e saída, dados intermediários. Estas maneiras diferentes devem ser exploradas e avaliadas para que seja determinada qual produz uma decomposição computacional natural e eficiente. Particionamento dos dados de saída: em muitas computações, cada elemento da saída pode ser computado independentemente de outros elementos como uma função da entrada. Em tais computações, um particionamento de dados de saídaautomaticamente induz uma decomposição de problema em tarefas, onde a cada tarefa é atribuído o trabalho de computar uma porção da saída. Exemplo: multiplicação de matrizes 2x2 Tare f a1 : C[1,1] = A[1,1]B[1,1]+A[1,2]B[2,1] Tare f a2 : C[1,2] = A[1,1]B[1,2]+A[1,2]B[2,2] Tare f a3 : C[2,1] = A[2,1]B[1,1]+A[2,2]B[2,1] Tare f a4 : C[2,2] = A[2,1]B[1,2]+A[2,2]B[2,2] Particionamento de dados de entrada: o particionamento de dados de saída pode ser realizado somente se cada saída pode ser naturalmente computada como uma função da entrada. Em muitos algoritmos, não é desejável ou possível particionar os dados de saída. Por exemplo: ao encontrar a soma de uma seqüência de N números usando p pro- cessos (N > p), podemos particionar a entrada em p subconjuntos de tamanhos aproxima- damente iguais. Cada tarefa, então, computa a soma de números em um dos subconjuntos. Finalmente, os p resultados parciais podem ser adicionados para produzir o resultado final. 66 DCC/UFLA - Programação Paralela e Distribuída Particionar dados de entrada e de saída: em alguns casos, em que é possível particionar os dados de saída, particionar os dados de entrada pode oferecer concorrência adicional. Particionamentode dados intermediários: algoritmos são freqüentemente estrutu- rados em computações em múltiplos estágios tal que a saída de um estágio é a entrada do estágio subseqüente. O particionamento de dados intermediários pode algumas vezes produzir uma concorrência maior do que particionar dados de entrada ou saída. Os da- dos intermediários não são gerados explicitamente em algoritmo seqüencial para resolver o problema e alguma reestruturação do algoritmo original pode ser necessária para usar o particionamento de dados intermediário para induzir uma decomposição. 3.2.3 Decomposição exploratória É usada para decompor problemas cuja computação corresponde a uma busca em um espaço por soluções. Em decomposição exploratória, particionamos o espaço de busca em partes menores e buscamos cada uma dessas partes concorrentemente, até que as soluções desejadas sejam encontradas. Exemplo 4: O 15-puzzle consiste em 15 quadradinhos numerados de 1 a 15 e um quadradinho em branco, todos colocados numa grade 4x4. Um quadradinho pode ser mo- vido da posição branco para uma posição adjacente a ela, criando um branco na posição original do quadradinho. Dependendo da configuração da grade, até 4 movimentos são possíveis: para cima, para baixo, para a direita e para a esquerda. As configurações iniciais e finais dos quadradinhos são especificadas. O objetivo é determinar qualquer seqüência ou a seqüência mais curta de movimentos que transforme a configuração inicial na con- figuração final. A Figura 3.9 ilustra os estados gerados por uma instância do problema 15− puzzle. 3.2.4 Decomposição especulativa É usada quando um programa pode tomar um de muitos ramos computacionalmente possíveis dependendo da saída de outras computações que o precedem. Enquanto uma tarefa está realizando computação cuja saída é usada na decisão da próxima computação, outras tarefas podem concorrentemente iniciar as computações do próximo estágio. Este cenário é similar à avaliação de um ou mais dos ramos de um comando switch em C em paralelo antes da entrada para o switch estar disponível. Enquanto uma tarefa está rea- lizada a computação que resolverá o switch, outras tarefas podem explorar os múltiplos ramos do switch em paralelo. Quando a entrada do switch tiver sido computada, a com- putação correspondente ao ramo correto seria usado enquanto que a correspondente aos outros ramos seria descartada. O tempo de execução paralelo é menor do que o tempo de execução seqüencial porque a quantidade de tempo necessária para avaliar a condição da Projeto de Algoritmos Paralelos 67 Figura 3.9: Estados gerados por uma instância do problema 15-puzzle. qual a próxima tarefa depende é utilizada para realizar uma computação útil para o próximo estágio em paralelo. 3.2.5 Decomposições híbridas Uma computação é estruturada em múltiplos estágios e é, algumas vezes, necessário aplicar diferentes tipos de decomposição em diferentes estágios. 3.3 TÉCNICAS DE MAPEAMENTO PARA BALANCEAMENTO DE CARGA Uma vez que uma computação tenha sido decomposta em tarefas, estas tarefas são mapeadas em processos com o objetivo de que todas as tarefas executem no mais curto intervalo de tempo. Para alcançar um tempo de execução pequeno, os overheads da exe- cução de tarefas em paralelo devem ser minimizados. Mapeamento é o mecanismo pelo qual tarefas são atribuídas aos processos para execução. As técnicas de mapeamento usadas para algoritmos paralelo podem ser classificadas em estática e dinâmica. O paradigma de programação paralela e as características das tarefas e comunicações entre elas determinam se é melhor utilizar ummapeamento estático ou dinâmico. Mapeamento estático: distribui as tarefas entre os processos antes da execução do algoritmo. 68 DCC/UFLA - Programação Paralela e Distribuída Mapeamento dinâmico: distribui o trabalho entre os processos durante a execução do algoritmo. Se as tarefas são geradas dinamicamente elas devem também ser mapeadas dinamicamente. Geralmente, os algoritmos que fazem uso de mapeamento estático são mais fáceis de projetar e programar. Já os algoritmos de mapeamento dinâmico são mais complicados, principalmente no paradigma de passagem de mensagens. Esquemas para mapeamento estático são baseados em particionamento de dados ou em particionamento de tarefas. 3.3.1 Mapeamento estático As técnicas de mapeamento estático são usadas em conjunto com a decomposição baseada em particionamento de dados. Além disso, são usadas para mapear certos pro- blemas que são expressos naturalmente por um grafo de dependência de tarefas estático. No mapeamento baseado em particionamento de dados as tarefas são associadas com porções de dados. Mapear os dados relevantes em processos é equivalente a mapear tarefas em processos. A técnica de distribuição em blocos é a forma mais simples de distribuir um vetor e atribuir porções contíguas do vetor a diferentes processos. Nesta distribuição, um vetor de d-dimensões é distribuído entre os processos tal que cada processo receba um bloco contíguo de entradas do vetor em um subconjunto específico de dimensões do vetor. A distribuição em blocos de vetores são particularmente apropriadas quando há uma loca- lidade de comunicação, isto é, a computação de um elemento de um vetor requer outros elementos próximos a ele no vetor. A distribuição em blocos cíclicos particiona o vetor em mais blocos do que o nú- mero de processos e atribui as partições aos processos de forma round-robin tal que cada processo receba diversos blocos não adjacentes. O particionamento de grafo é usado para algoritmos que operam sobre estruturas de dados esparsas e quando a dependência dos dados é altamente irregular. As simulações numéricas de fenômenos físicos envolvem a computação de valores de certas quantidades físicas em cada ponto da malha. A computação de um ponto da malha geralmente requer dados correspondentes àquele ponto da malha e os pontos que são adjacentes a ele na malha. A distribuição dos pontos visa balancear a carga e mi- nimizar a quantidade de dados que cada processo precisa acessar para completar suas computações. É necessário particionar a malha de forma que cada uma das p partições seja atribuída a cada um dos p processos. Cada processo recebe regiões contínuas da malha tal que o número total de pontos da malha que precisam ser acessados nos limites da partição seja minimizado. O mapeamento baseado em particionamento de tarefas visa particionar o grafo de dependência de tarefas e mapear seus nós aos processos, onde as tarefaspossuem Projeto de Algoritmos Paralelos 69 tamanhos conhecidos. Além disso, visa minimizar o tempo ocioso e o tempo de interação de um algoritmo paralelo. 3.3.2 Esquemas para mapeamento dinâmico O mapeamento dinâmico é necessário em situações onde o mapeamento estático pode resultar em um alto desbalanceamento de trabalho entre os processos ou onde o grafo de dependência de tarefas é dinâmico. A razão principal para usar mapeamento dinâmico é balancear a carga de trabalho entre os processos, por isso, o mapeamento dinâmico pode ser também denominado de balanceamento de carga dinâmico. As técnicas de mapeamento dinâmico são geralmente classificadas como centraliza- das e distribuídas. Esquema de mapeamento dinâmico centralizado Todas as tarefas são mantidas em uma estrutura de dados central ou em um processo especial ou em um subconjunto de processos. Se um processo é designado como o ge- rente do pool de tarefas disponíveis, então é denominado mestre e outros processos, que dependem do mestre para receber trabalho são os escravos. Quando um processo não tem trabalho, ele pega trabalho com o mestre ou na estrutura de dados central. Quando uma nova tarefa é gerada ela é adicionada à estrutura de dados central ou reportada ao mestre. Quanto maior o número de processos maior é o acesso ao mestre ou à estrutura de dados centralizada, tornando-se um gargalo. Esquema de mapeamento dinâmico distribuído O conjunto de tarefas executáveis é distribuído entre os processos que trocam tarefas em tempo de execução para balancear trabalho. Cada processo pode enviar ou receber tra- balho dos demais processos. Os parâmetros críticos para um esquema de balanceamento de carga distribuído são os seguintes: • Como estão os processos enviando e recebendo juntos? • A transferência de trabalho é iniciada pelo emissor ou pelo receptor? • Quanto trabalho é transferido em cada troca? Se pouco trabalho é transferido, então o receptor pode não receber trabalho suficiente e transferências freqüentes podem resultar em interação excessiva. Se muito trabalho é transferido, então o emissor pode ficar sem trabalho brevemente, novamente resultando em transferên- cias freqüentes. • Quando a transferência do trabalho será realizada? Por exemplo, no balancea- mento de carga iniciado pelo receptor, o trabalho deve ser requisitado quando o processo tem realmente que executar o trabalho ou quando o receptor tem pouco trabalho e antecipa que faltará trabalho. 70 DCC/UFLA - Programação Paralela e Distribuída 3.3.3 Portabilidade para arquiteturas paralelas Mapeamento centralizado e distribuído podem ser implementados em paradigmas de passagem de mensagens e espaço de endereçamento compartilhado. Como o balance- amento de carga dinâmico implica em movimento de tarefas de um processo para outro, eles funcionam melhor em computadores de passagem de mensagens, onde o tamanho das tarefas em termos de computação deve ser muito maior do que o tamanho dos dados associados com as tarefas. No paradigma de espaço de endereçamento compartilhado, as tarefas não precisam se mover explicitamente, embora haja algum movimento implícito de dados para caches locais ou bancos de memória dos processos. Em geral, a granulosi- dade das tarefas a serem movidas é muito menor do que em arquitetura de passagem de mensagens. 3.4 MÉTODOS PARA CONTENÇÃO DE OVERHEADS DE INTERAÇÕES O overhead que um programa paralelo possui devido a interações entre seus proces- sos depende de muitos fatores, tais como o volume dados trocados durante interações, a freqüência de interação, o padrão de interações espaciais e temporais, etc. As principais técnicas usadas para conter overhead são: • Maximizar localidade de dados; • Minimizar contenção; • Sobrepor computações com interações; • Replicar dados ou computações; • Usar operações de interação coletiva otimizadas; • Sobrepor interações com outras interações. Maximizar localidade de dados: as tarefas executadas por diferentes processos re- querem acesso a alguns dados comuns. O overhead de interações pode ser reduzido usando técnicas que promovem o uso de dados locais ou dados que foram buscados re- centemente. A localidade de dados tenta minimizar o volume de dados não locais que são acessados e maximizar a reutilização de dados recentemente acessados e minimizar a freqüência de acessos. Minimizar o volume de dados trocados: minimizar o volume global de dados que são compartilhados por processos concorrentes. Isto equivale a manter os dados em me- mória local ou cache para que os processos possam executar suas tarefas. Para isso, é necessário usar decomposição e mapeamento apropriados. Outra forma de reduzir os da- dos compartilhados que são acessados por múltiplos processos é usar dados locais para armazenar resultados intermediários e realizar o acesso a dados compartilhados para so- mente colocar os resultados finais da computação. Projeto de Algoritmos Paralelos 71 Minimizar freqüência de interações: reestruturar o algoritmo tal que dados compar- tilhados sejam acessados e usados em grandes pedaços. Isto implica em manter também localidade espacial. Minimizar contenção: contenção ocorre quando múltiplas tarefas tentam acessar o mesmo recurso concorrentemente, múltiplas transmissões de dados sobre um link de inter- conexão, múltiplos acessos simultâneos ao mesmo bloco de memória ou múltiplos proces- sos enviando mensagens ao mesmo processo ao mesmo tempo. A redução da contenção envolve reprojetar o algoritmo para acessar dados de uma forma livre de contenções. Sobrepor computações com interações: a quantidade de tempo que processos gastam esperando por um dado compartilhado chegar ou receber trabalho adicional depois de uma interação ter sido iniciada pode ser reduzida realizando computação útil durante este tempo de espera.Uma forma de sobreposição é iniciar uma interação cedo o suficiente tal que ela se complete antes que seja necessária para computação. Identificar computa- ções que possam ser realizadas antes da interação ou não dependam dela. Isto é possível se o padrão de interação é espacialmente ou temporalmente estático e se múltiplas tarefas que estão prontas para execução estão disponíveis em um mesmo processo tal que se uma tarefa bloqueia para esperar por uma interação completar, o processo pode trabalhar em outra tarefa. Em arquitetura de espaço de endereçamento compartilhado, o hardware com prefetch realiza busca antecipada, adiantando o acesso a endereços de memória que serão acessados em um futuro imediato e pode iniciar o acesso antecipadamente. Um compilador pode também detectar o padrão de acesso e colocar pseudo referências a certas locações de memória antes que estas locações sejam utilizadas pela computação Replicação de dados ou computações: múltiplos processos podem requerer acesso freqüente a estruturas de dados compartilhadas somente leitura. A replicação de uma có- pia da estrutura de dados compartilhada em cada processo faz com que os acessos sub- seqüentes a esta estrutura de dados estão livres de qualquer overhead de interação. No paradigma de programação por passagem de mensagens a replicação reduz o overhead de interação e simplifica a escrita do programa paralelo. No paradigma de espaço de ende- reçamento compartilhado, a replicação de dados acessados para somente leitura é útil em casos onde o acesso a dados compartilhados é mais caro ou mais difícil de expressar do que o acesso a dados locais. Usando operações interativas coletivas otimizadas: as operações de dados com- partilhados coletivas podem ser: operações usadas por tarefas para acessar dados; ope- rações para computações de comunicação intensiva; sincronização. As implementações otimizadas destas operaçõesestão disponíveis em forma de bibliotecas, tais como MPI. Sobrepondo interações com outras interações: se a capacidade de transferir da- dos do hardware permitir, então a sobreposição de interações entre múltiplos pares de processos pode reduzir o volume efetivo de comunicação. Por exemplo, em uma operação 72 DCC/UFLA - Programação Paralela e Distribuída de broadcast um-para-todos com 4 processos, o P0 envia dados para P2. P0 envia dados para P1 concorrentemente P2 envia os mesmo dados para P3 A operação inteira gasta 2 passos. 3.5 MODELOS DE ALGORITMOS PARALELOS Um modelo de algoritmo é uma forma de estruturação de um algoritmo paralelo sele- cionando uma técnica de decomposição e mapeamento e aplicando a estratégia apropriada para minimizar interações. Modelo de dados paralelos (data-parallel model): as tarefas são estaticamente ou semi-estaticamente mapeadas aos processos e cada tarefa realiza operações similares em dados diferentes. Tipo de paralelismo é o resultado de operações idênticas sendo apli- cadas concorrentemente em diferentes itens de dados, sendo denominado paralelismo de dados. O trabalho é realizado em fases e os dados operados em diferentes fases pode ser diferente. Fases de computação de dados paralelos são intercaladas com interações para sincronizar as tarefas ou obter dados mais atuais para as tarefas. Geralmente baseada no particionamento de dados porque um particionamento uniforme de dados seguido por um mapeamento estático é suficiente para garantir balanceamento de carga. Podem ser imple- mentados para os paradigmas de espaço de endereçamento compartilhado e passagem de mensagens. Modelo grafo de tarefas: o interrelacionamento entre as tarefas é utilizado para pro- mover localidade ou reduzir o custo de interação. Modelo utilizado para resolver problemas em que a quantidade de dados associada com as tarefas é grande relativo à quantidade de computação associada a elas. Geralmente, as tarefas são mapeadas estaticamente para ajudar a otimizar o custo de movimentação de dados entre as tarefas. As técnicas de re- dução de interação aplicáveis a este modelo incluem redução do volume e freqüência da interação pela promoção de localidade durante o mapeamento das tarefas, baseado no pa- drão de interação das tarefas e usando métodos de interação assíncronos para sobrepor a interação com computação. Modelo pool de trabalho: caracterizado pelo mapeamento dinâmico de tarefas aos processos para balanceamento de carga em que qualquer tarefa pode potencialmente ser executada por qualquer processo O mapeamento pode ser centralizado ou descentralizado Ponteiros para as tarefas podem ser armazenados em listas compartilhadas fisicamente, fila de prioridade, hash table ou árvore ou em estruturas de dados distribuídas fisicamente. O trabalho pode estar estaticamente disponível no início ou pode ser gerado dinamicamente, ou seja, os processos podem gerar trabalho e adicioná-lo ao pool de trabalho global Se o trabalho for gerado dinamicamente e o mapeamento for descentralizado então é necessário Projeto de Algoritmos Paralelos 73 um algoritmo de detecção de terminação. O paradigma de passagem de mensagens usa o modelo pool de trabalho. Modelo mestre-escravo (master-slave ou manager-worker): um ou mais processos mestre gerenciam o trabalho e aloca aos processos trabalhadores. As tarefas podem ser alocadas a priori, se o mestre puder estimar o tamanho das tarefas ou se um mapeamento aleatório pode ser feito por um job adequado de balanceamento de carga. Em outro cená- rio, os trabalhadores podem receber pedaços menores de trabalho em instantes diferentes. Este modelo pode ser generalizado para o modelo hierárquico ou modelo mestre-escravo multinível onde o gerenciador de nível mais alto atribui largos pedaços de tarefa para os ge- renciadores do segundo nível, que por sua vez subdividem a tarefa entre seus trabalhadores e pode também desempenhar parte do trabalho. Usado tanto em espaço de endereçamento compartilhada quanto em passagem de mensagens. Modelo pipeline ou produtor-consumidor: o fluxo de dados é passado para uma sucessão de processos que realizam alguma tarefa sobre eles. A execução simultânea de programas diferentes sobre um fluxo de dados é denominada paralelismo de fluxo. Com ex- ceção do processo inicial do pipeline, a chegada de novos dados é disparada pela execução de uma nova tarefa por um processo no pipeline Um pipeline é uma cadeia de produtores e consumidores. Cada processo no pipeline pode ser visto como um consumidor de uma seqüência de itens para processos precedentes a ele no pipeline e como um produtor de dados para processos seguintes no pipeline. O pipeline não precisa ser uma cadeia linear, pode ser um grafo orientado Usa mapeamento estático de tarefas aos processos. Modelos híbridos: pode ser composto de múltiplos modelos aplicados hierarquica- mente ou múltiplos modelos aplicados seqüencialmente para diferentes fases de um algo- ritmo paralelo. 3.6 EXERCÍCIOS 1. Dada uma tarefa que pode ser dividida em m subtarefas, cada subtarefa requer 1 unidade de tempo, quanto tempo é necessário para um pipeline de m estágios processar n tarefas? 2. Suponha que um vetor de 10.000 posições será alocado a 20 processadores de forma cíclica, ou seja, o primeiro elemento será alocado ao primeiro processador, o segundo elemento será alocado ao segundo processador e assim por diante. a) Quantos elementos serão processados por cada processador? b) Que processo é responsável pelo pedaço de trabalho j, onde 0 £ j <10.000? Considere que os processadores são numerados a partir de 0. 3. Para o problema da questão anterior, se a alocação de trabalho aos processadores for em blocos (subvetores), ou seja, o primeiro bloco alocado ao primeiro proces- 74 DCC/UFLA - Programação Paralela e Distribuída sador, o segundo bloco ao segundo processador e assim por diante, como ficariam as respostas das letras a) e b)? 4. Considerando os grafos de tarefas da Figura 3.11, determine o seguinte: a)grau máximo de concorrência b)speedup máximo alcançado sobre um processo, assu- mindo que um número arbitrariamente grande de processos está disponível c)o número mínimo de processos necessários para obter o speedup máximo possível d)o speedup máximo alcançável se o número de processos é limitado a (a) 2, (b) 4 e (c) 8. 5. O grafo de tarefas mostrado na Figura 3.10 representa uma aplicação de processa- mento de imagens. Cada círculo representa um tarefa seqüencial. Há 12 tarefas: uma tarefa de entrada, 10 tarefas de computação e uma tarefa de saída. Cada uma das 12 tarefas podem ser realizada em uma unidade de tempo do processador. A tarefa de entrada deve ser completada antes de qualquer tarefa computacional co- meçar. Todas as 10 tarefas computacionais devem completar antes da tarefa de saída começar. A tarefa de entrada consome a largura de banda inteira do disposi- tivo de entrada. A tarefa de saída consome a largura de banda inteira do dispositivo de saída. (a) Qual o speedup máximo que pode ser alcançado se este problema for resolvido em 2 processadores? (b) Qual é o limite superior do speedup que pode ser alcançado se este pro- blema for resolvido com processadores paralelos? (c) Qual é o menor número de processadores necessário para alcançar o spe- edup na parte (b)? (d) Qual é o speedup máximo que pode ser alcançado para resolver cinco instâncias deste problema em 2 processadores? Continue a assumir que há somente um dispositivo de entrada e somente um dispositivo de saída. 6. Considere os grafos de tarefas da Figura 3.11 e assuma que cada tarefa gasta uma unidade de tempo para ser executada e o tempo de comunicação entre os proces- sadores é zero. Determine: i)o número mínimo de processosnecessários para obter o speedup máximo possível; ii)o speedup máximo alcançável se o número de processos é limitado a (a) 2, (b) 4 e (c) 8. Projeto de Algoritmos Paralelos 75 Figura 3.10: Grafos de Tarefas do Exercício 2 Figura 3.11: Grafos de Tarefas do Exercício 3 76 DCC/UFLA - Programação Paralela e Distribuída
Compartilhar