padrões e frameworks de programação paralela em … · padrões paralelos e padrões de projeto...

55
Pontifícia Universidade Católica do Rio Grande do Sul Faculdade de Informática Programa de Pós-Graduação em Ciência da Computação Padrões e Frameworks de Programação Paralela em Arquiteturas Multi-Core Dalvan Griebler Orientador: Luiz Gustavo Fernandes Relatório Técnico N o 064 Porto Alegre, Janeiro de 2011

Upload: dangmien

Post on 28-Jan-2019

238 views

Category:

Documents


0 download

TRANSCRIPT

Pontifícia Universidade Católica do Rio Grande do SulFaculdade de Informática

Programa de Pós-Graduação em Ciência da Computação

Padrões e Frameworks de Programação Paralelaem Arquiteturas Multi-Core

Dalvan Griebler

Orientador: Luiz Gustavo Fernandes

Relatório Técnico No 064

Porto Alegre, Janeiro de 2011

Resumo

A computação paralela vem crescendo rapidamente em termos de desempenho,um dos adventos é o modelo arquitetural multi-core. Entretanto, não bastam estardisponíveis tais arquiteturas multiprocessadas, se estas não são exploradas devida-mente. A maior parte dos programadores de código sequencial se negam a dedicarseus esforços para trabalhar com este paradigma, pela complexidade apresentadano tratamento de problemas. Depuração, condições de corrida, sincronização dethreads ou processos e controle de acesso aos dados são exemplos de fatores crí-ticos para estes ambientes paralelos. Novas maneiras de abstrair a complexidadeem lidar com estes sistemas estão sendo estudadas, a fim de que a programaçãoparalela seja algo comum para os desenvolvedores de software. Neste sentido, oobjetivo deste trabalho é estudar e fazer um levantamento teórico sobre os meca-nismos utilizados para melhorar a programação paralela em ambientes multi-core.Padrões paralelos vêm sendo o alvo de constantes estudos com intuito de padro-nizar a programação paralela. A padronização foi um dos primeiros passos emdireção a melhores implementações para o espaço de projeto de algoritmos para-lelos. No entanto, uma implementação eficiente com estes padrões nem sempre épossível. Depende do programador se a modelagem e os cuidados com relação aoacesso de dados foram corretamente tratados. Padrões paralelos determinísticostornaram-se o mais atual objeto de pesquisa no cenário de programação paralela(multi-core). O propósito deles é possíbilitar o desenvolvimento de programaseficientes mantendo uma única ordem na execução, similar ao que se tem nos al-goritmos sequenciais. Além disso, fornecem determinismo na execução dos pro-gramas eliminando a necessidade de condições de corrida. O desenvolvimento deframeworks passou a ser uma solução para ajudar os programadores nesta tarefa,abstraindo parte da complexidade em lidar com os problemas frequentes, visandorapidez e facilidade na aprendizagem e desenvolvimento de programas paralelos.

Lista de Figuras

2.1 Encontrando Concorrência. Adaptado de [1] . . . . . . . . . . . . 122.2 Estruturas de Algoritmos. Adaptado de [1]. . . . . . . . . . . . . 162.3 Estruturas de Apoio. Adaptado de [1] . . . . . . . . . . . . . . . 212.4 Mecanismos de Implementação. Adaptado de [1] . . . . . . . . . 28

3.1 Map Parallel Pattern. . . . . . . . . . . . . . . . . . . . . . . . . 313.2 Reduction Parallel Pattern. . . . . . . . . . . . . . . . . . . . . . 323.3 Superscalar Sequences Parallel Pattern. . . . . . . . . . . . . . . 333.4 Pipeline Parallel Pattern. . . . . . . . . . . . . . . . . . . . . . . 343.5 Nesting Parallel Pattern. . . . . . . . . . . . . . . . . . . . . . . 343.6 Scan Parallel Pattern. . . . . . . . . . . . . . . . . . . . . . . . . 353.7 Recurrences Parallel Pattern. . . . . . . . . . . . . . . . . . . . . 363.8 Partition Parallel Pattern. . . . . . . . . . . . . . . . . . . . . . . 363.9 Speculation Selection Parallel Pattern. . . . . . . . . . . . . . . . 373.10 Gather Parallel Pattern. . . . . . . . . . . . . . . . . . . . . . . . 383.11 Search Parallel Pattern. . . . . . . . . . . . . . . . . . . . . . . . 393.12 Stencil Parallel Pattern. . . . . . . . . . . . . . . . . . . . . . . . 393.13 Scatter Parallel Pattern. . . . . . . . . . . . . . . . . . . . . . . . 403.14 Permutation Scatter Parallel Pattern. . . . . . . . . . . . . . . . . 403.15 Atomic Scatter Parallel Pattern. . . . . . . . . . . . . . . . . . . . 413.16 Merge Scatter Parallel Pattern. . . . . . . . . . . . . . . . . . . . 413.17 Priority Scatter Parallel Pattern. . . . . . . . . . . . . . . . . . . . 423.18 Pack Parallel Pattern. . . . . . . . . . . . . . . . . . . . . . . . . 433.19 Expand Parallel Pattern. . . . . . . . . . . . . . . . . . . . . . . . 433.20 Split Parallel Pattern. . . . . . . . . . . . . . . . . . . . . . . . . 44

4.1 Arquitetura FastFlow. Extraído de [2]. . . . . . . . . . . . . . . . 454.2 Intel Parallel Studio. Extraído de [3]. . . . . . . . . . . . . . . . . 47

1

Lista de Tabelas

2.1 Relacionamento entre os padrões de estruturas de apoio e ambi-entes de programação. Extraído de [1] . . . . . . . . . . . . . . . 27

2.2 Relacionamento entre os padrões de estruturas de apoio e pa-drões de estrutura algoritmos. Extraído de [1] . . . . . . . . . . . 27

2

Lista de Símbolos e Abreviaturas

SPMD Single Process, Multiple Data 11SIMD Single Instruction, Multiple Data 11NUMA No Uniform Memory Access 13MPI Message Passing Interface 21OpenMP Open MultiProcessor 21

3

Sumário

LISTA DE FIGURAS 1

LISTA DE TABELAS 2

LISTA DE SÍMBOLOS E ABREVIATURAS 3

Capítulo 1: Introdução 71.1 Motivação e Objetivos . . . . . . . . . . . . . . . . . . . . . . . 81.2 Organização do Trabalho . . . . . . . . . . . . . . . . . . . . . . 9

Capítulo 2: Padrões de Programação Paralela 102.1 Encontrando Concorrência . . . . . . . . . . . . . . . . . . . . . 11

2.1.1 Padrão de Decomposição de Tarefas . . . . . . . . . . . . 122.1.2 Padrão de Decomposição de Dados . . . . . . . . . . . . 132.1.3 Padrão de Grupo de Tarefas . . . . . . . . . . . . . . . . 132.1.4 Padrão de Ordenção de Tarefas . . . . . . . . . . . . . . . 142.1.5 Padrão de Compartilhamento de Dados . . . . . . . . . . 142.1.6 Padrão de Avaliação de Projeto . . . . . . . . . . . . . . . 15

2.2 Estrutura de Algoritmo . . . . . . . . . . . . . . . . . . . . . . . 152.2.1 Padrão de Paralelismo de Tarefas . . . . . . . . . . . . . 172.2.2 Padrão de Divisão e Conquista . . . . . . . . . . . . . . . 182.2.3 Padrão de Decomposição Geométrica . . . . . . . . . . . 182.2.4 Padrão de Recursão de Dados . . . . . . . . . . . . . . . 182.2.5 Padrão de Pipeline . . . . . . . . . . . . . . . . . . . . . 192.2.6 Padrão de Coordenação Baseada em Eventos . . . . . . . 20

2.3 Estruturas de Apoio . . . . . . . . . . . . . . . . . . . . . . . . . 212.3.1 Padrão SPMD (Single Program, Multiple Data) . . . . . . 222.3.2 Padrão Mestre/Escravo . . . . . . . . . . . . . . . . . . . 222.3.3 Padrão de Paralelismo de Laço . . . . . . . . . . . . . . . 23

4

2.3.4 Padrão Fork/Join . . . . . . . . . . . . . . . . . . . . . . 242.3.5 Padrão de Compartilhamento de Dados . . . . . . . . . . 242.3.6 Padrão de Compartilhamento de Fila . . . . . . . . . . . . 262.3.7 Padrão de Array Distribuído . . . . . . . . . . . . . . . . 262.3.8 Escolhendo Padrões para o Projeto de Algoritmos Paralelos 27

2.4 Mecanismos de Implementação . . . . . . . . . . . . . . . . . . . 282.4.1 Gerenciamento de Threads e Processos . . . . . . . . . . 282.4.2 Sincronização . . . . . . . . . . . . . . . . . . . . . . . . 282.4.3 Comunicação . . . . . . . . . . . . . . . . . . . . . . . . 29

Capítulo 3: Programação Paralela com Padrões Estrutura-dos e Determinísticos 30

3.1 Padrões de Computação Paralela . . . . . . . . . . . . . . . . . . 313.1.1 Map Pattern . . . . . . . . . . . . . . . . . . . . . . . . . 313.1.2 Reduction Pattern . . . . . . . . . . . . . . . . . . . . . . 323.1.3 Superscalar Sequences Pattern . . . . . . . . . . . . . . . 333.1.4 Pipeline Pattern . . . . . . . . . . . . . . . . . . . . . . . 343.1.5 Nesting Pattern . . . . . . . . . . . . . . . . . . . . . . . 343.1.6 Scan Pattern . . . . . . . . . . . . . . . . . . . . . . . . 353.1.7 Recurrences Pattern . . . . . . . . . . . . . . . . . . . . 363.1.8 Partition Pattern . . . . . . . . . . . . . . . . . . . . . . . 363.1.9 Speculative Selection Pattern . . . . . . . . . . . . . . . . 37

3.2 Padrões de Gerenciamento de Dados Paralelos . . . . . . . . . . . 373.2.1 Gather Pattern . . . . . . . . . . . . . . . . . . . . . . . 383.2.2 Search Pattern . . . . . . . . . . . . . . . . . . . . . . . 383.2.3 Stencil Pattern . . . . . . . . . . . . . . . . . . . . . . . 393.2.4 Scatter Pattern . . . . . . . . . . . . . . . . . . . . . . . 403.2.5 Permutation Scatter Pattern . . . . . . . . . . . . . . . . . 403.2.6 Atomic Scatter Pattern . . . . . . . . . . . . . . . . . . . 413.2.7 Merge Scatter Pattern . . . . . . . . . . . . . . . . . . . . 413.2.8 Priority Scatter Pattern . . . . . . . . . . . . . . . . . . . 423.2.9 Pack Pattern . . . . . . . . . . . . . . . . . . . . . . . . . 423.2.10 Expand Pattern . . . . . . . . . . . . . . . . . . . . . . . 433.2.11 Split Pattern . . . . . . . . . . . . . . . . . . . . . . . . . 44

Capítulo 4: Frameworks para Programação Paralela Multi-Core 45

4.1 FastFlow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454.2 TBB (Threading Bulding Blocks) . . . . . . . . . . . . . . . . . . 46

5

4.3 SWARM (SoftWare and Algorithms for Running on Multicore) . . 474.4 Intel Parallel Studio . . . . . . . . . . . . . . . . . . . . . . . . . 47

Capítulo 5: Conclusão 49

6

1 Introdução

A indústria de processadores tem como objetivo melhorar o desempenho com-putacional através do emprego de diversas formas de paralelismo. Para tanto,hardwares paralelos estão presentes nos principais equipamentos ou plataformascomputacionais atuais. Estações de trabalho, servidores, supercomputadores, ouaté mesmo sistemas embarcados fazem parte desta exploração de paralelismo paraprover maior desempenho em suas respectivas plataformas.

Esta abordagem de baixo nível de paralelismo direciona o desenvolvedor paramecanismos de programação mais complexos e com pouca portabilidade. So-mente um pequeno número de programadores encara este desafio, a grande mai-oria não se sujeita a aprender e trabalhar neste nível. Até mesmo programadoresprofissionais tentam ignorar a programação paralela, pelos problemas que estaabordagem necessita tratar nos mais diversos ambientes de arquiteturas.

Computadores em um contexto geral já suportam multithreading ou multi-core. A decomposição de tarefas e de dados é uma forma de abstrair a comple-xidade na exploração de paralelismo destes processadores. Sendo que o escalo-namento das threads ou processos é de responsabilidade do sistema operacionalrealizar a distribuição da carga entre os elementos de processamento.

Mesmo havendo esta abstração, desenvolver programas paralelos não é umatarefa trivial. Implica que o programador entenda da arquitetura alvo para quesejam possíveis maiores níveis de escalabilidade e eficiência. Além disso, os am-bientes arquiteturais estabelecem diferentes formas e abordagens de paralelismo,desde o controle de acesso a dados para o nível de memória compartilhada até acomunicação entre processos em memória distribuída.

Padrões paralelos e padrões de projeto auxiliam o programador na análise derequisitos e nos cuidados em relação a programabilidade dos algoritmos parale-los. Padrões de projeto direcionam o desenvolvimento de programas para umadas melhores soluções paralelas, baseados em uma metodologia. Enquanto isso,padrões paralelos, especificam e definem a estrutura algorítmica, tratando as ques-tões de acesso aos dados e os detalhes do ambiente arquitetural a ser paralelizado,provendo maior eficiência na utilização dos recursos disponíveis.

Enquanto interfaces de programação implementam alguns padrões paralelos,

7

alguns padrões são usados para fazer a modelagem do programa. Com as interfa-ces de programação disponíveis, é possível abstrair parte dos detalhes de progra-mação paralela para a exploração de paralelismo em ambientes computacionais.Entretanto, a programação neste contexto ainda não é um atrativo ou simples-mente motiva os programadores a implementar softwares para estas arquiteturas,pois exigem que o programador domine os conceitos da programação paralela edos detalhes de implementação.

Um dos problemas está na dificuldade de se programar em paralelo. Pensarem paralelo exige maior esforço do programador para tratar os problemas de coe-rência e sincronização, para os quais, a depuração é um processo bastante custoso.Sendo esta uma das fases que exige mais do programador, pois este procedimentoé manual e poucas ferramentas ou interfaces fornecem mecanismos de depuraçãode código inteligentes para a detecção rápida das inconsistências do programa.

Outro problema está na exploração do paralelismo disponível. Os padrõesparalelos e padrões de projetos utilizados na programação até o momento, nãoproporcionam mecanimos bons o suficiente, a ponto de explorar o máximo deparalelismo. Isso porquê, em programas paralelos podem ocorrer dependênciascausais, estas podem gerar uma sobrecarga de acesso aos dados ou na sincroniza-ção de processos ou threads.

1.1 Motivação e ObjetivosO recente mercado de estações de trabalho e servidores vem aumentando gradati-vamente a quantidade de núcleos e processadores, elevando ainda mais o parale-lismo e a complexidade em lidar com estas arquiteturas de hardware. É necessárioa disponibilidade de mecanismos que estejam preparados para fornecer escalabili-dade e explorar o máximo de paralelismo possível para estas arquiteturas, conhe-cidas como multi-core.

Como um dos problema enfrentados na atualidade por programadores refere-se à complexidade em desenvolver programas paralelos, uma abordagem estru-turada tende a ser útil para reduzir a complexidade no desenvolvimento de pro-gramas, particularmente relevante para arquiteturas multi-core, onde existe umagrande potencial de paralelismo.

Entretanto, para tornar os programas paralelos mais confiáveis e eficientes,podem ser usados padrões de algoritmos determinísticos. Sendo que estes, en-quanto melhoram a produtividade através de padrões espertos, específicos e dacombinação deles, também podem direcionar os desenvolvedores inexperientes eprofissionais a desenvolverem softwares com maior eficiência e escalabilidade.

Além disso, é possível melhorar a exploração de paralelismo nos algoritmos,pois alguns padrões determinísticos, se implementados corretamente, podem eli-

8

minar os gargalos e a sobrecarga de programas paralelos. Isso porquê, padrõesdeterminísticos oferecem determinismo e consistência através de uma única or-dem de execução.

Tendo conhecimento do funcionamento dos padrões específicos, uma das pos-sibilidades é fazer a combinação deles, a fim de resultar em soluções eficientespara direcioná-los em aplicações específicas, isso pode ser feito usando padrõesestruturados determinísticos ou não determinísticos.

Padrões paralelos contribuem na padronização do desenvolvimento de algorit-mos paralelos. Estes são uma forma de abstrair parte da complexidade, podendoservir de suporte para a criação de frameworks com alto nível de abstração, simpli-ficando na programabilidade de algoritmos paralelos como um todo. Sendo estauma das grandes necessidades dos programadores nos dias de hoje.

Com base na motivação e nos problemas elencados até então, o trabalho tempor objetivo realizar um estudo sobre o projeto de algoritmos paralelos, padrõesparalelos e um breve levantamento de alguns frameworks para ambientes de pro-gramação multi-core.

1.2 Organização do TrabalhoEste trabalho está organizado em sete capítulos: O Capítulo 1, faz uma introduçãodos atuais problemas encontrados para arquiteturas de processadores multi-coree as motivações para a realização de pesquisas nesta área. Em seguida é dadoinício ao contexto de padrões de programação paralela no Capítulo 2, descrevendoas fases ou espaços de projeto de algoritmos paralelos, bem como os padrõesutilizados em um contexto geral de algoritmos paralelos.

No Capítulo 3 são estudados os padrões de programação paralela estruturae determinística, onde são abordados essencialmente os padrão que resolvem osproblemas mais frequentes em ambientes multi-core. Para fins de conheciemen-tos da disponibilidade de frameworks, o capítulo 4 traz alguns dos principais fra-meworks utilizados na programação multi-core. Ao final, o capítulo 5 relata asconclusões sobre o presente o trabalho.

9

2 Padrões de ProgramaçãoParalela

Um padrão de programação é uma solução genérica para problemas encontradosfrequentemente na computação [4]. Padrões de programação apareceram com osurgimento das primeiras linguagens de programação no fim da década de 60/iní-cio da década de 70 [5].

Os padrões sequenciais são tradicionalmente divididos em dois grupos: pa-drões de controle de fluxo e padrões de gerenciamento de dados. Dentre os pa-drões de controle de fluxo destacam-se: sequência, seleção, iteração e recursão.Os padrões de gerenciamento de dados mais usados são: leitura de acessos alea-tório, alocação de pilha e alocação dinâmica de memória.

Alto desempenho e paralelismo necessitam que programadores entendam daarquitetura de software para que possam identificar tarefas independentes e da-dos em aplicações. Nesse cenário, resolver ou organizar pedaços de código emuma implementação paralela é uma tarefa difícil. No entanto, a exploração de es-tratégias de paralelismo tem motivado a obtenção de desempenho em programascomputacionalmente intensivos. A utilização de padrões de programação paralelasurge como uma alternativa viável para auxiliar no desenvolvimento de programasparalelos.

Os padrões de programação são descritos ou implementados a partir de umpadrão de linguagem. A linguagem ajuda explorar várias abordagens para a com-putação levando à solução de problemas encontrados enquanto é desenvolvido umsoftware paralelo [6].

Os principais padrões de linguagem são compostos de:

• Padrões Estruturados: descrevem a estrutura global de uma computação,onde incluem-se padrões como, pipe, filtros, agente e repositórios, map,reduction, grafos de tarefas estáticas, entre outros.

• Padrões Computacionais: descrevem várias classes importantes da com-putação que surgem da computação intensiva, como álgebra linear e bar-reira.

10

• Padrões de Estratégia Algoritmica: descrevem a maneira de decompor acomputação dentro de unidades paralelas, nos quais são incluídos parale-lismo de dados, especulação e paralelismo em pipeline.

• Padrões de Estratégia de Implementação: descrevem a maneira de imple-mentar a computação paralela e suas correspondentes estruturas de dados,como paralelismo de laço, SPMD (Single Process, Multiple Data), mestre-escravo e compartilhamento de fila.

• Padrões de Execução Concorrente: descreve a partir do mais baixo níveldo padrão de linguagem, maneira na qual existe a integração com hardwareparalelo. Por exemplo, SIMD (Single Instruction, Multiple Data), pool dethreads e troca de mensagens.

Com o uso de padrões de linguagem, torna-se mais fácil o entendimento deaplicações que são escritas de forma paralela, e também na construção de ferra-mentas que auxiliam na exploração do paralelismo. Para tanto, frameworks podemser criados a partir destes padrões, com a finalidade de facilitar aos programadoresa implementação de software paralelo [1].

Este capítulo descreve as quatros fases de desenvolvimento de programas pa-ralelos. A primeira fase, referece-se ao espaço de projeto Encontrando Concor-rência, onde o programador trabalha em um problema para identificar a concor-rência disponível, usando esta para o projeto de algoritmo paralelo.

Na segunda fase, trata do espaço de projeto Estrutura de Algoritmo, no qualo programador trabalho em um alto nível para organizar um algoritmo paralelo.Em seguida, na terceira fase, o espaço de projeto Estruturas de Apoio, trata decomo os programas paralelos podem ser organizados e as técnicas usadas para ogerenciamento de dados compartilhados.

A quarta e última fase de desenvolvimento, o espaço de projeto Mecanismosde Implementação, observa as construções de um software específico para im-plementar um programa paralelo.

O estudo sobre estas fases de projeto baseia-se principalmente nos autores de[6], [7], [8], [9], [10], [1], [11], [4], [12], [13], [14] e [15], para descrever osconceitos no decorrer deste capítulo.

2.1 Encontrando ConcorrênciaO desenvolvimento de algoritmos paralelos não é uma tarefa simples e fácil. Antesde modelar um algoritmo paralelo, deve-se fazer o levantamento da aplicação,identificando o problema a ser resolvido. Se paralelizar passa a ser vantajoso,pode-se estudar quais padrões paralelos podem ser implementados na aplicação.

11

Figura 2.1: Encontrando Concorrência. Adaptado de [1]

Assim, o primeiro passo do projeto de algoritmo paralelo começa a partir dospadrões de concorrência. A Figura 2.1 ilustra três grupos de padrões correspon-dentes a este espaço de projeto.

O grupo de padrões de decomposição tem por objetivo quebrar um problemaem pedaços de forma a executar concorrentemente. Os padrões de análise dedependência operam entre eles e possivelmente podem revisar os padrões de de-composição. Já o grupo de padrões de avaliação de projeto direcionam o projetode algoritmo para uma avaliação (testando se o melhor padrão foi aplicado), antesde encaminhar para o próximo espaço de projeto.

2.1.1 Padrão de Decomposição de TarefasNo padrão de decomposição de tarefas, o problema é visto como um fluxo de ins-truções que podem ser quebrados em tarefas que são executadas concorrentemente[10]. O programador deverá identificar quais são os pontos (que geram tarefas) aserem paralelizados no algoritmo, levando em conta os seguintes aspectos:

• Flexibilidade: permitir que o número e o tamanho de tarefas possa ser pa-rametrizado para diferentes números de processadores.

• Eficiência: garantir que não ocorra overhead pelo gerenciamento de depen-dência das tarefas, e que o número de tarefas não seje maior que o númerode unidades de processamento.

• Simplicidade: tornar possível a depuração e a manutenção o mais simplespossível.

O padrão de decomposição de tarefas pode ser usado em aplicações como:imagem médica; multiplicação de matrizes e dinâmica molecular.

12

2.1.2 Padrão de Decomposição de DadosA decomposição de dados foca nos dados exigidos pelas tarefas e como pode serdecomposto em pedaços separados. Isso só é eficiente se os pedaços de dadospodem operar independentemente.

Algoritmos de decomposição de dados fazem o uso eficiente da memória e(em ambientes com memória distribuída) usam menor largura de banda. Mas,ocorre maior sobrecarga de comunicação durante a execução de partes concorren-tes, sendo também mais complexa a sua decomposição. Máquinas NUMA (NoUniform Memory Access) se mostram vantajosas na decomposição de dados [8].

Ao começar a decomposição de dados, deve-se identificar as estruturas dedados definidas pelo problema e verificar o que pode ser quebrado em pedaços,que podem executar concorrentemente. Por exemplo, computação baseada emvetores (quebrar em segmentos o vetor) e estrutura de dados recursiva (decomporem sub-árvores) [1]. Para isso, é necessário levar em conta os seguintes aspectos:

• Flexibilidade: o tamanho e o número de pedaços de dados devem ser fle-xíveis, de maneira a suportar um sistema paralelo amplo, onde a partir daparametrização define-se a granularidade, sendo que esta varia de acordocom o hardware.

• Eficiência: a decomposição torna-se eficiente quando a quantidade de pe-daços não causa a sobrecarga no gerenciamento de dependência. Para tanto,um algoritmo paralelo deve balancear a carga entre as threads ou processos.

• Simplicidade: decompor dados é complexo, o que torna a depuração difí-cil. Esta decomposição geralmente requer um mapeamento global dentro deuma tarefa local. Faz-se este mapeamento abstrato a fim de isolar e testar.

A decomposição de dados também pode ser realizada em aplicações como:imagem médica, multiplicação de matrizes e dinâmica molecular. Sendo que agranularidade é mais fina se comparado com a decomposição de tarefas.

2.1.3 Padrão de Grupo de TarefasEste padrão corresponde aos padrões de decomposição de dados e tarefas. Noentanto, descreve a primeira etapa na análise de dependências.

Um aplicação pode ser dividida em problemas, onde cada problema se utilizade tarefas para resolvê-lo. Para isso, é possível criar grupos de tarefas que sãoagrupados correspondentes a seus devidos problemas. Mostra-se benéfico na aná-lise de dependência das tarefas, pois o controle passa a ser realizado nos grupos(quando um grupo termina um outro grupo pode começar a ler). Caso um grupo de

13

tarefas trabalhe junto com uma estrutura de dados compartilhada, a sincronizaçãoé necessária em todo grupo.

Se existem restrições entre as tarefas, podem ocorrer casos como: dependên-cia temporal; coleção de tarefas executam ao mesmo tempo e tarefas podem serindependentes umas das outras dentro do grupo [12].

Para identificar as restrições e grupos de tarefas, deve-se olhar primeiramentecomo o problema original foi decomposto, em seguida verificar se outro grupocompartilha a mesma restrição, e ao final, identifica-se as restrições entre os gru-pos de tarefas [1].

2.1.4 Padrão de Ordenção de TarefasA ordenação de tarefas consiste na segunda etapa de análise de dependências,objetivando-se a identificar como os grupos de tarefas podem ser ordenados paraatender as retrições entre as tarefas. Para tanto, a ordenação deve ser restrita osuficiente para satisfazer todos os requisitos que resultam em um projeto correto.Também, a ordenação não deve ser mais restrita que ela precisa ser.

De forma a contribuir na ordenação das restrições, é importante observar anecessidade do dado por um grupo de tarefas, antes de ele poder executar. Tam-bém podem ser considerados serviços externos que promovem a ordenação derestrições e notar quando uma ordenação não existe [6].

2.1.5 Padrão de Compartilhamento de DadosUm algoritmo paralelo consiste basicamente de: uma coleção de tarefas que exe-cutam concorrentemente, uma decomposição de dados correspondente a uma so-lução de tarefas concorrentes e também sobre a dependência entre as tarefas, quedevem ser gerenciadas para permitir uma execução segura [1].

O objetivo deste padrão é o compartilhamento de dados entre os grupos detarefas e determinar o acesso de dados compartilhados de maneira correta e efici-ente. Para garantir estes fatores, o algoritmo deve prever questões como: condiçãode corrida, geração de sincronização excessiva e overhead de comunicação (emsistemas distribuídos).

Em um primeiro instante, os dados são compartilhados entre as tarefas (pa-drão de decomposição de dados), no qual, os dados são decompostos em blocose o compartilhamento de dados é realizado entre estes blocos. No padrão de de-composição de tarefas se torna mais complicado, pois é necessário tratar os dadospassados dentro ou fora da tarefa e tratar quando um dado é atualizado no corpoda tarefa.

O programador fica responsável por conhecer as formas de uso do compar-tilhamento de dados, para as quais inclui-se: somente leitura, eficazmente local

14

(operações com matrizes), leitura e escrita, acumulação e múltipla leitura/somenteescrita.

2.1.6 Padrão de Avaliação de ProjetoEste padrão consiste na última etapa para encontrar concorrência, visando pre-parar o programa para o próximo espaço de projeto [6]. A decomposição doproblema original e análise pode ser feita com: uma decomposição de tarefas queidentifica tarefas que podem executar concorrentemente; uma decomposição dedados que identifica o local dos dados para cada tarefa; uma maneira de agru-pamento de tarefas e ordenação de grupos para satisfazer restrições temporais, eanálise de dependências entre tarefas.

Entretanto, a partir destes termos o padrão se objetiva a encontrar qual é amelhor decomposição do problema para produzir um projeto de ótima qualidade,baseando-se na avaliação de três aspectos:

• Adequação para a plataforma alvo: para a escolha da plataforma alvo, de-vem ser levados em conta a quantidade de processadores disponíveis, comosão compartilhadas as estruturas de dados entre os elementos de processa-mento, identificar como a arquitetura alvo implica sobre o número de thre-ads ou processos e como as estruturas de dados são compartilhadas entreelas, e ao final, analizar o tempo gasto trabalhando em uma tarefa, sendoque o tempo deve ser ótimo quando for lidar com dependências.

• Qualidade de projeto: a qualidade de projeto requer que sejam mantidosem mente as características da plataforma alvo, avaliando as dimensões deflexibilidade, eficiência e simplicidade.

• Preparação para a próxima fase de projeto: o projetista quando se depa-rar com avaliação deste aspecto, deve considerar a regularidade das tarefase suas dependências de dados, interação entre as tarefas (síncrono ou assín-crono) e se as tarefas são agrupadas da melhor maneira.

2.2 Estrutura de AlgoritmoUma estrutura de algoritmo requer eficiência, simplicidade, portabilidade e esca-labilidade [1]. Porém, estes termos causam conflitos, como por exemplo, eficiên-cia com portabilidade.

Alguns programas necessitam ser escritos com características específicas, oque não torna possível a portabilidade. Outro caso de conflito é eficiência com

15

simplicidade, pois geralmente se usa paralelismo de tarefas, para o qual, em al-guns casos é necessário o uso de algoritmos de escalonamento complexos, quedificultam o entendimento. No entanto, um bom projeto de algoritmo deve esta-belecer um balanço entre abstração e portabilidade para uma determinada arqui-tetura alvo.

Figura 2.2: Estruturas de Algoritmos. Adaptado de [1].

A Figura 2.2 demonstra a organização do espaço de projeto, sendo este orga-nizado em três grupos de padrões paralelos. O princípio de organização implicana concorrência, na qual são descritos termos de tarefas e grupos de tarefas, dadose ordenação entre grupos de tarefas.

A fim de encontrar um algoritmo estruturado que representa como este mape-amento de concorrência é realizado dentro das threads ou processos. O projeto dealgoritmo paralelo faz o uso de múltiplas estruturas de algoritmos, os quais estãodividido nos seguintes grupos:

• Organização por Tarefa: possui duas alternativas quando a execução detarefas é a melhor organização. Uma é o padrão de paralelismo de tarefas,usado quando é possível juntar em um conjunto linear em qualquer númerode dimensões. A segunda alternativa é o padrão de divisão e conquista,utilizado em casos onde as tarefas são enumeradas por um procedimentorecursivo.

• Organização por Decomposição de Dados: a escolha deste grupo parte doprincípio que o mesmo deve ter entendimento da concorrência, visando opadrão de decomposição geométrica (processamento linear e bem aplicadopara paralelismo em computação baseada em grades) e o padrão de recursãode dados (recursão de estruturas de dados, i.e árvore binária).

• Organização por Fluxo de Dados: consiste na forma em como o fluxo dedados é imposto na ordenação em grupo de tarefas. Para tanto, pode serescolhido o padrão pipeline, quando o fluxo de dados entre os grupos detarefas é regular, o sentido é em uma direção e não muda no decorrer do

16

algoritmo. Para o padrão de coordenação baseado em eventos o fluxo dedados é irregular, dinâmico e imprevisível.

Nas próximas subseções será detalhado cada um dos padrões contidos nosgrupos de organização por tarefas, organização por decomposição de dados e or-ganização por fluxo de dados, conforme estão ilustrados na Figura 2.2.

2.2.1 Padrão de Paralelismo de TarefasQuando se trata de paralelismo de tarefas, ao desenvolver uma aplicação deve-seobservar e analisar como as tarefas são definidas, as dependências entre elas e oescalonamento delas.

As tarefas, na decomposição de um problema, deveriam existir pelo menostantas quanto unidades de processamento, de preferência mais, para obter maiorflexibilidade no escalonamento [13]. Também é importante que a computaçãoassociada com as tarefas seja larga o suficiente para equilibrar a sobrecarga como gerenciamento e manipulação de dependências.

Um dos maiores impactos no padrão de paralelismo de tarefas é a dependên-cia, isso envolve questões como o compartilhamento de dados e grupos de tarefas.Na maioria dos casos os algoritmos se tornam mais complexos e perdem eficiên-cia [7]. Para isso, ao desenvolver um programa, é importante que o programa-dor esteja preocupado com estas questões, procurando remover dependências eseparando-as se possível.

O escalonamento parte da ideia de balancear a carga entre os processadores,mantendo um equilíbrio de tarefas entre as threads ou processo. Esta atribui-ção de tarefas aos processadores, pode ser realizado estaticamente (determinandono início da computação como e quanto é usado a thread ou processo, e esta semantem até o fim) ou dinamicamente (variando a distribuição entre as threads ouprocessos, conforme procede a computação).

O paralelismo de tarefas pode ser baseado em laços, isso implica que as tarefassão baseadas nas iterações dos mesmos. Neste caso, a melhor solução é realizar aoperação deste em paralelo. Deve-se tomar cuidado com uma grande quantidadede tarefas, pois pode gerar muita dependências entre elas.

A questão de replicação de dados também é um fator crítico no paralelismo detarefas, pois necessita de muito controle (ocorrendo um réplica na variável local,uma para resolver as tarefas independentes e uma para recombinar os resultadosem um único resultado) [13].

17

2.2.2 Padrão de Divisão e ConquistaO padrão de D&C (Divide and Conquer) consite em dividir o problema em sub-problemas menores, resolvendo-os, independentemente e fundindo todas as sub-soluções em um problema [12]. Isso é resolvido de forma recursiva, tendo comoobjetivo explorar a eficiência na resolução do problema.

Ao contrário do padrão de paralelismo de tarefas, D&C quase não exploraconcorrência entre as tarefas e os subproblemas também possuem seções de par-tir e fundir. Em ambientes de memória distribuída, os subproblemas são geradospor um processo ou thread e executados por outro. As tarefas são geradas dina-micamente e cuidados como a sobrecarga na comunicação devem ser levados emconsideração.

Em alguns níveis de recursão não é vantajoso criar novos processos para sub-dividir o problema, pois de certa forma o problema é pequeno e é resolvido maisrápido de forma sequencial. Como o problema é dividido em subproblemas e de-pois é feita uma fusão destes, existe o custo da comunicação para subdividir edepois juntar todos estes subproblemas.

2.2.3 Padrão de Decomposição GeométricaA decomposição geométrica se objetiva em dividir um região geométrica em sub-regiões geométricas [14]. Para vetores, esta decomposição pode ser de uma oumais dimensões, e os sub-vetores resultantes são chamados de blocos e denomi-nados de pedaços as sub-estruturas e as sub-regiões.

Havendo a decomposição de dados em pedaços, implica que sejam atualizadasas operações nas tarefas, onde cada tarefa representa a atualização de um pedaçoe estas executam concorrentemente. Em certos casos, alguns pontos necessitamda atualização de outros pedaços, para tanto, a informação deve ser compartilhadaentre os pedaços para completar a atualização.

Este padrão pode trabalhar em conjunto com os padrões de decomposiçãode tarefas (quando a atualização para cada pedaço pode ser feita sem dados apartir dos pedaços) ou D&C (quando a estrutura de dados pode ser distribuída erecursiva).

2.2.4 Padrão de Recursão de DadosPara os problemas de lista, árvores ou grafos, o padrão de recursão de dados podeatuar, visando explorar paralelismo para obter maior desempenho. O padrão deD&C também trabalha com recursão de dados, mas sem grande potencial de con-corrência. No entanto, o objetivo é reformular estas operações, com a finalidade

18

de que o programa possa operar concorrentemente em todos os elementos da es-trutura de dados e também operar recursivamente [1].

Um dos principais desafios deste padrão é aplicar a mudança do algoritmoorginal para explorar a concorrência, isso implica que em todos os níveis da estru-tura exista concorrência entre os processos. Para isso, cada nó comunica-se como raiz ou com o pai dele. O padrão de recursão de dados é semelhante ao D&C,porém, acrescenta a concorrência na estrutura de dados.

2.2.5 Padrão de PipelineO padrão pipeline consiste de uma computação baseada em estágios, podendoser visto como uma sequência de dados através de uma sequência de estágios[7]. O pipeline é introduzido em CPUs modernas para explorar o paralelismo,executando várias instruções concorrentemente. Exemplo disso é o comando shell"cat arquivo |grep ’brasil’ | wc", sendo que este é executado em três estágios dopipeline, com um processo para cada comando (cat, grep e wc) [1].

Na definição de estágios de um pipeline, cada processo deve conhecer a quan-tidade de estágios do pipeline para que cada estágio saiba a partir da contagem donúmero de elementos, o momento de parar e quando o dado foi processado. Noentanto, alguns fatores implicam no desempenho deste padrão:

• A concorrência é limitada pelo número de estágios, isso pode implicar emum overhead de comunicação, pois é necessário organizar a computaçãoa um determinado número de estágios, uma boa modelagem poderá evitareste tipo de problema.

• Este padrão possui um melhor desempenho se todos os estágios do pipelinepossuem capacidade computacional igual, pois se alguns estágios variamsua capacidade, os que possuem menos recursos criam um gargalo para orendimento do pipeline.

• O padrão também opera melhor se o tempo requisitado para encher e esva-ziar o pipeline é menor comparado ao tempo de execução global, sendo queeste é influenciado pelo número de estágios (mais estágios, mais tempo deencher e esvaziar).

Para estruturar a computação, pode-se usar o modelo SPMD e usar o ID (iden-tificador) de cada processo, para escolher uma opção para cada caso correspon-dendo a um estágio do pipeline.

Na representação do fluxo de dados entre os elementos do pipeline, para ambi-entes de troca de mensagem, refere-se a comunicação para que os processos pos-sam se sincronizar. Em cada mensagem é possível enviar uma grande quantidadede dados, reduzindo assim, o número de mensagens trocadas entre os processos.

19

Em ambientes onde a troca de mensagem não é uma boa alternativa, os es-tágios do pipeline podem ser conectados explicitamente com canais buferizados,implementando por exemplo, filas compartilhadas entre os enviadores e recebe-dores de tarefas, usando o padrão de compartilhamento de fila.

2.2.6 Padrão de Coordenação Baseada em EventosAo contrário do padrão de pipeline, a coordenação baseada em eventos não operaestritamente em uma estrutura linear, não possui restrições que um fluxo de dadosseja apenas de uma maneira, e as iterações são irregulares e algumas vezes comintervalos imprevisíveis.

Um exemplo disso é uma garagem de lava-carros. Esta por sua vez, possuiduas máquinas de lavar carro e uma fila de atendimento. Cada carro poderá ficarum determinado tempo usando o recurso e se ainda a limpeza não for concluída,o carro volta para a fila de atendimento, para passar novamente pela máquina delavar carro e assim sucessivamente, até que o carro esteja limpo. Os carros queestão na fila ganham o recurso quando a máquina não está ocupada e quandochegou a sua vez.

Para fluxos de dados usa-se eventos, onde cada evento contém uma tarefa quegera o evento e uma tarefa que processa o evento. Isso porquê, um evento deveser gerado antes que ele seja processado, os quais definem restrição de ordenaçãoentre as tarefas, onde a computação de cada tarefa consite de processamento deeventos [1].

A estrutura básica de cada tarefa consiste no recebimento de um evento, pro-cessamento dele e possivelmente gerâ-lo. Para representar o fluxo de evento, estãoassociados a comunicação e a computação de sobreposição, de modo geral, a co-municação é assíncrona dos eventos em que uma tarefa pode criar (enviar) umevento, e então continuar sem esperar por um destinatário para receber um evento.

Em ambientes de memória distribuída, um evento pode ser representado poruma mensagem enviada assincronamente a partir da tarefa, gerando o evento paraa tarefa que irá processá-lo. Já em ambientes de memória compartilhada, umafila pode ser usada para simular a troca de mensagens, onde uma fila pode seracessada por mais de um tarefa e deve ser implementada de forma a permitir oacesso seguro e concorrente.

Na coordenação baseada em eventos, deve se ter cuidado com problemas dedeadlock, escalonamento e alocação de processo (um processo por elemento deprocessamento) e uma eficiente comunicação de eventos. O cuidado com relaçãoa comunicação deve ser maior para ambientes de troca de mensagem.

20

2.3 Estruturas de ApoioO espaço de projeto de estruturas de apoio descreve construções ou estruturas desoftware que suportam os algoritmos paralelos. O espaço de projeto está represen-tado em dois grupos de padrões conforme ilustra a Figura 2.3, nos que representama abordagem de estrutura dos programas e nos que representam o uso de estruturade dados.

Figura 2.3: Estruturas de Apoio. Adaptado de [1]

Nada impede que se use outro padrão para implementar uma estrutura, comopor exemplo, Mestre/Escravo utilizar Fork/Join ou SPMD. Isso significa que es-tes padrões não representam unicamente uma maneira de estruturar um programaparalelo.

Para um programador MPI (Message Passing Interface), todos os programasde padrões estruturados são derivados do padrão SPMD. Entretanto, para um pro-gramador OpenMP (Open MultiProcessor) existe uma enorme diferença entreprogramas que utilizam identificadores de threads (padrão SPMD) para progra-mas que expressam a concorrência em nível de laço (padrão de paralelismo delaço) [12].

Geralmente, todos os programas que utilizam padrões estruturados enfrentamalguns problemas básicos, tais como:

• Clareza e Abstração: refere-se a forma que o algoritmo está escrito nocódigo fonte. Uma abstração clara é importante para escrever um códigocorretamente e auxiliar na depuração.

• Escalabilidade: diz respeito a quantidade de processos que um programaparalelo pode eficientemente utilizar. Restringindo-se a concorrência dis-ponível no algoritmo, implica no limite de processadores que podem serusados, e o overhead pode contribuir para baixar e limitar a escalabilidade.

21

• Eficiência: está diretamente realacionada com desempenho do programaem relação ao programa sequencial.

• Sustentabilidade: refere-se na facilidade de se modificar, depurar e verifi-car um software e em relação a sua qualidade.

• Afinidade do Ambiente: é a relação do programa com o ambiente de pro-gramação e a escolha do hardware.

• Equivalência Sequencial: esta situação corre quando um programa produzresultados equivalentes, quando executa com vários processos tal como emum único processo.

2.3.1 Padrão SPMD (Single Program, Multiple Data)Em um programa que utiliza o padrão SPMD, todos os processos executam omesmo programa, mas cada processo tem seu próprio conjunto de dados [7]. Osprocessos podem seguir caminhos diferentes dentro de um programa. Pode serusado para descrever e estruturar os padrões do espaço de projeto da estrutura dealgoritmos paralelos.

O padrão SPMD é melhor aplicado quando usado em casos de integração nu-mérica e dinâmica molecular. Este é constituído por alguns elementos básicos quecompõem sua estrutura:

• Inicialização.

• Obtenção de um único identificador.

• Execução do mesmo programa em cada processo, usando o identificadorúnico para diferenciar comportamento em diferentes processos.

• Dados distribuídos.

• Finalização.

2.3.2 Padrão Mestre/EscravoNo padrão mestre/escravo, o processo ou thread mestre, configura uma certa quan-tidade de processos ou threads trabalhadoras e um saco de tarefas. Os trabalhado-res executam concorrentemente cada trabalho, removendo uma tarefa do saco detarefas e processando-a, até que todas sejam processadas [7].

No contexto do programa, o processo mestre inicia a computação, depois con-figura o problema e cria o saco de tarefas, para então enviar aos escravos, que por

22

sua vez, inicializam e computam o resultado e, se a computação estiver terminada,devolvem ao processo mestre. Enquanto os escravos trabalham, o mestre fica es-perando para coletar os resultados de cada um dos escravos, e após esse processo,cabe a ele terminar o programa.

Para [1] este padrão é relevante para problemas que utilizam paralelismo detarefas quando não existe dependência entre as tarefas, e também pode ser usadocom o padrão fork/join para casos onde o mapeamento de tarefas nas threads ouprocessos é indireto.

2.3.3 Padrão de Paralelismo de LaçoEste padrão tem como propósito encontrar maneiras para programas que possuemestruturas baseadas em laço, possam processar em paralelo, ou seja, transformarum programa serial, cujo o tempo de execução é determinando por um conjuntointensivo do processamento de um laço, onde diferentes iterações, são executadasem paralelo.

Segundo [1], é particularmente relevante usar o paralelismo de laço para pro-gramas OpenMP executando em computadores com memória compartilhada, eproblemas que utilizam padrões de paralelismo de tarefas e decomposição geo-métrica.

A implementação do padrão de paralelismo de laço usando OpenMP implicanas seguintes etapas:

• Encontrar gargalos: fica a cargo do programador identificar os laços nocódigo para combinar e encontrar o desempenho necessário para cada sub-problema.

• Eliminar dependência na realização do laço: consiste em eliminar opera-ções de escrita e leitura que provocam uma sessão crítica.

• Paralelizar o laço: dividir as iterações entre as threads ou processos.

• Otimizar o escalonamento do laço: as iterações devem ser escalonadaspara executarem nas threads ou processos, a fim de obter o balanceamentode carga.

• Mesclar laços: situação onde o problema de uma sequência de laços quepossui limites consistentes de laço, onde estes podem frequentemente sermesclados em um único laço com iterações mais complexas.

• Reunir laços alinhados: normalmente é possível reunir laços alinhados emum único laço, com uma grande combinação de iterações.

23

2.3.4 Padrão Fork/JoinPadrão Fork/Join é baseado no conceito de processo pai e processos filhos. Umprocesso pai pode criar vários processos filho dinamicamente, quando um pro-cesso terminar, ele executa join e acaba, e os demais processos filhos continuamsua execução [15].

Este padrão é usado em exemplos que usam recursividade, como o padrãoD&C. Com o produto da execução do programa o problema é divido em subpro-blemas e novas tarefas são recursivamente criadas para executar concorrentementeos subproblemas, cada uma destas tarefas pode se tornar um subdivisor. Quandotodas as tarefas forem criadas para fazer uma divisão, elas terminam e se juntamcom a tarefa pai, e a tarefa pai continua a computação.

Parafaseando [1], o padrão fork/join é relevante para programas Java execu-tando em computadores com memória compartilhada e para problemas que usampadrões como D&C e Recursão de Dados. O OpenMP pode ser usado efetiva-mente com padrões quando o ambiente OpenMP suporta alinhamento de regiõesparalelas.

Os problemas que usam o padrão fork/join, possuem o mapeamento de tarefasem threads ou processos de diferentes maneiras:

• Mapeamento direto de tarefa: nesse mapeamento existe uma tarefa porprocesso ou thread. Para cada nova subtarefa criada, novas threads ou pro-cessos são criados para manipulá-las. Na maioria dos casos, existe um pontode sincronização onde a tarefa principal espera pelo término das subtarefas,conhecido como join.

• Mapeamento indireto de tarefa: neste mapeamento existe uma certa quan-tidade de threads ou processos trabalhando em um conjunto de tarefas. Aideia é a criação estática de threads ou processos antes de começar as opera-ções de fork/join. O mapeamento de tarefas em threads ou processos entãoocorre dinamicamente usando uma fila de tarefas. Neste tipo de mapea-mento, não é possível que threads ou processos criam e destruam a si mes-mos, mas podem simplesmente mapear para tarefas criadas dinamicamenteconforme necessitarem. Sua implementação é complicada e geralmente re-sultam em programas eficientes com bom balanceamento de carga.

Algoritmos de ordenação de vetores como mergesort, podem ser implementa-dos usando mapeamento direto ou indireto de tarefas.

2.3.5 Padrão de Compartilhamento de DadosA manipulação de dados é normalmente compartilhada por mais de um processo,onde o compartilhamento de dados implica em um conjunto de tarefas operando

24

concorrentemente.Operar com dados compartilhados pode ser considerado como um dos aspec-

tos que propicia o programador a cometer erros no projeto de algoritmos paralelos.Entretanto, é importante uma boa abordagem que enfatiza a simplicidade e umaabstração, e só assim partir para abordagens mais complexas se necessário, paraobter um desempenho aceitável.

A primeira etapa no projeto do algoritmo é identificar se este padrão é real-mente necessário. Se é importante o uso deste padrão, pode-se começar a definiruma abstração e os tipos de dados, como por exemplo, no padrão de comparti-lhamento de fila, inicialmente apenas se inclui operações como colocar na filaum elemento e tirá-lo. Somente após concluir estas operações, inclui-se novasoperações de manipulação na fila compartilhada [12].

É necessário que o algoritmo paralelo implemente um protocolo de controlede concorrência apropriado com:

• Um em um tempo de execução: em ambiente de computação com memó-ria compartilhada, as operação são tratadas como parte de uma única sessãocrítica, e usa o protocolo de exclusão mútua, para garantir que apenas umathread ou processo executa a sessão crítica.

• Conjunto de operações sem interferência: esta abordagem é usada paramelhorar o desempenho começando pela análise de interferência entreas operações, tratanto questões onde uma operação pode enferir com elamesma, ou mais que uma tarefa executa a mesma operação (por exemplo,mais que uma tarefa tira um elemento da fila ao mesmo tempo).

• Leitura/Escrita: é usado para casos onde é possível particionar operaçõesem conjuntos disjuntos, levando em consideração o tipo de interferência.Neste caso, algumas operações podem alterar dados e outras somente lerdados, sendo assim, é possível atribuir as tarefas a conjuntos, em que so-mente realizam leitura e outras para realizar a alteração de dados.

• Redução do tamanho da sessão crítica. É uma outra maneira de melhoraro desempenho analisando a implementação de operações com mais deta-lhes. Identificando se o tamanho da seção crítica pode ser reduzido empequenas partes. Este tipo de ação pode facilmente provocar erros de pro-gramação, sugere-se que seja feito, apenas se necessáro para melhorar odesempenho e simplificar a abordagem.

• Alinhamento de locks. Esta técnica faz o uso híbrido das abordagensde redução do tamanho da seção crítica com operações sem interferência.Neste contexto, geralmente são criadas duas funções de bloqueio ("lockA"e"lockB", onde a variável A requer lockB e a variável B requer lockA).

25

2.3.6 Padrão de Compartilhamento de FilaO padrão de compartilhamento de fila tem por objetivo tratar o problema da execu-ção concorrente de threads e processos em uma fila compartilhada. A implemen-tação de algoritmos paralelos requer uma fila que é compartilhada entre processosou threads. Uma das situações mais comuns é a necessidade de um fila de tarefasimplementada no padrão Mestre/Escravo.

Para não haver erros na programação é importante prover uma abstração bemclara e tornar a verificação da fila compartilhada simples, para identificar se estaestá corretamente implementada. Em alguns casos podem ser crescentes as chan-ces de processos e threads permanecerem bloqueados esperando acesso à fila e li-mitar a concorrência disponível. É importante destacar que, mantendo uma únicafila para sistemas com hierarquia de mémoria mais complicada (por exemplo, má-quinas NUMA e clusters), pode causar overhead na comunicação, mas para re-solver este problema, pode-se usar filas múltiplas ou ditribuídas, eliminando osgargalos.

Na implementação do projeto de uma fila compartilhada, pode-se seguir osmesmos requisitos de projeto de padrão de compartilhamento de dados [1]. Ini-cialmente deve-se prover a abstração do tipo de dados para definir os valores queesta fila pode pegar e o conjunto de operações. Em seguida, deve-se considerar oprotocolo de controle de concorrência, começando da maneira mais simples, umem um tempo de execução, para então aplicar os refinamentos direcionando estametodologia para este padrão.

2.3.7 Padrão de Array DistribuídoO padrão de array distribuído é normalmente usado na computação científica,para dividir um determinado array de maneira a processsá-lo em paralelo entreprocessos e threads, no qual, são calculadas equações diferenciais e isso requer ouso de arrays muito grandes [15].

Este padrão é importante quando o algoritmo usa o padrão de decomposiçãogeométrica e para estrutura do algoritmo o padrão SPMD. Na plataforma NUMA,onde cada elemento de processamento tem acesso a todas as memórias locais, otempo de acesso varia. Não é necessário especificar a distibuição do array paracada plataforma, mas mesmo assim é importante gerenciar a hierarquia de memó-ria. Devido a isso, em máquinas NUMA, programas MPI podem algumas vezesserem melhores do que algoritmos similares que implementam multithread paramanter páginas de memória próximos aos processadores que irão trabalhar comeste array.

Questões como balanceamento de carga, gerenciamento de memória (podetrazer bom desempenho quando feio o uso correto da hierarquia de memória) e

26

abstração (clareza em como os arrays estão divididos entre os processo ou thre-ads e mapeados) podem resultar em uma implementação com bons resultados,aproveitando bem os recursos disponíveis.

A abordagem para este padrão é particionar o array em blocos e mapeá-los emprocessos ou threads. Cada processo ou thread tem a mesma quantidade de tra-balho e todos as threads ou processos deixam compartilhado um único endereço,cada bloco do processo ou thread armazena em um array local e para acessar oselementos do array distribuído é realizado usando índices no array local.

2.3.8 Escolhendo Padrões para o Projeto de Algoritmos Para-lelos

As Tabelas 2.1 e 2.2 são classificadas na escala de zero a quatro estrelas (F).Estas tabelas têm por objetivo auxiliar o programador na escolha da combinaçãode padrões e na escolha do padrão com relação o ambiente de programação parao projeto de programas paralelos.

Tabela 2.1: Relacionamento entre os padrões de estruturas de apoio e ambientesde programação. Extraído de [1]

OpenMP MPI JavaSPMD FFF FFFF FF

Paralelismo de Laço FFFF F FFF

Mestre/Escravo FF FFF FFF

Fork/Join FFF FFFF

Tabela 2.2: Relacionamento entre os padrões de estruturas de apoio e padrões deestrutura algoritmos. Extraído de [1]

Paralelismode Tarefas

Divisão eConquista

DecomposiçãoGeométrica

Recursão deDados

Pipeline CordenaçãoBaseada emEvento

SPMD FFFF FFF FFFF FF FFF FF

Paralelismode Laço

FFFF FF FFF

Mestre/Escra-vo

FFFF FF F F F F

Fork/Join FF FFFF FF FFFF FFFF

Analisando as Tabelas 2.1 e 2.2, os padrões SPMD e Mestre/Escravo são osmais flexíveis e podem ser usado em todas as combinações, mas não significa queestes são os melhores padrões para todos os casos, cada padrão é combinado comdeterminado padrão e o número de estrelas (F) determina o quão eficiente e boaé a combinação.

27

2.4 Mecanismos de ImplementaçãoO mecanismo de implementação é a última etapa do espaço de projeto de algo-ritmos paralelos, sendo este o mais alto nível de construção para organizar pro-gramas paralelos, este está dividido em três grupos conforme ilustra a Figura 2.4:gerenciamento de threads e processos, sincronização e comunicação.

Figura 2.4: Mecanismos de Implementação. Adaptado de [1]

As próximas seções relatam os três mecanismos de implementação, que nãosão padrões, mas fazem parte do projeto de algoritmos paralelos, referindo-se aogerenciamento de threads e processos, sicronização e comunicação.

2.4.1 Gerenciamento de Threads e ProcessosO processo é um objeto que carrega um contexto em algum lugar no sistema, issoinclui memória, registradores, buffers. Em um sistema, diferentes processos per-tencem a diferentes usuários. Já uma thread é um processo leve, uma coleçãode threads está contido em um processo. Recursos como memória são compar-tilhados entre as threads, na qual a comunicação entre elas, pertence ao mesmoprocesso.

Threads e processos podem ser criados e destruídos. Threads são mais simplese não exigem muitos ciclos de máquina para serem criadas, por outro lado, osprocessos são mais custosos, pois quando um processo é criado, é dele exigidoque se carregue todas as informações necessárias para definir um lugar no sistemano qual irá atuar.

API’s como OpenMP, Pthread, Java e MPI possuem funções de criação dethreads e processos, abstraindo grande parte da complexidade em lidar com estaabordagem de programação [9].

2.4.2 SincronizaçãoNo gerenciamento de threads e processos, a sincronização ocorre quando se de-seja manter uma ordem na execução dos eventos [11]. As API’s OpenMP, Pth-read, Java e MPI implementam isso em suas funções, sendo que este é realizadotratando os seguintes fatores:

28

• Sincronização e cercas de Memória: no ambiente computacional de me-mória compartilhada, threads e processos podem executar uma sequênciade instruções que são lidas e escritas na memória compartilhada atomica-mente. Também pode ser visto como uma sequência de eventos atômicos,os quais intercalam a partir de diferentes threads ou processos. As cercasde memória são usadas na sincronização para garantir que processos ou th-reads tenham uma visão consistente da memória. No contexto de threads,um programa cheio de condições de corrida pode não ser vantajoso.

• Barreiras: são o mais alto nível para tratar a sincronização de threads eprocessos. As barreiras são usadas para garantir que um conjunto de thre-ads ou processos não proceda antes que todos cheguem a um determinadoponto.

• Exclusão Mútua: o objetivo é garantir que threads ou processos não inter-ferem entre si, pois em situações que se tem recursos compartilhados entreeles, dois ou mais processos ou threads podem tentar atualizar um dadocompartilhado, podendo gerar conflito. O acesso a seção crítica é contro-lado usando exclusão mútua, onde uma thread ou processo progride, en-quando os outros ficam esperando até que a seção crítica seja desbloqueada.

2.4.3 ComunicaçãoPara que a troca de informações ocorra entre as threads e processos, é necessárioa comunicação, esta pode ser realizada usando mensagens. Uma mensagem podeconter dados sobre a mensagem e outras informações. A troca de mensagens podeser feita de duas formas: de uma origem específica para um destino específico emúltipla troca de mensagem entre processos ou threads em uma única comunica-ção (comunicação coletiva).

Na comunicação coletiva estão envolvidas diferentes operações, como o me-canismo de broadcast, barreiras e redução [12]. Esta redução consiste em reduziruma coleção de itens de dados para um único item de dados, para então combinaros itens de dados com uma operação binária, assiciativa ou comutativa (i.e, soma,produto, maior elemento, menor elemento, entre outras).

29

3 Programação Paralela comPadrões Estruturados eDeterminísticos

Um padrão paralelo determinístico é o resultado da combinação de distribuiçãode tarefas e acesso a dados [4]. Os modelos de programação normalmente supor-tam apenas um pequeno número de padrões ou padrões que são implementadosem baixo nível (implementação em hardware). Na teoria, um sistema que suportapadrões determinísticos e permite a sua composição, tende a resultar em imple-mentações eficientes para uma grande variedade de arquiteturas [5]. Isso porque oobjetivo principal é prover altos níveis de escalabilidade e desempenho e, forneceruma camada de abstração sobre os mecanismos de paralelismo que implementamcaracterísticas de hardware.

São crescentes os eforços na padronização da programação paralela em umalto nível de abstração para que seja possível o desenvolvimento de software emlarga escala. Padrões paralelos determinísticos são uma alternativa para cobriresta lacuna em ambientes computacionais de memória compartilha (arquiteturasmulti-core).

Padrões determinísticos implicam em uma maior facilidade de manutenção,pois a criação de condições de corrida não se tornam necessárias. Além disso,possuem escopo limitado para introdução das condições de corrida e simplicam oentendimento, a depuração e os testes, por oferecer determinismo e consistênciaatravés de uma única ordem de execução.

Aplicações que são orientadas a padrões paralelos podem proporcionar altaprodutividade, pois os padrões derivam a partir de casos que ocorrem normal-mente em aplicações, onde o subconjunto de padrões é universal para uma apli-cação e pode especificar os domínios específicos [14]. Isso possibilita ao progra-mador simplificar a aprendizagem em escrever programas eficientes, focando-seapenas no que interessa: paralelismo e localização dos dados.

Modelos de programação também podem ser baseados nestes padrões para-lelos, a fim de complementar na automatização de uma implementação eficiente.

30

Plataformas de programação, também podem usar padrões estruturados, com opropósito de deixar claro o entendimento do desenvolvedor de software ao que serefere a coerência de memória.

As abordagens de paralelismo de tarefas e paralelismo de dados são usadas nospadrões determinísticos, que estão divididos em dois grupos: padrões de compu-tação paralela e padrões de gerenciamento de dados paralelos.

Neste capítulo serão detalhados os dois grupos de padrões determinísticos comseus respectivos padrões, levantando essencialmente os mais importantes e maisusados para o desenvolvimento de programas paralelos. Para tanto, o estudo sebaseia nos autores de [5], [12], [16], [17], [18], [19], [14], [1], [4] e [20].

3.1 Padrões de Computação ParalelaOs padrões de computação paralela estão classificados por categorias: categoriados padrões que operam nos valores de dados, categoria dos padrões de acesso adados e a categoria dos padrões que não podem acessar dados. Os quais são fre-quentemente combinados com outros padrões e, a maioria dos padrões descritosnas próximas subseções acessam e atualizam dados de maneira específica.

3.1.1 Map PatternO padrão Map é aplicado em iterações de laço quando não há dependência entreas iterações, para as quais, um conjunto de índices do laço são gerados e a com-putação é realizada independente para um único índice [5]. Esta computação nãopermite que a comunicação seja realizada com outra. Para tanto, no final do Map,existe uma barreira implícita e então outras operações podem depender da saídadas operações de Map como um todo. Mecanismos paralelos em nível de proces-sos ou threads incluem instruções superscalar, pipeline e operações de cálculo oumemória paralela tais como, múltiplos cores e instruções vetorizadas.

Figura 3.1: Map Parallel Pattern.

31

A partir de uma simples estrutura de paralelismo representado pelo Map torna-se possível automatizar a geração de implementações eficientes, que explorammúltiplos mecanismos ao mesmo tempo. Exemplo disso, é ilustrado na Figura3.1, onde se implementa um processador dual-core com um vetor de 4 elementose 4 ciclos de pipeline.

Map pode dar origem a outros padrões através das iterações (i.e paralelismode laço), mesmo se existe a dependência entre as iterações do laço. Entretanto, seexistir independência entre as iterações de um laço, maior será a chance de obtera execução paralela [18].

As operações de Map são também combinadas com operações de leitura eescrita aleatória, que podem gerar padrões de entrada e saída de dados para tornaro acesso restrito a múltiplas entradas e saídas, e operações com vizinhos.

Conforme a Figura 3.1, o Map replica uma funcão sobre todos os elementosde um conjunto de índices (i.e A = map(f,B)). Este pode ser abstrato ou associadocom os elementos de um array. Isso substitui o uso específico de iterações emprogramas sequenciais, onde são processados todos os elementos de uma coleçãode operações independentes.

3.1.2 Reduction PatternUm Reduction se utilizada de um operador de associatividade, sendo este usadopara todos os elementos de uma coleção reduzindo para um único elemento [19].A Figura 3.2 ajuda a entender o funcionamento deste padrão, onde "f"são os ele-mentos. Podendo ser usado por exemplo, para encontrar o menor valor de umarray (b = reduce(f,B)).

Figura 3.2: Reduction Parallel Pattern.

O padrão Reduction é combinável com outros padrões para prover maior efi-ciência no desenvolvimento de programas paralelos, por exemplo, multiplicacão

32

de matrizes, no qual, o padrão pode ser visto como um Map de Reductions sobrepartições formadas a partir de linhas e colunas.

3.1.3 Superscalar Sequences PatternNo padrão Superscalar Sequences uma operação é executada por vez, desta forma,para que uma operação comece sua execução a anterior deverá estar finalizada [5].Entretanto, se uma sequência de operações pode ser reordenanda respeitando asdependências de dados, pode-se explorar formas de paralelismo de tarefas.

Um exemplo prático de uso do padrão de Superscalar Sequences é chamarvárias funções em sequência, como pode ser visto no código abaixo:

B = f(A); | G = g(E,F);C = g(B); | P = p(B);E = f(C); | Q = q(B);F = h(C); | R = r(G,P,Q);

Com a sequência de invocação das funções definidas é possivel gerar o grafoilustrado na Figura 3.3, resultante da relação da dependência dos dados conformea ordem da chamda de funções do código acima.

Figura 3.3: Superscalar Sequences Parallel Pattern.

Mesmo que o padrão trabalha somente com uma operação de cada vez, nestecaso algumas operações são executadas em paralelo, como os elementos p e q,f e h. Para tanto, a entrada do grafo {g,f,h,g} é executado em paralelo com osubgrafo {p,q}. Sendo assim, se o sistema permite identificar as dependências dedados é possível explorar o paralelismo.

Neste padrão não são possíveis: operações de laços e algumas linguagens quepossuem características como ponteiros globais para acessar qualquer coisa na

33

memória, gerando dependências globais. Isso deve ser evitado para não havererros na construção do grafo e das execuções paralelas.

3.1.4 Pipeline PatternO pipeline também se adapta nos padrões determinísticos, pois suas característicasde operação se enquadram no grupo de padrões computacionais. Por tanto, opadrão pipeline é um conjunto de tarefas executando simultaneamente (estágios)fazendo um relacionamento produtor-consumidor [1]. A Figura 3.4 exemplifica ofuncionamento de um pipeline com três estágios.

Figura 3.4: Pipeline Parallel Pattern.

Ao contrário do padrão Superscalar Sequences, no pipeline não é necessárioque uma operação acabe para que outra comece. O padrão pode ser uma boaalternativa para paralelizar codecs com compressão e também para processamentode vídeo.

3.1.5 Nesting PatternNesting é considerado um padrão com alocação de dados baseado em pilha, quepossui propriedades com boa coerência de dados [5]. A implementação de Nestedde forma recursiva contribui para resolver o problema de paralelismo de tarefas.

Figura 3.5: Nesting Parallel Pattern.

O Padrão Nesting pode ser chamado através da chamada de padrões paralelosdentro de outros padrões. Por exemplo, usando um Map ou Reduction dentro de

34

uma função usando outro Map ou Reduction. Isso gera uma hierarquia de grafo detarefas, que em alguns casos torna-se necessário expandir paralelismo adicional.

Este padrão é classificado como paralelismo de tarefas e de dados, pois suaestrutura como mostra a Figura 3.5, é composta da fusão com outros padrões de-terminísticos como, Map, Speculative Selection e Superscalar Sequence, os quaispodem ser combinados com outros padrões como Reduction.

3.1.6 Scan PatternO padrão Scan é mais apropriado para organizar, mas tende a ser usado na im-plementação interna de outros padrões paralelos [19]. Se um algoritmo serial érealizado em uma dimensão em um laço, se torna interessante verificar quais sãoas suas dependências para poder converter em um Scan.

Aplicações que utilizam Scan incluem manipulação de estruturas de dadospara que possam ser usados na computação de posições de objetos antes de umGather ou Scatter. Mais precisamente, pode ser eficiente quando usado em im-plementações com o padrão Pack e Split [5]. Entretanto, este padrão requer umoperador de associatividade, como por exemplo, uma aplicação do Scan é com"add". Nesta aplicação, pode ser usada pesquisa binária para gerar amostras ale-atórias de acordo com uma probabilidade específica. A Figura 3.6 exemplifica ofuncionamento do Scan com pesquisa binária.

Figura 3.6: Scan Parallel Pattern.

A pesquisa binária pode ser realizada usando um vetor auxiliar booleano, paracomparar se a operação em uma determinada região na memória pode ser efetu-ada. O vetor booleano é dado como uma entrada paralela (identificando os dadosa serem usados), mas em uma aplicação real, este vetor pode ser uma entrada paraoutro Scan ou uma operação paralela.

35

3.1.7 Recurrences PatternRecurrences normalmente ocorrem no código sequencial devido ao uso de depen-dências na execução do laço, as quais, em alguns casos podem ser paralelizadas.

A execução de fatias paralelas pode ser realizada usando paralelismo com Mapou wavefront [17]. Conforme ilustra a Figura 3.7, a implementação de Recurren-ces pode fazer o uso de blocos para obter desempenho.

Figura 3.7: Recurrences Parallel Pattern.

Este padrão pode ser combinado com outros padrões, por exemplo, se combi-nado com o padrão pipeline implementa a computação wavefront e também podeser combinado com o padrão Superscalar Sequence.

Exemplos usando o padrão Recurrences incluem filtros recursivos e váriosalgoritmos de fatoração de matrizes.

3.1.8 Partition PatternPatitions podem ajudar no desempenho em arquiteturas com mamória NUMA,onde os algoritmos podem ser designados para enfatizar a localidade dos dadosaproveitando o particionamento para deixar os dados mais próximos da memória.Neste contexto, as partições não movem dados, elas apenas são uma forma devisualizar a organização.

Figura 3.8: Partition Parallel Pattern.

36

Conforme a Figura 3.8, uma coleção de dados é quebrada em blocos sem so-brepor as regiões. Nestes blocos são atribuídas coleções para cada um dos blocos(partições) e estas realizam operações de leitura e escrita sem causar condições decorrida (somente uma tarefa acessa uma determinada partição).

O padrão Patition pode operar com partições regulares (mesmo tamanho) eirregulares (tamanhos diferentes) [16]. As caches são divididas em blocos comdiferentes tamanhos e a memória virtual está organizada em páginas e, o compar-tilhamento de dados entre os processadores é gerenciado em blocos.

O projetista de software ao usar o padrão Partition deve evitar dependênciana memória física, escrevendo algoritmos de maneira genérica parametrizando otamanho das partições.

3.1.9 Speculative Selection PatternSpeculative Selection é o padrão que faz a ecolha de resultados a partir de uma ouduas alternativas baseadas em tarefas, onde ambas as alternativas de escolha po-dem executar em paralelo enquanto as condições estão no processo de avaliação,conforme é exemplificado na Figura 3.9.

Figura 3.9: Speculation Selection Parallel Pattern.

Este padrão também pode se tornar mais trabalhoso do que um programa se-quencial, porém é bom para trabalhar com condições complexas [5]. Quando ascondições forem avaliadas, uma delas deve ser descartada, isso é benéfico paraa redução do tempo de execução do algoritmo. Em uma implementação prática,Speculation pode ser usado se existem processadores que estão inativos.

O uso deste padrão é aconselhável quando um recurso computacional estáinativo, ou quando as tarefas são um fator crítico.

3.2 Padrões de Gerenciamento de Dados ParalelosOs padrões deste grupo trabalham na organização de acesso aos dados. Comu-mente, estes padrões são associados com os padrões do grupo de padrões de com-

37

putação paralela. Isso porquê, para implementações eficientes, elas são frequen-temente imperativas para um padrão de acesso a dados, os quais são vistos comoa fusão de padrões paralelos.

3.2.1 Gather PatternUma operação Gather realiza leitura aleatória de um conjunto de elementos a par-tir da memória [12]. O padrão Gather pode ser visto como uma leitura sequencialde memória combinado com o padrão Map. Usando-se leitura aleatória dentro deuma função usada em um Map, resulta em um Gather realizando operações emparalelo.

Figura 3.10: Gather Parallel Pattern.

Conforme ilustrado na Figura 3.10, o padrão Gather executa a leitura de locali-zação aleatória (computação) em um array, e não em ponteiros globais. O padrãotambém usa semântica, onde antes de efetuar uma operação de escrita deve haveruma operação de leitura no kernel, de forma a evitar condições de corrida.

É possível ter o Gather estático ou dinânico. Para um Gather estático pode-se permitir reordená-lo para obter maior desempenho. Caso ele for dinâmico, sediminui a latência para não ultrapasar os limites dos recursos disponíveis.

3.2.2 Search PatternPadrão Search é semelhante ao padrão Gather, porém realiza a recuperação dedados a partir de um banco de dados combinando o conteúdo [5]. O paralelismoestá na pesquisa de elementos chaves em paralelo ou pela pesquisa realizada emdiferentes partes de um banco de dados.

Um exemplo usando Search é representado na Figura 3.11, uma pesquisa érealizada, e então, é feita a redução de todos os elementos com o mesmo iden-tificador em um array. Este padrão é adotado pelo google map-reduce para omecanismo de busca [20].

38

Figura 3.11: Search Parallel Pattern.

3.2.3 Stencil PatternStencil é encontrado em várias aplicações, mas é mais usado em aplicações deprocessamento de imagem e simulação e possui soluções explícitas para partici-onar equações diferenciais [5]. Usado também em simulações e reconstruçõessísmicas, tal como processamento de imagem.

Figura 3.12: Stencil Parallel Pattern.

Uma implementação eficiente de Stencil vai depender do número de otimiza-ções. Uma implementação em baixo nível se torna bastante complexa, mas podeser significativamente melhor que uma implementação ingênua em termos de de-sempenho e eficiência.

Como pode ser visto na Figura 3.12, Stencil utiliza uma janela deslizante sobreum faixa, onde por padrão se tem saídas próximas a partir do Stencil e normal-mente estas saídas compartilham entradas. E para este padrão, uma otimização aser realizada é a manipulação das fronteiras entre os blocos, de forma a garantir(organizar), que não se tenha blocos de leitura inválidos.

Neste caso, torna-se vantajoso o ganho de desempenho com a memória, poisé possível realizar a leitura da cache e enviar leituras novamente (obtendo leituraredundante) economizando a memória principal do sistema. Entretanto, com um

39

Stencil multidimencional, é possível ter paralelismo o suficiente e não há pro-blema em sacrificar um pouco para obter maior desempenho na memória. Umaboa orientação pode depender no layout dos dados na memória, bem como a formado stencil.

3.2.4 Scatter PatternO padrão Scatter é a combinação de Map e escrita aleatória [5]. Neste sentido,ele opera com escrita de dados para uma localização aleatória em uma coleção,conforme pode ser visto na Figura 3.13, podendo sobreescrever qualquer dado quejá foi armazenado.

Figura 3.13: Scatter Parallel Pattern.

Scatter quando usado dentro de Map torna-se uma operação paralela. Porém,como é possível sobreescrever um dado que já esteja armazenado podem ocorrercondições de corrida quando duas escritas estão ocorrendo no mesmo endereço(colisão). Entretanto, para se obter um Scatter determinístico torna-se necessárioo uso de regras determinísticas para resolver as possíveis colisões.

É possível obter algumas variantes a partir de Scatter dependendo de como ascolisões são resolvidas. As variantes de Scatter serão explicadas em seguida.

3.2.5 Permutation Scatter PatternEsta derivação de Scatter não opera concorrentemente se houver colisões. Comopode ser visto na Figura 3.14, cada elemento de uma coleção acessa ou escreve noendereço correspondente.

Figura 3.14: Permutation Scatter Parallel Pattern.

40

O padrão Permutation Scatter pode ser implementado em termos de um scat-ter inseguro, mas é possível através deste produzir resultados incorretos se usadode maneira errada. Isso se implementado com escrita duplicada em um mesmoendereço. Entretando, é necessário que se use mecanismos de depuração e cuida-dos com a implementação (o programador pode pensar que pode usar o mesmoendereço).

3.2.6 Atomic Scatter PatternEste padrão resolve colisões mas não de forma determinística, onde somente umresultado é mantido em uma determinada saída quando ocorre uma colisão, deacordo com o ilustrado na Figura 3.15.

Figura 3.15: Atomic Scatter Parallel Pattern.

O uso do padrão Atomic Scatter resultará em programas não determinísticos.Para tanto, a implementação de Atomic Scatter é boa se necessário o uso de blo-queios para que se possa preservar escritas atômicas.

3.2.7 Merge Scatter PatternMerge Scatter utiliza operadores associativos para resolver um caso de colisão.Para isso, fica a cargo do programador gerenciar estes operadores associativos eas colisões. No entanto, esta regra pode ser usada para combinar valores dispersoscom saída de dados em um array.

Figura 3.16: Merge Scatter Parallel Pattern.

41

A Figura 3.16 demonstra a operação do padrão Merge Scatter usando associ-atividade na escrita. Quando ocorre uma operação são usados operadores que nãosão associativos como, a leitura, modificação e escrita.

3.2.8 Priority Scatter PatternPriority Scatter atribui para todos os elementos paralelos uma prioridade. Con-forme pode ser visto na Figura 3.17, deterministicamente as colisões são tratas,determinado o vencedor baseado na prioridade.

Figura 3.17: Priority Scatter Parallel Pattern.

Um Priority Scatter é bastente útil, pois a ordenação serial do corpo de umlaço pode ser usado para gerar regras não ambíguas. Isso significa que um laçocom operações Scatter pode convergir no padrão Priority Scatter. Este padrãopode ser referência para implementação de padrões em diferentes maneiras. Umadas maneiras é não tornar possível a colisão com outras threads.

3.2.9 Pack PatternPack é usado para eliminar espaço em um lugar da coleção que está escasso epegar a taxa variável de saída de um Map [5]. Possui também como entrada,uma condição booleana, onde os elementos de uma coleção de entrada podemser descartados se forem falsos e colocar em uma coleção de saída eliminando asaberturas, conforme é exemplificado na Figura 3.18.

As entradas que não são descartadas são empacotadas em uma única coleçãoem uma sequência contínua sem saídas mantendo a ordem original. Para isso, asaída de um elemento pode ser vazia, assim um sistema suportando Pack precisapermitir a representação de coleções vazias.

O padrão Pack implementado sozinho não é uma boa solução de paralelismo,mas se fundido com Map pode obter bons resultados em termos de desempenho.Enquanto Map implementa as funções sobre todos os elementos de uma coleção,por outro lado, Pack pode ser implementado como um modo de saída para umMap, onde uma flag pode ser usada para verificar se cada saída de Map pode ser

42

Figura 3.18: Pack Parallel Pattern.

mantida ou descartada, evitando assim, usar largura de banda de memória para asaída de elementos.

3.2.10 Expand PatternO padrão Expand também é resultante da combinação de Map com modos desaídas. Ao contrário de Pack, as saídas podem ser diferentes não apenas 0 e 1.Como pode ser visto na Figura 3.19, toda saída pode conter um número variável eos elementos são encapsulados em uma sequência simples mantendo-se a ordeme a forma sobre cada sub-grupo.

Figura 3.19: Expand Parallel Pattern.

Expand se torna desafiante quando não se tem conhecimento do tamanho dasaída em uma progressão. Por outro lado, dispõe formas de alocação dinâmicade memória, que pode ser muito útil, mas não é possível fazer a pré-alocação dassaídas. Entretanto, uma solução é colocar fronteiras (limites) no número total deelementos que podem ser gerados a partir de cada posição.

43

3.2.11 Split PatternSplit também é uma variante do padrão Pack, porém não descarta os elementosbaseados em condições booleanas, ao invés disso, é possível embalá-los na metadesuperior do conjutno de saída [16]. Para tanto, a ordem de entrada é mantida,conforme é ilustrado na Figura 3.20.

Figura 3.20: Split Parallel Pattern.

Neste padrão o tamanho total da saída é sempre o mesmo que o da entrada,podendo ser implementado executando duas vezes e fundindo os dois resultados.Entretanto, esta implementação pode ser mais eficiente se usado o padrão Pack,pois este padrão é determinístico e a implementação pode explorar a coerência deespaço.

44

4 Frameworks para ProgramaçãoParalela Multi-Core

Este capítulo faz um breve estudo e levantamento dos principais frameworks deprogramação paralela para ambientes multi-core. Nesta breve abordagem são le-vantadas as principais características, bem como o suporte e licença de uso dosframeworks.

4.1 FastFlowFastFlow é um framework de programação paralela para ambientes multi-corebaseado em mecanismos de sincronização não bloqueantes. Composto por umapilha de camadas, que se resume em aplicações com memória compartilhada [21].Este tem por finalidade fornecer abstração para promover alto nível de programa-ção paralela e programação eficiente para aplicações multi-core, o qual é orientadopara aplicações de streaming.

Figura 4.1: Arquitetura FastFlow. Extraído de [2].

A Figura 4.1 apresenta a pilha de camadas sendo esta a arquitetura do Fast-Flow [2]. Através desta arquitetura são caracterizadas cada uma das camadas,

45

compostas de aplicação e:

• Ambiente de resolução de problemas: esta camada tem por objetivo de-compor em estruturas o programa sequencial, simular e automatizar o pro-cesso de paralelização.

• Alto nível de programação: refere-se a abstração mais alta, onde são em-pregados os padrões de programação paralela.

• Baixo nível de programação: trata-se da etapa de abstração sobre a sincro-nização e fluxo de dados de um para muitos, muitos para um e muitos paramuitos.

• Suporte em tempo de execução: prove mecanismos para fornecer de formasimples streaming de rede, e em tempo de execução são implementadosmecanismos não bloqueantes eficientes e filas com único consumidor e comúnico produtor.

• Hardware: nesta camada são tratadas as questões de coerência de cachepara ambientes homogêneos suportando vários modelos de consistência dememória e otimização para plataforma NUMA.

Além de ser um framework eficiente e prover altos níveis de abstração, sualicença é Open Source, podendo ser utilizado sem custos e está disponível paradownload em sua página principal. Além disso, FastFlow é mais rápido queOpenMP e TBB (Threading Bulding Blocks) e sua biblioteca está descrita emlinguagem C++ [21].

4.2 TBB (Threading Bulding Blocks)TBB foi desenvolvido pela empresa Intel, cujo o objetivo é fornecer uma interfacepara o programador para explorar maior desempenho em ambientes multi-core.Representando assim, alto nível de abstração baseado no paralelismo de tarefas,abstraindo detalhes e mecanismos de thread para fornecer escalabilidade e desem-penho [22].

A bibloteca soporta a linguagem de programação C++ disponível na versãoOpen Source e suporta os sistemas operacionais Linux, MAC e Windows. In-cluindo em sua biblioteca funções que implementam alguns padrões paralelosdeterminísticos, por exemplo, Reduce, Partition, Scan e Pipeline. No entanto,também dipõe de funções expecíficas para o paralelismo de laço (for).

46

4.3 SWARM (SoftWare and Algorithms for Runningon Multicore)

SWARM é um framework Open Source de programação paralela para ambientesmulti-core, o qual, fornece uma biblioteca de programação mutithread, com su-porte a sincronização, controle e gerenciamento de memória, bem como operaçõescoletivas [23].

O SWARM foi contruído baseado no padrão POSIX thread, o que permiteusar primitivas que já foram desenvolvidas ou primitivas diretas de threads. Elecontém também construções para a paralelização, restrição de controle de threads,alocação de memória compartilhada, primitivas de comunicação para sincroniza-ção, replicação e broadcast [24].

4.4 Intel Parallel StudioÉ um dos frameworks mais completos para programação paralela e, software pro-prietário da empresa Intel. Porém, não possui licença Open Source, mas suporteaos sistemas operacionais Linux (versões enterprise) e Windows.

O "Intel Parallel Studio"usa o termo ciclo de desenvolvimento para o seu fra-mework, sendo este composto por um conjunto de ferramentas, que juntas com-pletam o ciclo do projeto de desenvolvimento de software paralelo (atendendoas necessidades do fluxo de projeto do software paralelo), o qual é ilustrado naFigura 4.2.

Figura 4.2: Intel Parallel Studio. Extraído de [3].

O ciclo inicia no projeto, para o qual a ferramenta "Parallel Advisor"oferecefuncionalidade de detecção de conflitos, como condições de corrida e bloqueiosantes que o paralelismo seja aplicado podendo ser compatível a qualquer aplica-tivo C/C++. O próximo passo é aplicar otimizações no código, para isso é usado

47

a ferramenta "Parallel Composer", onde esta fornece opções avançadas de com-pilador e bibliotecas, bem como o suporte ao paralelismo simples e complexo, dedados para tarefas, oferecendo aplicações de classe específica como as bibliotecaspre-threaded e thread-safe [3].

A terceira fase consiste na verificação, no qual é usado a ferramenta "ParallelInspector". Esta por sua vez, tem a finalidade de encontrar problemas com re-lação a construção do programa (memória e threads) fornecendo uma interfacede depuração de fácil visualização e operação. Para fechar o clico de desenvolvi-mento, a ferramenta "Parallel Amplifier"ajuda a construir aplicativos de escalamulti-core e many-core e garantir o desempenho do aplicativo.

Com o conjunto de ferramentas o "Intel Parallel Studio"se torna um frameworkbastante eficiente é menos sucetível a falhas, pois possui depuradores avançadosque auxiliam o programador na hora de desenvolver um programa. Consequen-temente, os programas paralelos serão mais confiáveis e menos tempo é gasto nodesenvolvimento.

48

5 Conclusão

Padrões paralelos simplificam o entendimento da programação paralela e deixamclaramente expostas as necessidades e os requisitos de projeto ao desenvolvedor.Questões como controle de acesso à memória, sincronização, condições de corridae outros frequentes problemas da programação multi-core também são especifica-dos nos padrões, bem como a modelagem e a estrutura do algoritmo. Entretanto,para quem é inexperiente em lidar com paralelismo, necessita ir em busca de ma-teriais de estudo e dedicar seus esforços para entender sobre a arquitetura de soft-ware, mesmo que alguns frameworks atualmente disponíveis consigam abstrairparte da complexidade de se lidar com este paradigma de programação.

A depuração de código paralelo é uma das partes mais críticas no desenvolvi-mento de algoritmos paralelos. Esse problema está sendo resolvido, e de acordocom as características apresentadas pelo framework Intel Parallel Studio, a suainterface avançada dispõe de um mecanismo de depuração com threads e con-dições de corrida, um diferencial importante se comparado com os demais fra-meworks. No entanto, para o uso deste é necessário a aquisição de uma licença,e os softwares gratuitos não estão disponíveis com tamanha robustez no suporte àdepuração.

Os padrões estruturados determinísticos apresentam-se como uma tendênciade programação em ambientes multi-core, embora este termo de paralelismo aindaseja novo na literatura. Um padrão estruturado determinístico assemelha-se como modelo de programação sequencial, o que elimina a necessidade do tratamentode condições de corrida. Isso é bom, pois eliminam erros de programação e umapossível sobrecarga do sistema na sincronização das threads ou processos. Poroutro lado, o paralelismo ficará mais próximo do hardware podendo obter maioraproveitamento dos recursos disponíveis.

O estudo e a criação de padrões paralelos estão contribuindo para que se possamelhorar o desempenho dos softwares paralelos. Com isso, padrões são geradosa partir de padrões e pela combinação (fusão) deles. Sendo assim, o estudo destespadrões torna-se útil na criação de novos padrões com características voltadas aaplicações específicas. Além disso, contribuem para que novas soluções sejamcriadas para abstrair ainda mais os detalhes da programação paralela.

49

Assim, um framework integrando padrões paralelos, estruturados e não estru-turados, determinísticos e não determinísticos, com opções específicas e avança-das de depuração de código ofereceria uma interface intuitiva e de fácil enten-dimento. Isto permitiria implementações eficientes, que exploram o máximo deparalelismo disponível e um desenvolvimento de programas paralelos em um me-nor tempo. Isso tende a motivar grande maioria dos programadores de códigosequencial a escrever programas nativamente paralelos, visto que nos próximosanos, os ambientes paralelos (arquiteturas multi-core) serão mais frequentes emnosso cotidiano.

50

Referências Bibliográficas

[1] MATTSON G. T., SANDERS A. B., and MASSINGILL L. B. Patterns forParallel Programming. Addison-Wesley, Boston, MA, 2005.

[2] ALDINUCCI M. and TORQUATI M. Fastflow Fra-mework Parallel Programming (Home Page), Extracted from<http://calvados.di.unipi.it/dokuwiki/doku.php?id=ffnamespace:about>.Access in November, 2010.

[3] INTEL. Intel Software Network, Extracted from<http://software.intel.com/en-us/articles/intel-parallel-studio-home>.Access in November, 2010.

[4] CATANZARO R. and KEUTZER K. Parallel Computing with Patternsand Frameworks. XRDS: Crossroads, The ACM Magazine for Students,17(1):22–27, 2010.

[5] INTEL and MCCOOL D. M. Structured Parallel Programming with De-terministic Patterns. In HotPar-2nd USENIX Workshop on Hot Topics inParallelism, pages 1–6, Berkeley, CA, June 2010.

[6] GAMMA E., HELM R., JONHSON R., and VLISSIDES J. Design Pat-terns: Elements os Reusable Object-Oriented Software. Addison-Wesley,Boston, MA, 2002.

[7] GRAMA A., GUPTA A., KARYPIS G., and KUMAR V. Introduction toParallel Computing. Pearson (Addison-Wesley), Boston, MA, 2003.

[8] YANG-MING Z. and COCHOFF S.M. Medical Image Viewing on Mul-ticore Platforms Using Parallel Computing Patterns. IT Professional,12(2):33–41, 2010.

[9] LEE E. A. The Problem with Threads. IEEE Computer, 39(5):33–42, 2006.

51

[10] MASSINGILL B. L., MATTSON T. G., and SANDERS B. A. Reenginee-ring for Parallelism: an Entry Point into PLPP (Pattern Language for ParallelProgramming) for Legacy Applications. Concurr. Comput. : Pract. Exper.,19(4):503–529, 2007.

[11] SCHMIDT D., STAL M., ROHNERT H., and BUSCHMANN F. Pattern-Oriented Software Architecture, Patterns for Concurrent and Networked Ob-jects. Willey and Sons, New York, 2000.

[12] QUINN J. M. Parallel Programming in C with MPI and OpenMP. McGraw-Hill, New York, 2004.

[13] SCOTT L. R., CLARK T., and BAGHERI B. Scientific Parallel Computing.Princeton, New Jersey, 2005.

[14] MASSINGILL B. L., MATTSON T. G., and SANDERS B. A. Patterns forParallel Application Programs. In Pattern Languages of Programs’99, pages1–29, Monticello, Chicago, August 1999.

[15] RAUBER T. and RÜNGER G. Parallel Programming. Springer, New York,2010.

[16] BOCCHINO Jr., ROBERT L., ADVE V. S., ADVE S. V., and SNIR M.Parallel Programming Must be Deterministic by Default. In Proceedingsof the First USENIX conference on Hot topics in parallelism, pages 1–6,Berkeley, CA, USA, March 2009.

[17] BROMLING S., MACDONALD S., ANVIK J., SCHAEFFER J., SZA-FRON D., and TAN K. Pattern-based Parallel Programming. In ParallelProcessing, 2002. Proceedings. International Conference on, pages 257–265, Vancouver Canada, August 2002.

[18] SKILLICORN D. B. and TALIA D. Models and Languages for ParallelComputation. ACM Comput. Surv., 30(2):123–169, 1998.

[19] DARLINGTON J., Y. GUO, H. W. TO, and YANG J. Parallel Skeletons forStructured Composition. SIGPLAN Not., 30(8):19–28, 1995.

[20] DEAN J. and GHEMAWAT S. MapReduce: Simplified Data Processing onLarge Clusters. Commun. ACM, 51(1):107–113, 2008.

[21] ALDINUCCI M., RUGGIERI S., and TORQUATI M. Porting DecisionTree Algorithms to Multicore Using FastFlow. In Proc. of European Confe-rence in Machine Learning and Knowledge Discovery in Databases (ECMLPKDD), pages 7–23, Barcelona, Spain, September 2010.

52

[22] INTEL. Intel R© Threading Building Blocks (Home Page), Extracted from<http://www.threadingbuildingblocks.org/>. Access in November, 2010.

[23] BADER D. A., KANADE V., and MADDURI K. Swarm: A Parallel Pro-gramming Framework for Multicore Processors. In Parallel and DistributedProcessing Symposium, 2007. IPDPS 2007. IEEE International, pages 1–8,Long Beach, California USA, March 2007.

[24] BADER D. A., KANADE V., and MADDURI K. Software and Algorithmsfor Running on Multicore (Home Page), Extracted from <http://multicore-swarm.sourceforge.net>. Access in November, 2010.

53