universidade do vale do itajaÍ centro de ciÊncias ...siaibib01.univali.br/pdf/david graminho...

67
UNIVERSIDADE DO VALE DO ITAJAÍ CENTRO DE CIÊNCIAS TECNOLÓGICAS DA TERRA E DO MAR CURSO DE CIÊNCIA DA COMPUTAÇÃO AVALIAÇÃO DE TÉCNICAS DE ENGENHARIA DE SOFTWARE PARA MODELAGEM DE PROGRAMAS PARALELOS EM AMBIENTES MULTICORE Área de Engenharia de Software por David GraminhoVictor Marcello Thiry Comicholi da Costa, Dr. Orientador São José (SC), junho de 2008

Upload: others

Post on 01-Aug-2020

2 views

Category:

Documents


0 download

TRANSCRIPT

UNIVERSIDADE DO VALE DO ITAJAÍ CENTRO DE CIÊNCIAS TECNOLÓGICAS DA TERRA E DO MAR

CURSO DE CIÊNCIA DA COMPUTAÇÃO

AVALIAÇÃO DE TÉCNICAS DE ENGENHARIA DE SOFTWARE PARA MODELAGEM DE PROGRAMAS PARALELOS EM AMBIENTES

MULTICORE

Área de Engenharia de Software

por

David GraminhoVictor

Marcello Thiry Comicholi da Costa, Dr. Orientador

São José (SC), junho de 2008

UNIVERSIDADE DO VALE DO ITAJAÍ CENTRO DE CIÊNCIAS TECNOLÓGICAS DA TERRA E DO MAR

CURSO DE CIÊNCIA DA COMPUTAÇÃO

AVALIAÇÃO DE TÉCNICAS DE ENGENHARIA DE SOFTWARE PARA MODELAGEM DE PROGRAMAS PARALELOS EM AMBIENTES

MULTICORE

Área de Engenharia de Software

por

David Graminho Victor

Relatório apresentado à Banca Examinadora do Trabalho de Conclusão do Curso de Ciência da Computação para análise e aprovação. Orientador: Marcello Thiry Comicholi da Costa, Dr

São José (SC), junho de 2008

ii

DEDICATÓRIA

Dedicado àquela que considero a responsável por toda e qualquer conquista em minha vida:

minha mãe!

iii

AGRADECIMENTOS

Agradeço ao meu orientador Marcello Thiry pelo empenho, profissionalismo e pontualidade

nos momentos que se fizeram necessários. Sem dúvida alguma, a realização desse trabalho não seria

possível sem seu apoio.

Agradeço aos meus familiares e amigos que de alguma forma me incentivaram direta e

indiretamente.

E agradeço à minha mãe, Araceli Graminho...por tudo! Não consigo me lembrar de nada que

eu não deva agradecê-la!

iv

SUMÁRIO

LISTA DE ABREVIATURAS...................................................................v LISTA DE FIGURAS................................................................................vi LISTA DE TABELAS..............................................................................vii RESUMO..................................................................................................viii ABSTRACT................................................................................................ix 1. INTRODUÇÃO ....................................................................................10 1.1 PROBLEMATIZAÇÃO................................................................................. 13 1.1.1 Formulação do Problema ............................................................................ 13 1.1.2 Solução Proposta .......................................................................................... 15 1.2 OBJETIVOS ................................................................................................... 16 1.2.1 Objetivo Geral.............................................................................................. 16 1.2.2 Objetivos Específicos.................................................................................... 16 1.3 METODOLOGIA........................................................................................... 17 1.3.1 Caracterização da metodologia ................................................................... 17 1.3.2 Metodologia de desenvolvimento................................................................. 17 1.4 ESTRUTURA DO TRABALHO ................................................................... 17

2. FUNDAMENTAÇÃO TEÓRICA ......................................................19 2.1 VANTAGENS DA ARQUITETURA MULTICORE................................... 21 2.1.1 Paralelismo implícito versus paralelismo explícito..................................... 22 2.2 A ARQUITETURA MULTICORE............................................................... 24 2.2.1 Taxonomia de computadores paralelos....................................................... 24 2.2.2 Multiprocessadores ...................................................................................... 25 2.3 DESEMPENHO DE COMPUTADORES MULTIPROCESSADOS.......... 30 2.4 MODELOS DE PROGRAMAÇÃO PARALELA........................................ 32 2.4.1 Estrutura básica de software paralelo ........................................................ 33 2.4.2 Linguagens para programação paralela ..................................................... 37 2.5 MODELAGEM DE SOFTWARE PARALELO .......................................... 38 2.5.1 UML.............................................................................................................. 39 2.5.2 Redes de Petri............................................................................................... 43

3. MODELAGEM DO ESTUDO DE CASO.........................................45 3.1 ESTUDO DE CASO ...................................................................................... 45 3.1.1 Modelagem (Paradigma seqüencial) .......................................................... 46 3.1.2 Modelagem (Paradigma paralelo) .............................................................. 49

4. AVALIAÇÃO E TRABALHOS FUTUROS.....................................58 REFERÊNCIAS BIBLIOGRÁFICAS ...................................................64

v

LISTA DE ABREVIATURAS

CFD Computational Fluid Dynamics CPU Central Processing Unit HTM Hardware Transactional Memory JVM Java Virtual Machine MIMD Multiple Instruction Multiple Data MISD Multiple Instruction Single Data NUMA NonUniform Memory Access RPC Remote Procedure Call SIMD Single Instruction Multiple Data SISD Single Instruction Single Data SMP Symmetric Multiprocessors SPMD Single Program Multiple Data STM Software Transactional Memory TCC Trabalho de Conclusão de Curso UMA Uniform Memory Access UML Unified Modeling Language UNIVALI Universidade do Vale do Itajaí

vi

LISTA DE FIGURAS

Figura 1. Transistores ....................................................................................................................11 Figura 2. Velocidade do processador e da memória .......................................................................12 Figura 3. Arquitetura interna de processador multicore da Intel .....................................................12 Figura 4. Método Java compartilhado por dois processos paralelos................................................14 Figura 5. Planos da Intel para produção de processadores multicore ..............................................19 Figura 6. Arquitetura multicore de barramento único .....................................................................28 Figura 7. Componente seqüencial e seções paralelizáveis ..............................................................31 Figura 8. Ganho perfeito em relação ao ganho real de desempenho de sistema paralelos................32 Figura 9. Classe ativa ....................................................................................................................40 Figura 10. Processos paralelos em Redes de Petri: (a) paralelização; (b) sincronização ..................44 Figura 11. Código Java para processos paralelos............................................................................50 Figura 12. Classe Task...................................................................................................................51 Figura 13. Grafo para o problema do caixeiro viajante ........................................................... .......53 Figura 14. Método executado em paralelo......................................................................................56

vii

LISTA DE TABELAS

Tabela 1. Taxonomia de Flynn ......................................................................................................25 Tabela 2. Protocolo write-invalidate ..............................................................................................30 Tabela 3. Protocolo write-update....................................................................................................... 30 Tabela 4. Aplicação de métricas (paradigma seqüencial) ...............................................................61 Tabela 5. Aplicação de métricas (paradigma paralelo) ...................................................................62

viii

RESUMO

VICTOR, David Graminho. Avaliação de técnicas de engenharia de software para modelagem de programas paralelos em ambientes multicore. São José, 2008. 55 f. Trabalho de Conclusão de Curso (Graduação em Ciência da Computação)–Centro de Ciências Tecnológicas da Terra e do Mar, Universidade do Vale do Itajaí, São José, 2008.

Com o advento da tecnologia multicore os problemas provenientes das limitações relacionadas à miniaturização, como o calor dissipado pelos componentes e a interferência, são atenuados. Além disso, outra vantagem surge com a tecnologia multicore: ao dividir o processamento em vários núcleos, há a possibilidade de executar as instruções das aplicações de forma paralela ao invés da execução serial, aumentando a escalabilidade e desempenho dessas aplicações. A arquitetura multicore possui, dentro de um único chip de processador, dois ou mais núcleos de execução e possibilita – com o software apropriado – a efetiva execução paralela de múltiplas threads. No entanto, para haver uma efetiva paralelização é necessário que o software também seja paralelizado de alguma forma. No modelo atual de desenvolvimento de software, o projetista utiliza uma abstração de alto nível do hardware no qual o software irá executar, sem se preocupar com detalhes de restrições físicas. Porém, para o modelo paralelo, os aspectos nos níveis mais baixos de abstração devem ser considerados. Novos paradigmas deverão ser buscados em diferentes linhas de pesquisa. Na Engenharia de Software, por exemplo, será necessário buscar abstrações adequadas à necessidade de otimização no nível físico. É justamente esse o foco principal do presente trabalho: avaliar técnicas atuais de modelagem de software paralelo que possibilitem sua implementação em nível adequado de abstração sem descuidar da escalabilidade e de maior aproveitamento da tecnologia multicore, visando garantir o nível de concorrência adequado. Palavras-chave: multicore, software paralelo, Engenharia de Software.

ix

ABSTRACT

With the advent of the multicore technology the problems proceeding from the limitations related to the miniaturization, as the heat wasted for the components and the interference, are attenuated. Beyond this solution having brought excellent benefits, another great advantage appears with the multicore technology: when dividing the processing in some cores, has the possibility to execute the instructions of the applications of parallel form instead of the serial execution being increased the scalability and performance of these applications. The multicore architecture inside possess of only chip of processor two or more cores of execution and makes possible - with appropriate software - the effective parallel multiple execution threads. However, to have an effective parallelization it is necessary that software also is paralleled of some form. In the current model of software development, the designer uses an abstraction of high level of the hardware in which software will go to execute, without if worrying about details of physical restrictions. However, for the parallel model, the aspects in the abstraction levels lowest must be considered. New paradigms will have to be searched in different lines of research. In the Engineering of Software, for example, it will be necessary to search adequate abstractions to the necessity of optimization in the physical level. And is exactly this the main focus of gift work. Thus, objective to evaluate current techniques of modeling of parallel software that make possible its implementation in an adequate level of abstraction without neglecting of the scalability and a bigger exploitation of the multicore technology aiming at to guarantee the adequate level of competition. Keywords: multicore, parallel software, Software Engineering.

1. INTRODUÇÃO

Há mais de três décadas o engenheiro Gordon Moore previu que a densidade1 de transistores

em um chip duplicaria a cada 18 meses. Essa taxa de crescimento é conhecida popularmente como

lei de Moore (FAZZIO, 2004). Essa lei baseou-se na verificação de Moore ao observar que a

dimensão linear do transistor se reduziria a cada ciclo de uma nova tecnologia, geralmente

impulsionada pela necessidade de obter melhores índices de desempenho. O aumento na velocidade

com que um computador executa uma operação está diretamente relacionado com a densidade de

transistores em um chip (FAZZIO, 2004). Com isso, consegue-se aumentar continuamente a

freqüência de operação dos processadores. A solução aplicada visando alcançar esse objetivo foi a

fabricação de transistores em camadas de silício cada vez menos espessas a fim de reduzir o seu

tamanho (CARDOSO, ROSA & FERNANDES, 2006).

A Figura 1 apresenta o tamanho das camadas de silícios e das portas de alguns transistores

existentes e de alguns protótipos. A fabricante de processadores Intel prevê, para o ano de 2011

aproximadamente, que a tecnologia a ser empregada possibilitará produzir transistores com drenos

de 22 nm (nanomilímetros) e portas de 10 nm (apresentado na Figura 1 como L). Esse cenário é

preocupante uma vez que, quanto menor for a largura da porta, mais próximas ficarão as regiões da

fonte e dreno do transistor. Segundo Cardoso, Rosa e Fernandes (2006) quando a largura da porta

chegar a 5 nm, fonte e dreno ficarão separadas por um trecho de silício tão pequeno que não

conseguirá isolá-los completamente, gerando uma probabilidade de 50% de que a corrente flua

mesmo quando não houver tensão aplicada à porta. Quando isso ocorre o transistor deixa de ser

confiável como dispositivo de processamento de dados. Assim, a miniaturização alcançará os

limites impostos pelas leis da física (FAZZIO, 2004).

Há ainda outro problema: a dissipação de energia. Como o espaço dentro de um processador

é limitado e os componentes exigem energia, a intensa junção desses aumenta a densidade

provocando interferências e uma produção excessiva de calor devido a dissipação de energia

causada pela corrente elétrica que circula nos transistores. Se essa energia não for rapidamente

removida do circuito e transferida para o ambiente, o chip atingirá temperaturas tão elevadas que,

literalmente, derreterá (CARDOSO, ROSA & FERNANDES, 2006).

1 Densidade é o número de transistores por unidade de área

11

Figura 1. Transistores

Fonte: adaptado de Cardoso, Rosa e Fernandes (2006) e Fazzio (2004)

Segundo Paolo Gargine (apud CARDOSO, ROSA & FERNANDES, 2006), Diretor de

Tecnologia da Intel, mesmo que se conseguisse contornar o limite da largura da porta, não haveria

como remover dele o calor com a mesma rapidez com que seria produzido. O chip se autodestruiria.

Há ainda outras limitações impostas pela arquitetura de um único núcleo como a estreita

banda de dados, que faz com que 75% do tempo do processador (em média) seja gasto esperando

por resultados dos acessos à memória devido à grande diferença entre a velocidade do processador e

a da memória (CARDOSO, ROSA & FERNANDES, 2006). A Figura 2 ilustra essa diferença (gap)

de velocidade. Por último, existe o custo financeiro relacionado aos processos de miniaturização.

No contexto apresentado, pode ocorrer o aumento do preço do transistor e não a diminuição, como

acontece atualmente. Em síntese, dentro do paradigma da atual arquitetura, os chips em breve não

mais evoluirão e a lei de Moore será menos relevante (FAZZIO, 2004)

12

Figura 2. Velocidade do processador e da memória

Fonte: MA (2006)

O cenário apresentado impõe limites para a miniaturização e para o número de componentes

em um chip. A tecnologia multicore é uma resposta aos problemas causados pela miniaturização

dos componentes no núcleo de um processador. Sinteticamente, a arquitetura multicore consiste em

agregar dentro de um único chip de processador dois ou mais núcleos de execução, possibilitando –

com o software apropriado – a efetiva execução paralela de múltiplas threads. O sistema

operacional trata cada um desses núcleos de execução como processadores diferentes, com todos os

seus recursos de execução associados. Cada núcleo de execução possui seu próprio cache e pode

processar várias instruções simultaneamente. A Figura 3 ilustra essa arquitetura

Figura 3. Arquitetura interna de processador multicore da Intel

Com o advento da tecnologia multicore as limitações relacionadas à miniaturização, como o

calor dissipado pelos componentes e a interferência, são atenuadas. Há outra vantagem relacionada

13

à tecnologia multicore: ao dividir o processamento em vários núcleos, há a possibilidade de

executar as instruções das aplicações de forma paralela ao invés da execução serial

(TANENBAUM, 2001), aumentando a escalabilidade e desempenho dessas aplicações. Com a

adoção de dois ou mais núcleos, é possível obter o paralelismo real entre as threads de uma

aplicação (PATTERSON & HENNESSY, 2000). O conceito de escalabilidade será apresentado na

Subseção 0

No entanto, para a efetiva execução paralela de múltiplas threads é necessário que haja um

software apropriado. Um processador multicore que não possui tal software para ser executado tem

pouca ou nenhuma utilidade (TANENBAUM, 2001). Assim, para haver uma efetiva paralelização é

necessário que o software também seja paralelizado de alguma forma.

No modelo atual de desenvolvimento de software, o projetista utiliza uma abstração de alto

nível do hardware no qual o software irá executar, sem se preocupar com detalhes de restrições

físicas. Porém, para o modelo paralelo, os aspectos nos níveis mais baixos de abstração devem ser

considerados. Novos paradigmas deverão ser buscados em diferentes linhas de pesquisa. Na

Engenharia de Software, por exemplo, será necessário buscar abstrações adequadas à necessidade

de otimização no nível físico. E é justamente esse o foco principal do presente TCC (Trabalho de

Conclusão de Curso). Assim, objetiva-se avaliar técnicas atuais de modelagem de software paralelo

que possibilitem sua implementação em um nível adequado de abstração sem descuidar da

escalabilidade e de um maior aproveitamento da tecnologia multicore, visando garantir o nível de

concorrência2 adequado.

1.1 Problematização

1.1.1 Formulação do Problema

Existem vários fatores que causam a subutilização de sistemas multicore. Aparentemente, o

software é o principal obstáculo para o uso difundido de processadores paralelos (PATTERSON &

HENNESSY, 2000). Essa constatação deve-se aos problemas inerentes à programação paralela

relacionadas à comunicação e sincronização de processos. Como a arquitetura dos processadores

multicore caracteriza-se pelo compartilhamento de recursos a programação paralela para esses

ambientes requer algum mecanismo de coordenação. O exemplo da Figura 4 demonstra os

2 Concorrência e paralelismo serão considerados sinônimos no presente trabalho

14

problemas que podem ocorrer com o paralelismo. Para essa abordagem, devem-se desconsiderar

momentaneamente os aspectos relacionados à paralelização automática (ou implícita), às políticas

transacionais ou mecanismos de controle intrínsecos a uma determinada tecnologia. A intenção

nesse exemplo é demonstrar os problemas que o paralelismo pode causar quando não há qualquer

tipo de controle entre os processos paralelos de uma aplicação.

O exemplo da Figura 4 contém um método em Java denominado saque. Esse método é

responsável por deferir o saque baseado no saldo disponível e na quantia a ser sacada. Se houver

saldo, o saque é autorizado. Caso contrário, o saque é indeferido.

Figura 4. Método Java compartilhado por dois processos paralelos.

Considera-se, para o exemplo, um saldo inicial de R$1.000,00 e dois processos executando

em paralelo, denominados processos A e B. Ao iniciar a execução da aplicação, o seguinte

cenário pode ocorrer:

• Processo A realiza uma chamada ao método saque, passando como parâmetro o valor

700, representando a quantia a ser sacada;

• Simultaneamente, o processo B realiza uma outra chamada ao método saque passando

como parâmetro o valor 500, representando a quantia a ser sacada;

• O processo A verifica na linha 10 que saldo é maior que quantia e continua sua

execução;

15

• Por se tratar de tarefas paralelas, é possível que o processo B verifique a condição da

linha 10 momentos antes do processo A efetuar a operação de subtração da linha 11.

Como ainda não foi efetuada a operação de subtração, a aplicação interpreta como

verdadeira a condição e permite que o processo B continue sua execução. Nesse ponto,

há um erro de controle;

• O processo A efetua a subtração. Agora, saldo possui R$300,00; e

• O processo B também efetua a subtração. Agora, saldo possui R$200,00 negativos.

Nesse ponto o erro se consolidou.

Esse tipo de problema, natural ao paralelismo, admite a interferência entre processos

paralelos que acessem recursos compartilhados, podendo causar a inconsistência de dados. Portanto,

os problemas inerentes ao paralelismo devem ser tratados.

1.1.2 Solução Proposta

Há duas abordagens para o paralelismo: implícito e explícito. Ambas, à sua maneira, dispõe

de mecanismos que visam resolver o problema citado na Subseção 1.1.1 . O paralelismo implícito

abstrai os problemas inerentes à programação paralela, ou seja, toda a paralelização ocorre de forma

automática e transparente para o desenvolvedor de software. É possível que a responsabilidade

sobre tal paralelização fique a cargo de um compilador que receba como entrada um programa

seqüencial escrito em uma predeterminada linguagem e produza um programa paralelo eficiente. O

grande problema nessa abordagem resume-se no grau de dificuldade em construir tal compilador

(GRUNE et al., 2001).

A abordagem alternativa ao paralelismo implícito é o paralelismo explícito. Nesse caso, a

paralelização do software – e os problemas inerentes à programação paralela - fica a cargo do

desenvolvedor. Por esse motivo, geralmente, essa abordagem é menos desejada que a anterior

(paralelismo implícito). É possível tratar os problemas inerentes à programação paralela, como

aqueles relacionados à sincronização entre processos, por meio de primitivas de sincronização. Há,

inclusive, mecanismos que se propõe a facilitar a programação paralela abstraindo a própria

sincronização entre os processos por meio de memórias transacionais como o STM (Software

Transactional Memory) ou HTM (Hardware Transactional Memory) (ADL-TABATAI,

KOZYRAKIS & SAHA, 2006). De qualquer forma, como a responsabilidade é do desenvolvedor

de software decidir parcialmente ou totalmente as questões referentes ao paralelismo, a fase de

16

modelagem do software deve contemplar os aspectos paralelos. Existem técnicas de modelagem

com suporte ao paralelismo em um nível relativamente alto de abstração. Um exemplo é a UML,

com a qual é possível modelar processos paralelos abstraindo parcialmente os aspectos dos níveis

mais baixos da arquitetura de computadores.

O presente TCC se propôs a avaliar técnicas atuais de Engenharia de Software por meio da

identificação e aplicação dessas técnicas para a modelagem de softwares paralelos.

Especificamente, foi modelada uma aplicação onde a adoção de processadores multicore e a

conseqüente paralelização do software é justificada pela necessidade de melhores índices de

desempenho. A aplicação em questão visa tratar os aspectos paralelos que podem ser aplicados

sobre o problema do caixeiro-viajante. A modelagem considerou tratar os problemas inerentes à

programação paralela sem descuidar dos requisitos da aplicação. Para a avaliação propriamente dita,

foram adotadas técnicas presentes na Engenharia de software capazes de mensurar

aproximadamente o esforço e tempo despendidos no processo de modelagem do estudo de caso.

A proposta desse trabalho visa contribuir com o desenvolvimento de software paralelo por

meio da compilação de técnicas constantes na Engenharia de Software que contemplem o

paralelismo. Porém, é válido ressaltar que o presente TCC não se propõe a desenvolver uma nova

metodologia para modelagem de softwares paralelos tampouco qualificar o desempenho entre

programas seqüenciais e paralelos.

Multicomputadores, clusters e compiladores de paralelização automática estão fora do

escopo desse trabalho. O presente TCC restringe-se ao processamento paralelo baseado na

tecnologia multicore.

1.2 Objetivos

1.2.1 Objetivo Geral

Avaliar técnicas atuais de Engenharia de Software para a modelagem de programas

paralelos em ambientes multicore.

1.2.2 Objetivos Específicos

1. Identificar técnicas atuais da Engenharia de Software que contemplem a concorrência;

2. Analisar as técnicas identificadas no item 1 a fim de selecionar aquelas que atendam aos

requisitos de natureza paralela de uma aplicação;

17

3. Aplicar as técnicas selecionadas no item 2 visando implementar os aspectos paralelos de

uma aplicação; e

4. Avaliar os resultados obtidos baseando-se em métricas estruturais orientada a objetos.

1.3 Metodologia

1.3.1 Caracterização da metodologia

O presente trabalho é de natureza aplicada, pois a proposta consiste em avaliar as técnicas de

Engenharia de Software para a modelagem de programas paralelos em ambientes multicore pela

identificação, análise e aplicação daquelas técnicas em um software específico. A abordagem será

qualitativa, uma vez que o trabalho culmina em uma avaliação sem utilizar técnicas estatísticas. Do

ponto de vista dos objetivos, a pesquisa será basicamente exploratória, pois a avaliação envolvida

possibilita a construção de hipóteses. Vinculado aos objetivos, verifica-se que os procedimentos

técnicos serão baseados principalmente em pesquisa bibliográfica, consistindo inclusive de material

obtido na internet.

1.3.2 Metodologia de desenvolvimento

O TCC é dividido em 3 fases: fundamentação teórica, modelagem, implementação e

avaliação. O TCC I contempla totalmente a fase de fundamentação teórica e parcialmente a fase de

modelagem e implementação, deixando a cargo do TCC II o restante. Assim, neste trabalho houve a

análise da arquitetura multicore, dos modelos de programação paralela e das técnicas para

modelagem de softwares paralelos por meio de pesquisa bibliográfica. Também foi determinado um

estudo de caso onde adoção de processadores multicore e a conseqüente paralelização do software

seja justificada pela necessidade de melhores índices de desempenho.

1.4 Estrutura do trabalho

Este documento está estruturado em quatro capítulos. O Capítulo 1, Introdução, apresenta

um breve histórico sobre os motivos que culminaram no desenvolvimento de processadores

multicore. Foram abordados os impactos provocados por tais processadores no desenvolvimento de

software paralelo. No Capítulo 2, Fundamentação Teórica, é apresentada a arquitetura dos

processadores multicore, as vantagens que esses processadores proporcionam e um comparativo

18

entre o paralelismo implícito e o paralelismo explícito. Serão apresentados ainda os impactos que a

tecnologia multicore e a programação paralela provocam no desenvolvimento de software e as

técnicas de Engenharia de Software para a modelagem de software paralelo. O Capítulo 3 apresenta

o estudo de caso a ser modelado segundo as técnicas de modelagem para software paralelo

identificadas no decorrer desse trabalho. Concluindo, o Capítulo 4 apresenta as considerações finais

a respeito da avaliação das técnicas de Engenharia de Software para a modelagem de softwares

paralelos em ambientes multicore.

19

2. FUNDAMENTAÇÃO TEÓRICA

Caso as previsões dos fabricantes de processadores do mercado tornem-se realidade, os

processadores multicore provavelmente serão a plataforma comum para servidores, dispositivos

móveis e desktops. Assim, os processadores multicore se tornarão o padrão industrial e não a

exceção (SHIEL, 2006). Conforme ilustrado na Figura 5. Planos, os planos da fabricante de

processadores Intel consistem em aumentar gradativamente a produção de seus processadores

multicore até o ponto em que esses se tornem a maioria disponível no mercado. Essa tendência pode

provocar uma demanda por softwares paralelos além de investimentos em processos e metodologias

para desenvolvimento.

Figura 5. Planos da Intel para produção de processadores multicore

Fonte: Intel (2005)

A opção por desenvolver software paralelo depende das necessidades específicas de cada

aplicação. Se o desempenho for elemento crítico, despender esforços para desenvolver software

paralelo pode ser uma solução viável ao considerar os possíveis ganhos que o desempenho venha

20

agregar à aplicação. Sistemas de tempo real além das aplicações que demandam complexas

simulações 3D, bancos de dados maiores, interfaces de usuário mais sofisticadas e níveis adicionais

de segurança são exemplos onde o desempenho é crítico e requerem maior poder de processamento

(CARDOSO, ROSA & FERNANDES, 2006). Para esses, os ganhos conseguidos por meio dos

sistemas multicore e a conseqüente paralelização do software podem ser significativos. Assim, a

adoção do hardware paralelo está diretamente ligada à necessidade em prover mais recursos

computacionais de forma sustentável para as aplicações. Segundo Shiel (2006) há dois motivos

relacionados à adoção de hardware capaz de executar software em paralelo:

• pela necessidade de uma aplicação executar mais rapidamente determinado conjunto de

dados: múltiplos processadores permitem a execução de uma mesma tarefa por vários

processadores simultaneamente. Esse é o conceito generalizado de processamento

paralelo. O ganho de desempenho depende da organização dos processadores, da

linguagem de programação utilizada e do grau de paralelismo possível na aplicação

(MACHADO & MAIA, 2002). O processamento paralelo e os modelos associados são

abordados na Seção 0.

• pela necessidade de uma aplicação suportar mais usuários finais ou maior conjunto de

dados: múltiplos processadores permitem a execução simultânea de diversas tarefas

independentes, aumentando o throughput3 do sistema. Servidores de banco de dados e

servidores web são exemplos desses ambientes, onde o aumento no número de

processadores permite atender número maior de usuários simultaneamente (MACHADO

& MAIA, 2002)

Para as duas abordagens, há duas possíveis soluções: (i) incrementar o poder de processamento dos

recursos que compõe um sistema e/ou (ii) adicionar mais recursos ao sistema. A primeira solução

consiste em redimensionar um sistema verticalmente, como por exemplo, substituindo um

determinado processador de 1Ghz por outro com pinagem compatível de 2Ghz. A segunda solução

consiste em redimensionar o sistema horizontalmente adicionando uma nova unidade de

processamento, a exemplo de como ocorre nos processadores multicore.

21

2.1 Vantagens da arquitetura multicore

Machado e Maia (2002) descrevem vários benefícios provenientes dos sistemas com

múltiplos processadores.

• desempenho: a princípio, sempre que novos processadores são adicionados à arquitetura

de uma máquina, melhor é o desempenho do sistema. No entanto, o ganho real de

desempenho não é linear ao número de processadores adicionados. Essa questão é

abordada com mais detalhes na Seção 0;

• escalabilidade: é a proporção entre o aumento de desempenho de um sistema com o

acréscimo de hardware e a capacidade acrescida. Ou seja, um sistema ao qual se pode

adicionar mais processadores e aumentar de modo correspondente a sua potência

computacional é dito escalável (TANENBAUM, 2001). Ao ampliar a capacidade de

computação apenas adicionando-se novos processadores é possível reduzir os custos em

relação à aquisição de um outro sistema com maior desempenho;

• relação custo/desempenho: sistemas com múltiplos processadores permitem utilizar

CPUs convencionais de baixo custo, interligadas às unidades funcionais por meio de

mecanismos de interconexão. Dessa forma, é possível oferecer sistemas de alto

desempenho com custo aceitável;

• tolerância a falhas e disponibilidade: a tolerância a falhas é a capacidade de manter o

sistema em operação mesmo em casos de falhas de algum componente. Nessa situação,

mesmo se um dos processadores falhar, os demais podem assumir suas funções de

maneira transparente aos usuários e suas aplicações, embora com menos capacidade

computacional. A disponibilidade é medida em número de minutos por ano que o

sistema permanece em funcionamento de forma ininterrupta, incluindo possíveis falhas

de hardware ou software, manutenções preventivas e corretivas; e

• balanceamento de carga: é a distribuição do processamento entre os diversos

componentes da configuração do sistema, mantendo todos os processadores ocupados o

maior tempo possível. As tarefas são movidas dos processadores sobrecarregados para

3 Throughput pode ser entendido como a quantidade de dados processados em determinado espaço de tempo

22

processadores com menor carga de processamento. Servidores de banco de dados que

oferecem esse tipo de facilidade, permitem que as solicitações dos diversos usuários

sejam distribuídas entre os vários processadores disponíveis.

Apesar das vantagens, os sistemas com múltiplos processadores também apresentam, dadas

suas características, desvantagens. As principais são aquelas relacionadas aos problemas de

comunicação e sincronização entre os processadores que estejam acessando as mesmas posições de

memória. Nesse ponto, há um fator crítico a ser considerado: as soluções para os problemas

causados pela concorrência podem ser abordados de forma implícita ou explícita. A Subseção 0

aborda com mais detalhes essa questão.

2.1.1 Paralelismo implícito versus paralelismo explícito

A programação paralela geralmente consiste em dividir o problema a ser processado em

tarefas, alocando e sincronizando essas tarefas nos processadores. Há duas abordagens para essa

atividade:

• paralelismo implícito, onde o sistema (compiladores de paralelização automática ou

outro programa capaz de realizar algum tipo de paralelização automática) particiona o

programa e aloca tarefas aos processadores automaticamente; e

• paralelismo explícito, onde o particionamento do programa em tarefas e conseqüente

alocação de recursos fica sob o controle do programador.

Paralelismo implícito

Para Grune et al. (2001), o ápice da paralelização seria a construção de um compilador que

receba como entrada um programa seqüencial escrito em uma predeterminada linguagem e produza

um programa paralelo eficiente. O multiprocessamento seria transparente aos analistas, ficando sob

sua responsabilidade apenas inserir no projeto os requisitos necessários ao processamento paralelo.

O problema nessa abordagem é saber se esse compilador pode ser implementado. Segundo Grune et

al. (2001), ainda não existe compilador desse tipo e é difícil afirmar se alguém conseguirá criá-lo,

apesar de haver um progresso substancial com a paralelização automática.

Há ainda a possibilidade de delegar ao sistema operacional funções de paralelização

automática. No entanto, uma tendência recente é mover funcionalidades do sistema operacional

para o ambiente de tempo de execução. As vantagens para essa abordagem constituem-se em

23

poupar interações dispendiosas com o sistema operacional e delegar ao desenvolvedor do

compilador um controle maior sobre a implementação das funcionalidades. Percebe-se aqui que,

geralmente, o limite entre o ambiente de tempo de execução e o sistema operacional é vago. Assim,

há de se analisar cuidadosamente onde exatamente ocorrerá o paralelismo nessa abordagem.

Tanenbaum (2003) cita que há várias organizações possíveis ao abordar sistemas

operacionais para multiprocessadores. Atualmente, o modelo SMP (Symmetric Multiprocessor –

Multiprocessador Simétrico) é amplamente utilizado pelos multiprocessadores modernos. Nesse

modelo, existe uma cópia do sistema operacional na memória sendo que qualquer CPU pode

executá-la. Assim, quando uma chamada ao sistema é efetuada, a CPU local chamada é desviada

para o núcleo e a processa. No entanto, mesmo nesse modelo, há problemas críticos relacionados ao

desempenho, à gerência de regiões críticas e à sincronização dos núcleos de processamento

persistindo, portanto, as dúvidas sobre a viabilidade em delegar completamente o paralelismo ao

sistema operacional.

Paralelismo explícito

Segundo Patterson e Hennessy (2003), o paralelismo pode ocorrer no nível de thread. Ao

considerar essa afirmação, conclui-se que o papel dos analistas e desenvolvedores de software fica

mais evidente na concepção de sistemas que possuam o paralelismo como requisito, uma vez que,

geralmente, é delegado a esses a responsabilidade da gerência das threads. O grande problema

nessa abordagem é que a programação paralela não é trivial. Patterson e Hennessy (2000) citam

alguns motivos pelos quais desenvolver programas para processamento paralelo são muito mais

difíceis do que os programas seqüenciais. Entre eles está a obrigatoriedade em obter um bom

desempenho e alta eficiência dos programas que executam em um sistema multiprocessador, pois

caso contrário, é melhor utilizar um sistema monoprocessado, cuja programação é muito mais

simples. Há, no entanto, alguns avanços na área de programação paralela. Por exemplo, a fabricante

de processadores Intel publicou pesquisas que visam facilitar a programação paralela em arquitetura

multicore e a fabricante de processadores AMD criou um mecanismo denominado LWP (Light-

Weight Profiling) que permite ao software executar melhor suas funções nas plataformas multicore.

Mesmo com esses avanços, ainda assim é difícil construir abstrações de modo que

funcionem de maneira segura na presença de vários fluxos de controle (BOOCH, RUMBAUGH &

JACOBSON, 2006). Pode haver um excesso de engenharia em sua visão de processo (muitos fluxos

concorrentes e o sistema acaba falhando) ou uma engenharia escassa (uma concorrência insuficiente

não otimiza o tempo de resposta do sistema). Outro fator está relacionado ao desenvolvimento do

24

software propriamente dito. Assim, identificar na aplicação a ser desenvolvida frações passíveis de

paralelismo, além de gerenciar a comunicação e a sincronização entre os processos, são atividades

candidatas à incorporação na análise e projeto de sistemas. Os analistas e desenvolvedores que

estiverem inseridos nesse contexto podem se deparar com a necessidade de formalizar novos

processos de desenvolvimento a serem integrados a Engenharia de Software.

Um desafio é definir um nível adequado de abstração de forma que seja possível aos

projetistas e programadores desenvolverem software que atenda aos requisitos do processamento

paralelo sem comprometer significativamente a produtividade. A Seção 0 aborda a modelagem de

sistemas com mais detalhes.

2.2 A arquitetura multicore

O que torna o desenvolvimento de programas para processamento paralelo mais difícil do

que os programas seqüenciais é que o programador precisa conhecer o funcionamento do hardware.

Em um sistema monoprocessado, o programador da linguagem de alto nível abstrai questões sobre a

organização do hardware. Essa responsabilidade é delegada ao compilador. No entanto, em

máquinas paralelas é fundamental o conhecimento do hardware para que seja possível desenvolver

programas aptos a executar em máquinas com vários processadores.

2.2.1 Taxonomia de computadores paralelos

Flynn (1972 apud TANENBAUM, 2001) criou uma taxonomia para classificar os sistemas

de computadores em geral. Essa taxonomia baseia-se em dois conceitos: (i) seqüência de instruções

e (ii) seqüência de dados. Tanenbaum (2001) cita que para cada seqüência de instruções há um

registrador program counter (PC). Assim, um sistema com n processadores tem n registradores

program counter e, por conseqüência, n seqüências de instruções. A seqüência de dados é formada

por um conjunto de operandos. As seqüências de instruções e de dados são, de certa forma,

independentes, acarretando em quatro combinações possíveis dessas seqüências, conforme descrito

na

Tabela 1. Taxonomia de Flynn.

As máquinas SISD possuem uma única seqüência de instruções e de dados, não admitindo

qualquer tipo de paralelismo. Essas são as máquinas clássicas de Von Neumann. Com relação à

categoria MISD, devido as suas características naturais (várias instruções operando sobre um

conjunto único de dados), não está claro se alguma máquina existente pode ser classificada nessa

25

categoria MISD. As máquinas que pertencem à categoria SIMD têm uma única unidade de controle

que trata uma única instrução de cada vez, mas possuem várias UALs (Unidades Aritméticas

Lógicas) que podem executar essa instrução em vários conjuntos de dados simultaneamente

(TANENBAUM, 1999). As máquinas pertencentes à categoria MIMD são aquelas formadas por um

conjunto de processadores independentes onde cada processador executa diferentes instruções. A

maioria das máquinas paralelas enquadra-se nessa categoria, incluindo os processadores multicore,

sendo, portanto a categoria mais relevante para o presente TCC.

Tabela 1. Taxonomia de Flynn

Seqüência De instruções

Seqüência de dados

Nome Exemplos

1 1 SISD Máquina clássica de Von Neumann

1 Várias SIMD Computador vetorial, processador matricial

Várias 1 MISD Nenhum exemplo

Várias Várias MIMD Multiprocessador, multicomputador

Fonte: Tanenbaum (2001)

A taxonomia de Flynn pode ser estendida. Bell (1985 apud MACHADO & MAIA, 2002)

propôs uma outra maneira de subdividir as arquiteturas MIMD, considerando apenas o grau de

acoplamento da memória principal. Assim, as máquinas pertencentes à categoria MIMD podem ser

subdivididas em dois grupos: (i) multiprocessadores e (ii) multicomputadores. Multicomputadores

são máquinas que possuem seu próprio espaço de endereçamento individual e a comunicação entre

os sistemas é realizada por meio de mecanismos de troca de mensagens (MACHADO & MAIA,

2002). Por esse motivo, os sistemas MIMD inseridos nesse contexto, são classificados como

sistemas fracamente acoplados. Os processadores multicore não pertencem a esse grupo e, por esse

motivo, não será mais considerado no presente TCC. A ênfase será sobre o primeiro grupo, ou seja,

os multiprocessadores.

2.2.2 Multiprocessadores

Multiprocessadores são máquinas com memória compartilhada. A comunicação entre os

diversos processadores é realizada por intermédio de variáveis compartilhadas armazenadas nas

memórias, sendo que todos os processadores têm acesso a todos os endereços da memória

(PATTERSON & HENNESSY, 2000). Assim, os múltiplos processadores acessam toda memória

26

disponível como um espaço de endereço global e são controlados por um único sistema operacional.

Por esse motivo, os sistemas MIMD inseridos nesse contexto, são classificados como sistemas

fortemente acoplados. A maneira como a memória compartilhada é implementada em cada um

deles divide os multiprocessadores em dois grupos baseados principalmente no tempo de acesso à

memória: (i) UMA (Uniform Memory Access – Acesso Uniforme à Memória), na qual os tempos de

acesso para todas as áreas da memória são iguais (uniformes) e (ii) NUMA (NonUniform Memory

Access – Acesso Não-Uniforme à Memória), na qual os tempos de acesso para todas as áreas da

memória não são iguais (não-uniformes). Os sistemas formados por multiprocessadores de memória

compartilhada do tipo UMA também são conhecidos como sistemas SMP (Symmetric

Multiprocessors – Sistemas com Multiprocessadores Simétricos). Machado & Maia (2002) citam

que tais sistemas (SMP) são assim chamados por implementarem a simetria dos processadores, ou

seja, todos os processadores realizam as mesmas funções, em oposição aos sistemas assimétricos,

onde somente um processador, denominado mestre, pode executar serviços do sistema operacional.

Os demais processadores, denominados escravos, precisam requisitar serviços ao processador

mestre. Para Tanenbaum (1999), a denominação SMP deve-se pelo fato de que todos os

processadores têm acesso a todos os módulos de memória, sendo tratados de forma idêntica

(simétrica) pelo sistema operacional. Por idêntica, entenda-se como todos os processadores

realizando as mesmas funções. Apesar de existirem variações do modelo de classificação abordado,

o que foi apresentado é comum à maioria dos modelos sugeridos pelos autores referenciados na

bibliografia deste TCC.

Comunicação entre processadores

As unidades funcionais (processadores, memória principal e dispositivos de E/S) de um

sistema pertencente à categoria MIMD (em especial os sistemas SMP) são interligadas de duas

maneiras básicas a fim de possibilitar a comunicação entre os processadores e as demais unidades

funcionais: (i) unidades funcionais conectadas por meio de um único barramento e (ii) unidades

funcionais conectados por meio de uma rede (PATTERSON & HENNESSY, 2000). Os sistemas

SMP são geralmente interligados por meio de um único barramento. O problema dessa organização

é que somente uma unidade funcional pode utilizar o barramento em determinado instante. Isso

pode produzir um gargalo (vide Figura 6) quando várias unidades tentam acessar o barramento

simultaneamente. Outro problema relacionado ao barramento único é que, caso ocorra algum

problema no barramento, todo o sistema fica comprometido. Uma forma de reduzir a latência das

27

operações de acesso à memória é por meio do uso de caches. Nesse esquema, cada processador

possui seu cache individual, para leitura e escrita de dados. No entanto, essa solução também

introduz um problema conhecido como coerência de cache (MACHADO & MAIA, 2002). Na

medida em que processadores que operam em paralelo precisam compartilhar dados,

independentemente se for para leitura ou escrita, há a necessidade de se estabelecer uma

coordenação para tornar possíveis as operações sobre dados compartilhados (PATTERSON &

HENNESSY, 2000). Essa coordenação é conhecida como sincronização e é abordada na Subseção

0

Machado e Maia (2002) apresentam um exemplo sobre o processo de leitura e escrita de

uma variável e os problemas de sincronização relacionados. Consideram-se, para tal, dois

processadores: PA e PB.

• O processador PA lê uma variável X na memória principal e copia o conteúdo para sua

respectiva cache.

• O processador PA altera o valor de X. Nesse ponto, o valor de X na memória cache é

diferente do valor de X na memória principal. Os valores estão, portanto, inconsistentes.

• O processador PB lê a variável X na memória principal e copia o conteúdo para sua

respectiva cache. Logo, o processador PB possui em sua cache um valor de X diferente

do valor alterado pelo processador PA.

Uma solução para esse problema é conhecida como write-through, e consiste em atualizar a

memória principal sempre que um dado na cache for alterado. No entanto, essa solução degrada o

desempenho devido aos constantes acessos à memória a cada operação de escrita, provocando

tráfego no barramento. Uma alternativa é o esquema write-back que permite alterar o dado na cache

sem alterá-lo imediatamente na memória principal. Esse esquema oferece desempenho melhor que

o esquema write-through, porém, não resolve o problema de inconsistência. Há métodos de

sincronização de processadores por meio da coerência de caches e protocolos que visam manter a

coerência dos vários processadores. Esses, por sua vez, são chamados de protocolos para

manutenção da coerência de caches.

28

Figura 6. Arquitetura multicore de barramento único

Fonte: Ma (2006)

Protocolos para manutenção da coerência de caches

Segundo Patterson e Hennessy (2000), o mais popular protocolo para manutenção da cache

é conhecido como monitoramento (snooping). Nesse protocolo, todos os controladores das caches

monitoram o barramento para determinar se elas têm ou não uma cópia do bloco compartilhado, ou

seja, cada cache verifica se o endereço do dado que trafega no barramento está localmente

armazenado. Caso esse dado seja alterado por qualquer processador a técnica de snooping deve

garantir que os demais obtenham a cópia mais recente. Os protocolos de monitoramento são de dois

tipos:

• write-invalidate (invalidação na escrita): o processador, ao realizar uma operação de

escrita e antes de modificar sua própria cópia local, envia um sinal de invalidação pelo

barramento visando invalidar todas as cópias, armazenadas em outras caches, do dado a

ser escrito (PATTERSON & HENNESSY, 2000). Assim, apenas o processador que

realizou a operação de escrita possuirá uma cópia válida do dado na cache. Os demais

processadores, ao acessarem novamente o dado em suas respectivas caches, terão uma

cópia inválida e o dado deverá ser transferido da memória principal para suas caches

(cache miss) (MACHADO & MAIA, 2002). Há nesse esquema muitos leitores mas

apenas um escritor. A Tabela 2 exemplifica o protocolo write-invalidate.

29

Tabela 2. Protocolo write-invalidate

Processador Barramento Cache do PA Cache do PB Memória

PA lê X Cache miss do PA 0 - 0

PB lê X Cache miss do PB 0 0 0

PA escreve 1 em X Invalidação de X 1 - 1

PB lê X Cachê miss de PB 1 1 1

Fonte: Machado e Maia (2002)

• write-update (atualização na escrita): esse esquema, também conhecido como escrita em

broadcast, ao invés de invalidar cada bloco compartilhado, o processador escritor envia

o novo dado em broadcast pelo barramento. Assim, todas as cópias são atualizadas com

o novo valor (PATTERSON & HENNESSY, 2000). O desempenho dessa técnica é

inferior a técnica de write-invalidate, pois além do endereço do dado a ser atualizado

também deve informar, no barramento, seu novo valor. Por isso, a técnica de write-

invalidate é empregada na maioria dos sistemas SMP (MACHADO & MAIA, 2002). A

Tabela 3 exemplifica o protocolo write-update.

Tabela 3. Protocolo write-update

Processador Barramento Cache do PA Cache do PB Memória

PA lê X Cache miss do PA 0 - 0

PB lê X Cache miss do PB 0 0 0

PA escreve 1 em X Atualização de X 1 1 1

PB lê X Cachê miss de PB 1 1 1

Fonte: Machado e Maia (2002)

A maioria dos sistemas comerciais com múltiplos processadores usam caches write-back

com o protocolo write-invalidate, a fim de reduzir o tráfego no barramento e permitir que um

número maior de processadores possa ser colocado em torno desse barramento.

Segundo Patterson e Hennessy (2000), medidas recentes mostram que dados compartilhados

têm localidade espacial e temporal mais baixa que os demais tipos de dados. Portanto, as faltas no

30

acesso dominam o padrão de acesso aos dados compartilhados armazenados na cache, mesmo

considerando que eles representam de 10% a 40% do total de acessos a dados.

Muitas das soluções apresentadas enfrentam um problema em comum: o gargalo provocado

pelo barramento único. Para evitar esse gargalo, a memória principal pode ser dividida em módulos

para permitir múltiplos acessos simultâneos, podendo ser compartilhada entre as várias unidades

funcionais. As unidades funcionais podem ser conectadas entre si por meio de um barramento

cruzado comutado (crossbar switch), criando uma matriz de interconexão (MACHADO & MAIA,

2002). Segundo Tanenbaum (2003), uma das características mais relevantes de um barramento

cruzado é que ele é uma rede não-bloqueante, ou seja, a conexão solicitada por um processador

nunca é negada caso algum ponto de cruzamento ou linha já esteja ocupado. Nesse esquema, é

possível a comunicação simultânea entre diferentes unidades funcionais, ficando a cargo do

hardware e do sistema operacional a resolução dos possíveis conflitos de acesso a uma mesma

unidade. O problema em uma arquitetura de barramento cruzado, é que para cada n processadores e

n módulos de memória são necessários n² comutadores para interligar todos os pontos. Em uma

configuração onde n seja muito grande o custo tende a ser, conseqüentemente, muito alto

(MACHADO & MAIA, 2002).

2.3 Desempenho de computadores multiprocessados

A maneira mais simples de se melhorar o desempenho de um sistema é aumentando o

número de processadores disponíveis (TANENBAUM, 2001). Se a relação fosse de 1:1, ou seja, se

o uso de n processadores acarretasse em n vezes a execução mais rápida de um programa, haveria

um ganho perfeito de desempenho No entanto, o desempenho não melhora linearmente apenas

adicionando mais processadores ou mais núcleos. Assim, adicionar n núcleos de execução dentro de

um chip não significa que o desempenho de um programa executando nesses núcleos será n vezes

maior. Essa virtual impossibilidade de se obter o ganho perfeito deve-se, em parte, ao fato de que

todos os programas têm componentes seqüenciais. Esses componentes ocorrem, na maioria das

vezes, na fase de inicialização, leitura de dados ou obtenção de resultados desse mesmo programa

(TANENBAUM, 2001). A Figura 7. Componente seqüencial e seções paralelizáveis ilustra o

componente seqüencial e as seções paralelizáveis de um programa qualquer. De acordo com a

Figura 5, F é a fração de tempo executando código eminentemente seqüencial, (1 – F) a fração de

tempo executando código potencialmente paralelizável e N é o número de processadores (ou

núcleos) adicionados ao sistema.

31

Figura 7. Componente seqüencial e seções paralelizáveis

Fonte: Adaptado de Charão (2006)

A impossibilidade de se chegar ao ganho perfeito em razão do componente seqüencial é

contemplada pela lei de Amdahl. Essa lei determina o ganho real através da utilização de vários

processadores paralelos em relação ao uso de apenas um único processador. O ganho real pode ser

obtido através da Equação 1:

Equação 1

Assim, caso não existisse o componente seqüencial em dado programa, então F seria igual a 0

(zero). Após algumas simplificações, constatar-se-ia que o ganho seria diretamente proporcional ao

número de processadores (ou núcleos) adicionados ao sistema. No entanto, como o componente

seqüencial é imperativo (pelo menos atualmente) nos programas, tem-se que F > 0 e, portanto, o

ganho linear não é possível. A Figura 8 apresenta esse comportamento. Portanto, um desafio para a

programação paralela consiste em diminuir o valor de F o máximo possível.

Segundo Tanenbaum (1999), a lei de Amdahl não é o único motivo responsável pela quase

impossibilidade de se obter o ganho perfeito. As latências na comunicação, as bandas passantes

finitas e as ineficiências algorítmicas também são fatores que cooperam com a ocorrência dessa

32

impossibilidade. No caso específico dos algoritmos, convém observar que, muitas vezes, o melhor

algoritmo não pode ser facilmente paralelizado e, portanto, um algoritmo subótimo será usado para

a execução em paralelo.

Figura 8. Ganho perfeito em relação ao ganho real de desempenho de sistema paralelos

Fonte: adaptado de Amdahl (1967)

2.4 Modelos de programação paralela

Considerando as dificuldades em realizar a paralelização automática, deve-se considerar a

escolha por um modelo de programação apropriado que contemple positivamente a execução de

tarefas em paralelo. Um modelo de programação que se propõe a executar tarefas em paralelo, é

geralmente avaliado pela sua capacidade de expressão e simplicidade, visando aumentar a

produtividade de programação.

Um modelo de programação paralela consiste em uma arquitetura computacional que

forneça recursos capazes de executar código em paralelo e um software projetado para expressar o

paralelismo. Esse software pode ser um compilador, bibliotecas ou outro recurso que habilita a

aplicação a usar hardware paralelo. Modelos paralelos são implementados de várias formas: como

bibliotecas invocadas por linguagens seqüenciais, como extensão de linguagens que forneçam

parcialmente algum recurso passível de paralelização ou como uma linguagem completamente nova

capaz de efetuar todo tipo de processamento paralelo. Esses modelos são válidos tanto para os

sistemas de memória compartilhada como os de memória distribuída.

33

Tanenbaum (2001) classifica a produção de software para máquinas paralelas em quatro

metodologias: (i) acrescentando bibliotecas especiais para o tratamento de números às linguagens

de programação seqüencial; (ii) incorporando ao ambiente de execução bibliotecas especiais que

contêm primitivas de comunicação e controle; (iii) acrescentando à linguagem de programação

estruturas especiais capazes de efetuar algum tipo de processamento paralelo como, por exemplo, a

possibilidade de criar com facilidade novos processos paralelos ou executar as iterações de um loop

em paralelo e (iv) por meio do desenvolvimento de linguagens de programação novas, voltadas para

a programação paralela. Sobre essas metodologias é que se deve embasar a escolha por um modelo

de programação apropriado.

2.4.1 Estrutura básica de software paralelo

Todas as quatro metodologias apresentadas na Seção 0 possuem vantagens e desvantagens.

A escolha deve ser baseada em uma análise criteriosa sobre cinco aspectos que formam a estrutura

básica dos softwares para computadores paralelos (TANENBAUM, 2001): (i) modelos de controle;

(ii) granularidade do paralelismo; (iii) paradigmas computacionais; (iv) métodos de comunicação e

(v) primitivas de sincronização.

Modelos de controle

Há dois modelos básicos de controle que se referem ao suporte a uma ou várias linhas de

controle, também conhecidas como threads. Para o primeiro caso, o modelo com suporte a apenas

uma linha de controle, há um único programa e um único PC, porém há vários conjuntos de dados.

Na medida em que cada instrução é emitida, ela é executada simultaneamente sobre todos os

conjuntos de dados, por diferentes elementos de processamento. Para o segundo caso, ou seja, o

modelo com suporte para várias threads, cada uma delas tem seu próprio PC, seus registradores e

suas variáveis locais. Cada thread executa seu próprio programa, com seus próprios dados,

possivelmente se comunicando e sincronizando com outras threads em determinados momentos. As

variações em torno dessa idéia básica formam o modelo dominante para o processamento paralelo

(TANENBAUM, 2001).

34

Threads

Para que seja possível paralelizar o problema, é necessário que de alguma forma seja

possível explicitar o paralelismo. Há estruturas denominadas threads que possibilitam a execução

em paralelo de vários fluxos de controle. Threads são úteis em sistemas com múltiplos

processadores, para os quais o paralelismo real é possível (TANNENBAUM, 2003). As threads

podem ser de usuário ou de kernel (também conhecidas como threads nativas). O mapeamento das

threads de usuário para as threads de kernel depende diretamente da implementação de cada

sistema operacional. Dependendo do modelo adotado, cada thread criado pelo desenvolvedor será

diretamente mapeada para uma thread de kernel. Esse modelo é conhecido como “um-para-um” e é

usado por alguns sistemas operacionais como o Windows NT. Esses sistemas operacionais

executam threads de acordo com o número de núcleos disponíveis no sistema baseado na

arquitetura multicore.

O paralelismo conseguido com as threads depende diretamente do sistema operacional e do

modelo que o mesmo adota. O controle pelo escalonamento dos processadores fica a cargo do

sistema operacional e do modelo que o mesmo adota (ou seja, não é possível controlar as decisões

do agendador). Mesmo que algumas funções como “SetThreadIdealProcessor( )” possibilitem

especificar determinado processador para a execução de uma thread, isso não garante que esse

processador em questão seja escolhido no processo de escalonamento.

No caso dos processadores multicore, pelo fato dos mesmos serem homogêneos e que

qualquer núcleo disponível pode ser utilizado para executar um processo (SILBERSCHATZ, 2004),

é possível utilizar uma fila de processos separada para cada processador. No entanto, nesse esquema

um processador pode ficar ocioso (devido a sua fila de processos prontos estar vazia) enquanto que

no mesmo instante outro (núcleo) esteja sobrecarregado (com sua fila de processos prontos cheia).

Para evitar essa situação, utiliza-se uma fila de processos comum aos processadores sendo esses,

por sua vez, agendados para qualquer processador disponível. Porém, essa abordagem também

possui problemas sendo o mais evidente aqueles relacionados ao compartilhamento dos dados, pois

é possível que os processadores do sistema acessem e atualizem uma estrutura de dados comum

(SILBERSCHATZ, 2004) em determinado segmento de código conhecido como seção crítica e que

possam causar a inconsistência de dados. Para tanto, é necessário que sejam adotados métodos que

coordenem e sincronizem o acesso à seção crítica. Monitores são, dentre as soluções possíveis, uma

estrutura de sincronização de alto nível. Essa estrutura consiste em (i) declarações de variáveis

cujos valores definem o estado de uma instância desse tipo e (ii) corpos de procedimentos ou

funções que implementam operações desse tipo (SILBERSCHATZ, 2004). A estrutura do tipo

35

monitor garante que somente um processo de cada vez pode estar ativo dentro do monitor.

Algumas linguagens de programação (como Java) suportam diretamente o conceito de monitores.

Granularidade do paralelismo

O paralelismo pode ocorrer em vários níveis: do nível mais baixo, como as instruções de

máquinas, até os níveis mais altos de abstração, pelo uso de vários processos independentes na

solução de um único problema. Há vários níveis contidos dentro do intervalo entre os citados níveis

(instruções de máquina e os de vários processos). Entre esses, há aqueles que se utilizam de

processos leves como as threads. Nesse caso, o paralelismo é evidenciado quando as threads

executam de maneira independente uma das outras, possivelmente em processadores diferentes. Em

alguns sistemas, o sistema operacional controla todas as threads e é o principal responsável pelo seu

escalonamento. Em outros, cada processo de usuário é responsável por escalonar e gerenciar suas

threads, sem que o sistema operacional sequer saiba de sua existência. O paralelismo no nível de

threads é amplamente utilizado em algumas linguagens de programação como Java, que permite o

uso explícito desses recursos.

Paradigmas computacionais

Os paradigmas computacionais responsabilizam-se pela estrutura de trabalho da maioria dos

programas paralelos, em especial aqueles que suportam as threads e processos independentes. O

SPMD (Single Program Multiple Data – Único Programa Múltiplos Dados), por exemplo, executa

o mesmo programa em conjuntos de dados diferentes. Isso ocorre porque há apenas uma linha de

controle e várias unidades de execução. Há ainda outros paradigmas como, por exemplo, o pipeline.

Método de comunicação

Processos executados em paralelo precisam se comunicar. Essa comunicação pode ser feita

por variáveis compartilhadas ou por troca explícita de mensagens. Na primeira técnica, todos os

processos têm acesso a uma memória comum e se comunicam lendo e escrevendo nessa memória.

Em um sistema multiprocessador, as variáveis podem ser compartilhadas entre vários processos

mapeando a mesma página no espaço de endereçamento de cada um dos processos. Na segunda

técnica, os processos usam na comunicação primitivas como send e receive. Um processo executa

uma primitiva send, identificando outro processo como destino. Quando esse último processo

executar uma primitiva receive, a mensagem é copiada em seu espaço de endereçamento

36

(TANENBAUM, 2001). Essa técnica é comumente utilizada em multicomputadores ao passo que, a

primeira, é utilizada em sistemas multiprocessados (multicore). Multicomputadores também podem

fazer uso de variáveis compartilhadas, mas essa discussão está fora do escopo do TCC.

Primitivas de sincronização

A comunicação entre processos paralelos geralmente exige a sincronização de suas ações.

Por exemplo, quando há o compartilhamento de variáveis logicamente é necessário que haja a

garantia que, enquanto um processo estiver escrevendo na estrutura de dados compartilhada,

nenhum outro poderá ter acesso a essa estrutura. Para evitar que mais de um processo utilize os

mesmos dados compartilhados ao mesmo tempo é necessário que haja uma forma de sincronizar o

acesso a esses dados. Assim, a sincronização consiste em permitir a requisição ao uso exclusivo de

algum recurso compartilhado pela exclusão mútua entre processos. A exclusão mútua pode ser

garantida por meio de várias primitivas de comunicação como semáforos, travamentos, mutexes e

regiões críticas (TANENBAUM, 2001). Está fora do escopo deste TCC discutir os conceitos dessas

primitivas de comunicação.

As metodologias apresentadas fornecem uma base para iniciar a definição de um modelo

que contemple o desenvolvimento de software paralelo. Considerando exclusivamente a arquitetura

dos sistemas multicore e o contexto deste trabalho, define-se:

• O modelo de controle adotado será aquele com suporte a várias linhas de controle, ou

seja, com suporte a várias threads. Esses sistemas são conhecidos como multithread.

Essa escolha se baseia no fato de que as variações em torno dessa idéia formam o

modelo dominante para o processamento paralelo;

• A granularidade do paralelismo também contemplará as threads, haja vista que os

processadores multicore oferecem suporte ao paralelismo nos níveis mais baixos, como

o pipeline;

• O paradigma computacional adotado está fortemente acoplado à decisão dos itens

anteriores. Assim, o paradigma computacional adotado será aquele relacionado ao

suporte às threads e processos independentes;

• Os processadores multicore possuem memória compartilhada. Assim, o método de

comunicação adotado será aquele onde as variáveis podem ser compartilhadas entre

37

vários processos mapeando a mesma página no espaço de endereçamento de cada um

dos processos; e

• A sincronização para sistemas de memória compartilhada consiste em permitir a

requisição ao uso exclusivo de algum recurso compartilhado por meio da exclusão

mútua entre processos. A exclusão mútua pode ser garantida com várias primitivas de

comunicação.

2.4.2 Linguagens para programação paralela

Uma série de linguagens foi projetada para suportar o paralelismo, como a PL/I, ADA 95 e

Java (SEBESTA, 2000). As linguagens de programação estão vinculadas diretamente às abordagens

apresentadas na Subseção 0consistindo, portanto, em linguagens com paralelismo implícito e

linguagens com paralelismo explícito. LabVIEW, HPF, ZPL e SISAL são exemplos de algumas

implementações de linguagens com paralelismo implícito. Java, ADA e PVM são exemplos de

linguagens de programação com paralelismo explícito. Cada abordagem possui vantagens e

desvantagens. No entanto, um fator comum a essas abordagens e importante para o suporte à

concorrência é a sincronização. A sincronização visa garantir que apenas um processo poderá

acessar certa variável compartilhada em qualquer instante dado (GRUNE et al., 2001). Nos modelos

de memória compartilhada, as principais primitivas de sincronização são os semáforos e monitores.

Java e C# são linguagens baseadas no modelo de memória compartilhada com o bloqueio sendo

fornecido por monitores.

Além do suporte da sincronização, a concorrência também se caracteriza pelo modelo de

controle implementado pelas linguagens de programação. Conforme apresentado na Subseção 0o

modelo de controle dominante é aquele formado pelo suporte a várias linhas de controle, também

conhecidas como threads. A maior parte dos programas baseados em threads é feita em uma

linguagem convencional, como C, que foi estendido para ter uma biblioteca de threads. O pacote C

Threads e o padrão POSIX Threads, conhecido como pthreads, são exemplos disso. Há linguagens

que oferecem suporte direto para threads, como o Java e ADA 95 (COLOURIS, 2007).

38

Java

Java suporta o conceito de sincronização por meio da palavra-chave synchronized

garantindo que qualquer thread executando um método com aquela palavra-chave em sua assinatura

impeça outra thread de executar aquele método naquele instante.

Esse é um dos principais motivos pelo qual foi escolhido Java para a implementação do

estudo de caso. O objetivo é justificar a adoção do Java para a implementação apresentando

superficialmente o conceito de monitor devido à necessidade em utilizar esse conceito nas frações

paralelizáveis do estudo de caso. A vantagem de Java é que esta disponibiliza para o programador

as primitivas de simultaneidade além do suporte direto para as threads. O conceito de monitores foi

especialmente implementado em Java para atender também a uma necessidade da própria

linguagem de programação. Como Java roda numa máquina virtual, os programas em Java devem

compartilhar a memória destinada ao processo da máquina virtual, e assim o uso de monitores na

implementação da própria linguagem tornou-se uma facilidade que foi estendida também para a

programação.

Threads Java somente executam em sistemas que suportem diretamente o conceito de

threads e podem ser escalonadas para qualquer processador disponível. Entretanto, fica a cargo do

sistema operacional distribuir as threads pelos dos processadores.

A JVM (Java Virtual Machine) HotSpot faz o mapeamento 1:1 de todas as threads Java para

as threads nativas do sistema operacional. Esse mecanismo provê a verdadeira concorrência entre as

threads, no entanto permite erros como deadlocks em objetos sincronizados. No caso específico dos

sistemas com processadores multicore, o sistema operacional mapeia threads para diferentes

núcleos, dependendo de sua carga e disponibilidade.

2.5 Modelagem de software paralelo

Um modelo de desenvolvimento de software paralelo possui cinco aspectos (vide Subseção

0) que formam a estrutura básica dos softwares para computadores paralelos. Sinteticamente, esses

aspectos consistem na identificação de processos paralelos e na comunicação e sincronização entre

esses processos. Por conseqüência, a modelagem de software paralelo deve contemplar esses

aspectos.

Um fator sobre a modelagem e projeto de software paralelo é a possibilidade de representar

tais sistemas com formalismos existentes. Dois exemplos são a UML (Unified Modeling Language

39

– Linguagem de Modelagem Unificada) e as Redes de Petri. Com ambos, é possível representar

processos paralelos e abranger, principalmente, seus mecanismos de sincronização.

2.5.1 UML

Identificação de processos paralelos

Um modelo de controle de um determinado software paralelo pode fornecer suporte para

várias threads, onde cada thread executa seu próprio programa, com seus próprios dados,

possivelmente se comunicando e sincronizando com outras threads em determinados momentos.

Cada thread dessa é um fluxo de controle independente, capaz de iniciar uma atividade de controle.

Na UML, cada fluxo de controle independente é modelado como um objeto ativo. Portanto, um

objeto ativo é uma thread capaz de iniciar uma atividade de controle (BOOCH, RUMBAUGH &

JACOBSON, 2005). Segundo Eriksson et al. (2004), objetos ativos podem executar em paralelo

com outros objetos ativos. Um objeto ativo é uma instância de uma classe ativa sendo, portanto,

passível de representação (modelagem). Uma classe ativa representa uma unidade que executa

concorrentemente sendo implementada, geralmente, como um processo ou thread (ERIKSSON et

al., 2004). A UML fornece uma representação gráfica de uma classe ativa (vide Figura 9. Classe

ativa) constituindo-se de um retângulo com uma linha vertical extra e, como qualquer outra classe,

um compartimento para nome, atributos e operações de classe. A exceção fica por conta dos sinais,

que são enumerados em um compartimento extra. As classes ativas podem participar de

relacionamentos de dependência, generalização e associação além de poderem fazer uso de

qualquer mecanismo de extensibilidade da UML, como estereótipos, valores atribuídos e restrições.

Classes ativas podem participar em colaborações, sendo que a modelagem dessas classes é feita

com diagramas de interação (incluindo diagramas de colaboração e de seqüência). Em contraste aos

objetos ativos, existem os objetos passivos. Um objeto passivo é uma instância de uma classe

passiva. As instâncias dessa classe iniciam sua atividade somente quando outro objeto executa

alguma operação sobre as mesmas, como por exemplo, por meio do envio de uma mensagem

(ERIKSSON et al., 2004).

Classes ativas

O processo de modelagem de classes ativas geralmente consiste em identificar

oportunidades para ação paralela em um sistema. Nesse caso, eventos assíncronos, tarefas

periódicas, funções de tempo crítico e tarefas computacionais que executam em background são

40

exemplos de oportunidades passíveis de paralelização. Cada fluxo de controle é candidata a uma

classe ativa. Identificadas as classes ativas, é importante considerar uma distribuição equilibrada de

responsabilidades entre as mesmas.

Figura 9. Classe ativa

Fonte: adaptado de Booch, Rumbaugh e Jacobson (2006)

O processo de modelagem consiste ainda em examinar as demais classes ativas e passivas

com as quais cada uma colabora estaticamente. As decisões estáticas devem ser capturadas em

diagramas de classes, destacando cada classe ativa explicitamente. Com relação ao aspecto

dinâmico, deve-se assegurar que cada classe ativa (como qualquer outras classe) seja altamente

coesiva e ligeiramente acoplada em relação às classes vizinhas e que cada uma tenha o conjunto

correto de atributos, operações e sinais. Considerar como cada grupo de classes colabora com outro

dinamicamente e capturar essas decisões em diagramas de interação. Deve-se mostrar

explicitamente os objetos ativos como a raiz desses fluxos e identificar cada seqüência relacionada,

atribuindo-lhe o nome do objeto ativo.

Outro fator é generalizar conjuntos comuns de objetos ativos em uma única classe ativa

evitando assim o excesso de concorrência, visto que muitos fluxos concorrentes podem aumentar o

risco de falhas no sistema.

Comunicação entre processos

Várias estratégias podem ser utilizadas para habilitar a comunicação entre objetos ativos,

como por exemplo, por meio dos mecanismos de memória compartilhada, de envio de mensagens

ou RPC (Remote Procedure Call - Chamada Remota a Procedimento).

Objetos ativos devem estar habilitados para executar concorrentemente e enviar mensagens

para outros objetos ativos sem ter que esperar por um valor de retorno, ou seja, devem estar

habilitados para executar assincronamente (ERIKSSON et al., 2004). Segundo Booch, Rumbaugh e

Jacobson (2006), em um sistema composto por objetos passivos e ativos há quatro combinações

41

possíveis de interação: (i) uma mensagem é enviada de um objeto passivo para outro, (ii) uma

mensagem é enviada de um objeto ativo para outro, (iii) uma mensagem é enviada de um objeto

ativo para um objeto passivo e (iv) uma mensagem é enviada de um objeto passivo para um objeto

ativo. Na combinação (ii), ou seja, quando uma mensagem é enviada de um objeto ativo para outro

também ativo, há dois estilos possíveis de comunicação:

• O objeto ativo chama uma operação de outro objeto: um objeto chama a operação,

aguarda que o destinatário aceite a chamada, a operação é invocada, um objeto de

retorno é retornado ao objeto que fez a chamada, os dois objetos continuam sua

execução independentemente. Durante esse processo, os dois fluxos de controle ficam

em estado de espera; e

• Um objeto ativo assincronamente envia um sinal ou uma chamada a uma operação de

outro objeto: um objeto faz a chamada a uma operação e envia o sinal ou chama a

operação e depois continua sua execução independente. Enquanto isso, o destinatário

aceita o sinal ou chamada sempre que estiver no estado de pronto e prossegue sua

execução após concluir o que foi solicitado. Nesse caso, os objetos não estão

sincronizados.

Geralmente a comunicação entre objetos ativos é assíncrona enquanto a comunicação

interna a um objeto ativo é síncrona.

Eventos e sinais

Para Booch, Rumbaugh e Jacobson (2006), um evento é a especificação de uma ocorrência

significativa que possui uma localização no tempo e espaço. Para Eriksson et al.(2004), um evento é

algo que ocorre no sistema e que pode fornecer um gatilho (trigger) capaz de causar a execução de

determinado comportamento.

Na UML, é possível modelar quatro tipos de eventos: sinais, chamadas, contagem de tempo

e alteração no estado (BOOCH, RUMBAUGH & JACOBSON, 2006).

Um sinal é um tipo de evento que representa a especificação de um estímulo assíncrono

comunicado entre instâncias (BOOCH, RUMBAUGH & JACOBSON, 2006). É um classificador

de mensagens, ou seja, é um tipo de mensagem. Os sinais podem ter atributos e operações para

42

carregar informação e comportamento (ERIKSSON et al., 2004). Na UML, é possível modelar

sinais como classes estereotipadas.

Sincronização de processos

Sincronização é o processo de coordenar threads concorrentes para que interajam entre si de

forma eficiente (ERIKSSON et al., 2004). A coerência da cache surge como o principal exemplo

onde pode haver inconsistência de dados quando dois ou mais processadores tentam gravar valores

em uma variável compartilhada ao mesmo tempo. Esse problema é nato nas atividades que

envolvam concorrência. Qualquer descuido nesse contexto pode causar a inconsistência dos dados e

por conseqüência, provocar falha de difícil identificação. Segundo Booch, Rumbaugh e Jacobson

(2006) esse é o clássico problema da exclusão mútua. Em sistemas orientados a objetos é possível

solucionar esse problema tratando um objeto como uma região crítica que é uma primitiva de

sincronização. Existem três alternativas (mecanismos de sincronização) para essa abordagem:

• seqüencial: somente um fluxo de controle deve existir sobre o objeto por vez. Na

presença de vários fluxos de controle, a semântica e a integridade do objeto não pode ser

garantida;

• protegida: há vários fluxos de controle. A semântica e a integridade são garantidas pela

seqüencialização de todas as chamadas para todas as operações protegidas do objeto.

Dadas essas características, pode ocorrer deadlock; e

• concorrente: há vários fluxos de controle. A semântica e a integridade do objeto são

garantidas porque os fluxos de controle acessam conjuntos de dados disjuntos ou

somente dados de leitura.

Na UML, é possível anexar essas propriedades a uma operação, utilizando-se a notação de

restrição. Declarar a concorrência para uma operação significa que várias chamadas dessa operação

podem ser executadas ao mesmo tempo sem perigo. Declarar a concorrência para um objeto

significa que as chamadas de diferentes operações podem ser executadas concorrentemente sem

perigo (BOOCH, RUMBAUGH & JACOBSON, 2006). A correta modelagem de sistemas paralelos

pode evitar que os problemas inerentes a sincronização ocorram, como os acessos incorretos aos

recursos compartilhados, uso ineficiente de recursos, deadlocks e inversão de prioridade.

43

Na UML, o suporte para sincronização pode ser conseguido usando estereótipos ou

propriedades. Por exemplo, se a sincronização deve ser indicada de forma explícita, uma classe do

tipo semáforo pode ser definida e instanciada sempre que a sincronização entre objetos ativos deva

ser mostrada. Uma outra possibilidade é definir um estereótipo <<semáforo>> que pode ser usado

por todas as classes que deveriam ser protegidas pelo semáforo (ERIKSSON et al., 2004)

A sincronização deve receber a devida atenção não apenas entre objetos ativos, mas entre

objetos ativos e os objetos passivos com os quais colaboram. Os mecanismos de sincronização

(semântica de operação seqüencial, protegida ou concorrente) devem ser aplicados, conforme seja

apropriado e as propriedades de sincronização de operações devem ser mostradas explicitamente.

A UML fornece graficamente recursos como os diagramas de classes e diagramas de

interação capazes de auxiliar a visualização da forma como esses fluxos interagem uns com os

outros.

2.5.2 Redes de Petri

Redes de Petri é uma técnica de especificação de sistemas que possibilita uma representação

matemática e possui mecanismos de análise poderosos, que permitem a verificação da corretude do

sistema especificado. Apesar de não ser a linguagem adotada para o contexto do presente TCC, é

válido fazer algumas considerações a seu respeito.

Com Redes de Petri é possível modelar sistemas paralelos, concorrentes, assíncronos e não-

determinísticos. As características inerentes às Redes de Petri facilitam a modelagem de sistemas

que possuam concorrência e sincronização.

Para a modelagem, são válidas as mesmas prerrogativas apresentadas na Subseção 0, ou

seja, devem-se identificar oportunidades para ação concorrente em um sistema como eventos

assíncronos, tarefas periódicas, funções de tempo crítico e tarefas computacionais que executam em

background. Após a identificação de processos paralelos, os problemas de comunicação e

sincronização imediatamente aparecem (ERIKSSON et al., 2004) sendo possível identificá-los e

modelá-los. A representação de processos paralelos em Redes de Petri é ilustrado na Figura 10.

Processos paralelos em Redes de Petri: (a) paralelização; (b) sincronização Também é possível

modelar a comunicação e a sincronização com as Redes de Petri. A comunicação consiste em uma

simples passagem de transição enquanto que o sincronismo é representado por uma rede elementar.

44

Figura 10. Processos paralelos em Redes de Petri: (a) paralelização; (b) sincronização

A identificação de processos e dos mecanismos de comunicação e sincronização é

imperativa para o modelo de desenvolvimento de software paralelo em si, independente de

linguagens de programação e modelagem. No entanto, a abordagem sobre esses aspectos pode

variar se considerar o paradigma no qual está sendo modelado. A principal diferença entre UML e

Redes de Petri é que a primeira é comumente (mas não exclusivamente) utilizada em sistemas

orientados a objetos e a segunda em sistemas orientados a estados e ações.

45

3. MODELAGEM DO ESTUDO DE CASO

3.1 Estudo de caso

O presente TCC contempla o problema do caixeiro-viajante como estudo de caso. Esse

problema é um caso típico de otimização combinatória, freqüentemente utilizado em computação

para demonstrar problemas de difícil resolução e que pode ser representado por meio de um grafo

cujo objetivo é encontrar o ciclo hamiltoniano de menor extensão. O problema supõe que um

caixeiro-viajante deseja visitar N cidades (vértices) de certa localização e que, entre alguns pares de

cidades existem rotas (arcos ou arestas), através das quais ele pode viajar a partir de uma cidade

para outra. Cada rota tem um número associado, que pode representar a distância ou o custo

necessário para percorrê-la. Assim, o caixeiro viajante deseja encontrar um caminho que passe por

cada uma das N cidades apenas uma vez, e além disso que tenha custo menor que certo valor onde o

custo do caminho é a soma dos custos das rotas percorridas.

Uma forma de solucionar esse problema é explorar todas as permutações possíveis dos

vértices do grafo. Infelizmente esta abordagem não é prática porque não opera em tempo polinomial

pois para um conjunto de vértices V, existem V! permutações possíveis, requerendo um tempo de

O(V!), onde V! é o fatorial de V, que é o produto de todos os números de V até 1. Em geral,

algoritmos de tempo não-polinomial são evitados, porque, mesmo em pequenas operações, os

problemas rapidamente tornam-se impossíveis de serem rastreados (LOUDON, 2000).

Normalmente, o problema do caixeiro-viajante é resolvido utilizando um algoritmo de aproximação

(por exemplo, algoritmos genéticos) visando encontrar o ciclo hamiltoniano de menor extensão. No

entanto, o principal intuito do presente TCC não é necessariamente o de encontrar o caminho de

menor custo. O objetivo é utilizar sua complexidade computacional (por meio de permutações das

rotas possíveis) para avaliar a complexidade em desenvolver um software com características

paralelas baseado em ambientes multicore capazes de otimizar o desempenho computacional.

Segundo Silveira (2000), a importância do problema do caixeiro-viajante resume-se em duas

capacidades:

1. de modo simples e concreto, exemplifica a enorme velocidade de crescimento da fatorial;

2. é uma espécie de "metro" com o qual medimos a complexidade computacional dos

problemas combinatórios que ocorrem em engenharia e no trabalho científico.

46

3.1.1 Modelagem (Paradigma seqüencial)

Descrição do problema

O problema supõe que um caixeiro viajante deseja visitar N cidades (vértices) de certa

localização e que, entre alguns pares de cidades existem rotas (arcos ou arestas), através das quais

ele pode viajar a partir de uma cidade para outra passando por cada uma das N cidades apenas uma

vez .

Restrições:

1. O problema deve ser representado por meio de um grafo.

2. As questões relativas ao cálculo da aresta (rota) de menor custo não devem ser consideras.

3. Deve-se explorar (baseado nos critérios de caminhamento do algoritmo do caixeiro viajante)

todas as permutações (rotas) possíveis dos vértices do grafo.

Requisitos Funcionais 1. O sistema deve traçar todas as combinações de rotas existentes entre as coordenadas

selecionadas pelo usuário

2. O sistema deve apresentar graficamente cada rota processada

3. O sistema deve indicar o andamento do processamento

Requisitos Não-Funcionais 1. O sistema deve permitir que o processamento seja encerrado a qualquer momento pelo usuário

2. O processamento das rotas deve ocorrer em paralelo à sua apresentação gráfica

3. O processamento das rotas deve ocorrer em paralelo ao indicador de processamento

Requisitos de Interface 1. Deve existir um botão que possibilite o término do processamento pelo usuário

2. Deve existir uma barra de progresso que indique o status do processamento

Casos de uso

Nome : traça rotas

Atores : usuário

47

Descrição : usuário solicita ao sistema que gere todas as rotas existentes entre todas as

coordenadas por ele (usuário) selecionadas apresentando graficamente cada rota e um indicador de

processamento.

Fluxo principal

1. O usuário seleciona o número de coordenadas

2. O sistema inicia o processamento de todas as rotas possíveis entre as coordenadas

2.1. O sistema apresenta (traça) um gráfico para cada rota processada

2.2. O sistema atualiza o indicador de processamento a partir de cada rota processada

3. O sistema encerra o processamento

Fluxo de exceção

1. Se no passo 2 do fluxo principal o usuário decidir por encerrar o processamento do sistema,

esse (sistema) deve encerrar a apresentação do gráfico imediatamente e liberar os recursos

utilizados.

Diagrama de caso de uso

Diagrama de colaboração

48

Restrição [1]

Diagrama de classes

49

Diagrama de seqüência

3.1.2 Modelagem (Paradigma paralelo)

A engenharia de software ocupa todos os aspectos da produção de software, desde os

estágios iniciais de especificação do sistema até a manutenção do mesmo (SOMMERVILLE, 2003).

Considerando as métricas e os objetivos a que se propõe, a avaliação do estudo de caso enfatizará os

aspectos relacionados à fase de projeto de software.

O projeto de software é formado por alguns estágios, sendo os mais evidentes o projeto de

arquitetura, especificação abstrata, projeto de interface, projeto de componentes, projeto de

estrutura de dados e projeto de algoritmos. Ao considerar o estudo de caso do presente TCC,

percebeu-se que, para os aspectos paralelos, os estágios de maior relevância (no sentido que se

diferenciam com maior evidência em relação aos aspectos seqüenciais) são o projeto de estrutura de

dados e projeto de algoritmos. No entanto, apesar das citadas diferenças serem mais evidentes nos

estágios de projeto de estrutura de dados e projeto de algoritmos, o projeto de arquitetura de sistema

é vital para o projeto de qualquer software, principalmente porque requisitos não funcionais como

desempenho (relevantes em sistemas paralelos) podem ser afetados nesse estágio. Sobre a

arquitetura de sistema, considera-se o modelo estrutural, onde há uma preocupação em como um

sistema é decomposto em subsistemas, e o modelo de controle, complementar ao modelo estrutural

e que diz respeito ao controle de fluxo entre os sistemas. Apesar de não ter sido observada a

necessidade de aplicar de forma abrangente esses modelos no estudo de caso do presente TCC, é

válido comentar sua relevância para o desenvolvimento de programas paralelos, uma vez que uma

das abordagens para o controle de fluxo entre os subsistemas é baseada em eventos, onde podem ser

50

aplicados em sistemas de tempo real e, portanto, serem relevantes no projeto de aspectos paralelos.

É possível no projeto de arquitetura decompor os subsistemas (abordados no modelo estrutural) em

componentes menores denominados módulos. O modelo orientado a objetos pode ser utilizado na

decomposição de um subsistema em módulos. Sommerville (2003) cita que o projeto de software

pode ser abordado de forma metódica por meio dos métodos estruturados, que são conjuntos de

notações e diretrizes para o projeto de software. Entre esses métodos, encontra-se o método

orientado a objetos (adotado neste TCC), que inclui um modelo de herança do sistema, modelos dos

relacionamentos estáticos e dinâmicos entre objetos e um modelo de como os objetos interagem

entre si quando o sistema está em execução. Esse método pode ser aplicado ao desenvolvimento de

programas paralelos.

Para a modelagem no paradigma paralelo, é necessário que alguns fatores sejam

considerados. A implementação do estudo de caso do presente trabalho cria n threads de forma a

processar o cálculo das rotas do grafo em paralelo. O valor de n é proporcional ao número de

processadores (núcleos) disponíveis no sistema. O trecho de código apresentado na Figura 12 ilustra

essa operação:

Figura 11: código Java para processos paralelos

São criadas então n threads para n núcleos (disponibilizado pelo método

“availableProcessors( )”). Cada thread é representada por uma classe denominada Task (vide

Figura 13).

Figura 12. classe Task

51

A seguir, é apresentada uma discussão sobre a forma que as threads devem “caminhar”

sobre o grafo a fim de processar em paralelo as rotas.

Paralelização das rotas

Para “caminhar” sobre as arestas do grafo é necessário saber se determinada coordenada foi

visitada ou não. Assim, compartilhar a mesma estrutura (ou seja, o mesmo grafo) entre várias

threads pode ocasionar um comportamento indesejado. Mesmo se utilizar algum mecanismo de

sincronização como monitores, por exemplo, ainda assim pode ocorrer de uma coordenada já ter

sido visitada por uma thread_1 e não ser visitada, portanto, pela thread_2, justamente porque essa

consultará o grafo a fim de saber se foi visitada e o grafo responder positivamente. Assim, a

thread_2 não visitará aquela coordenada. Nesse caso, deveria haver algum mecanismo que

verificasse se determinada coordenada já foi visitada pela thread ativa. No entanto, isso pode

envolver uma complexidade algorítmica maior e comprometer a eficiência do algoritmo adotado

(DFS).

Outra possibilidade é duplicar o grafo e distribuir essas cópias entre duas threads (o número

de cópias também poderia ser maior se for considerado um número maior de threads). No entanto,

isso não traria ganhos substanciais podendo ocorrer justamente o efeito contrário (e indesejado):

duas threads realizando o mesmo trabalho por unidade de tempo com a possibilidade de competir

por recursos. A solução a ser adotada e que pode trazer ganhos de desempenho é duplicar o grafo e

dividir pelo número de threads as rotas a serem processadas. Exemplo: considerando um grafo

hipotético com 5 coordenadas, percebe-se que o número total de rotas a ser processadas é (n-1)!,

resultando em um total de 24 rotas. Uma thread_1 será a responsável por processar as 12 primeiras

rotas deixando as demais a cargo da thread_2. Deveria, no entanto, que garantir que uma thread não

processasse as mesmas rotas da thread concorrente, pois do contrário, causar-se-ia o mesmo efeito

apresentado no parágrafo anterior. Assim, há a necessidade de implementar algum mecanismo que

indique às threads as arestas iniciais a serem percorridas. Considerando o exemplo anterior, as

arestas iniciais que podem ser formadas a partir da origem são: (A,B), (A,C), (A,D), (A,E). A

thread_1 processaria as rotas a partir das arestas (A,B) e (A,C) enquanto que a thread_2 processaria

as rotas a partir das arestas (A,D) e (A,E). Assim, o número de rotas que cada thread executaria

seria igual a (n – 1) !/ 2 resultando em um total de 12 rotas para cada thread. Se considerar que

cada núcleo do processador multicore executará, exclusivamente e sem interrupções, uma, e

somente uma, thread, poder-se-ia afirmar que haveria um ganho de 50% no tempo de execução. Por

exemplo: se para o cálculo de 24 rotas determinada thread necessita de 100 unidades de tempo,

52

pode-se concluir que para a metade do trabalho, ou seja, para 12 rotas o tempo necessário seria

justamente a metade, necessitando apenas de 50 unidades de tempo. Como haveria duas threads

executando em paralelo, em 50 unidades de tempo é possível processar as 24 rotas.

Esse cálculo é hipotético e pouco real, uma vez que dificilmente haverá exclusividade de

núcleo para cada thread (mesmo configurando cada thread com prioridade máxima). Do contrário,

seria necessário configurar cada thread com prioridade máxima evitando então que o sistema

responda a qualquer evento do usuário (por exemplo, o usuário não conseguiria parar a execução do

programa, pois os núcleos do processador multicore estariam exclusivamente ocupados com o

processamento além de ocorrer o efeito do “congelamento” da interface gráfica). Esse cenário não é

desejável para este estudo de caso, cujo objetivo é tornar a interface gráfica disponível para

qualquer ação do usuário. Mesmo que fosse alocado àquela thread de prioridade máxima

determinado processador, ainda assim não há garantias. Isso ocorre porque o sistema operacional

obedece ao seu algoritmo de escalonamento. As prioridades apenas “influenciam”, mas não há

garantias. Por último, deve ser considerada a impossibilidade de se chegar ao ganho perfeito, ou

seja 1:1, em razão do componente seqüencial contemplada pela lei de Amdahl.

Identificação de processos paralelos

Barney (2007) cita que o projeto de software paralelo consiste, primeiramente, em

compreender o problema e identificar as frações paralelizáveis do software. A identificação consiste

em observar o grau de dependência entre as variáveis envolvidas em um determinado trecho do

software. Como exemplo, considera-se o cálculo da série de Fibonacci através da fórmula F(k + 2)

= F(k + 1) + F(k). Esses três termos não podem ser calculados independentemente e, portanto, não

são passíveis de paralelização.

No caso específico do estudo de caso, o cálculo das rotas pode ocorrer de forma paralela

desde que observados alguns parâmetros. Entre tais, está a definição da origem do sistema com o

respectivo cálculo em paralelo das rotas a partir de cada nodo filho daquela origem (considerando

aqui a estruturação do sistema em um grafo). Por exemplo: considerando o problema do Caixeiro-

Viajante, supõe-se que há quatro cidades a serem visitadas (por exemplo, cidades “A”, “B”, “C” e

“D”). Essas rotas podem ser representadas em um grafo, conforme mostrado na Figura 11 (por

motivos de simplicidade, na figura não está mostrada a aresta que liga o último nodo (ou cidade)

visitado à origem. No entanto, para fins de cálculo do problema do caixeiro viajante, tal aresta deve

ser considerada). A oportunidade de paralelização consiste em calcular as rotas a partir da origem

como se fossem threads independentes, representados na Figura 11 como T1, T2 e T3. É possível

53

ainda paralelizar essas mesmas threads em linhas de execução menores como, por exemplo, a

paralelização entre o caminho “B, C, D” e “B, D, C”. Porém, dado os objetivos do presente TCC,

esse cenário não será considerado.

Em um programa, pode haver várias oportunidades de paralelização. Barney (2007) cita que

os esforços em paralelizar determinada atividade deve ser focada naquelas onde há uma carga de

trabalho considerável, cujos esforços em paralelizar se justifiquem com ganhos significativos de

desempenho. Cita ainda que ferramentas de análise de desempenho são úteis nesse momento. As

atividades passíveis de paralelização, mas que fazem pouco uso do processador podem ser

ignoradas. Este estudo de caso possui como atividade de maior relevância (no que diz respeito ao

uso do processador) o processamento das rotas. A Figura 11 apresenta um modelo de grafo utilizado

para o referido processamento.

Sendo assim, tal modelo é passível de paralelização, pois possui oportunidades

paralelizáveis e justifica-se pelo uso excessivo do processador. Outro item refere-se aos gargalos do

sistema. Pode ocorrer do programa conter algumas frações que provoquem alguma degradação no

desempenho tornando-se um gargalo no sistema. À primeira vista, pode-se considerar como sendo

essa fração uma oportunidade paralelizável. No entanto, deve-se recorrer à análise algorítmica em

um primeiro momento visando melhorar o desempenho por meio da utilização de algoritmos mais

eficientes.

Figura 13. Grafo para o problema do caixeiro-viajante

Portanto, pode-se fazer uma síntese dessa análise de programas paralelos como:

54

1) Identificar oportunidades paralelizáveis e os possíveis inibidores de paralelismo devido à

dependência de dados (exemplo, série de Fibonacci);

2) Análise de carga de trabalho; e

3) Análise algorítmica (investigação de algoritmos mais eficientes)

Pode-se mapear esses conceitos para o estudo de caso, da seguinte forma:

1) Pela análise de um grafo similar ao apresentado na Figura 11, percebe-se que o

processamento de determinada rota (A, B, C, D) independe do processamento de outra

rota qualquer (A, D, C, B). Pode-se considerar esses processamentos como

independentes entre si, sendo considerado como atividades passíveis de paralelização;

2) Ao executar o algoritmo de processamento de rotas, observa-se que há uma carga

considerável de processamento. Isso foi constatado ao observar o comportamento de

processador específico em uma máquina composta por uma arquitetura específica4

através do utilitário “Task Manager” do sistema operacional Windows XP, da Microsoft

3) O algoritmo utilizado para o processamento de rotas foi baseado no algoritmo DFS

(Depth-Fisrt Search ou Busca por Profundidade)

A análise culminou na criação de uma classe denominada “ProcessaRotas”. Essa classe é

responsável por processar todas as rotas entre as coordenadas (cuja quantidade definiu-se como

sendo entre 2 e 10, ficando a critério do usuário a escolha) do grafo. A UML dispõe de mecanismos

para modelar a classe “ProcessaRotas” que, até o momento, não difere dos modelos atuais de

modelagem de software. Exeção é a palavra “concurrent” que indica que o método por essa palavra

referenciada deve ser executada em paralelo.

A classe “ProcessaRotas” possui como atributo um grafo e um método denominado

“execute( )” (responsável por implementar e executar o algoritmo DFS ) que é executado

paralelamente por n threads disponíveis no sistema (vide Figura 12). O atributo “grafo”,

identificado no processo de análise, é composto por arestas e ambos podem ser modelados como

classes de entidade. No entanto, como essas classes não têm impacto direto no processamento

4 A arquitetura referenciada é composta por um processador Intel Core 2 Duo E6700, 2 GB de RAM e FSB de 800 MHz

55

paralelo, a ênfase será dada à classe “ProcessaRotas”. A diferença em relação ao aspecto seqüencial

fica por conta dos objetos ativos.

Figura 14. Método executado em paralelo

Descrição do problema

O problema supõe que um caixeiro viajante deseja visitar N cidades (vértices) de certa

localização e que, entre alguns pares de cidades existem rotas (arcos ou arestas), através das quais

ele pode viajar a partir de uma cidade para outra passando por cada uma das N cidades apenas uma

vez . O cálculo das rotas devem ser paralelizadas.

Restrições:

1. O problema deve ser representado por meio de um grafo.

2. As questões relativas ao cálculo da aresta (rota) de menor custo não devem ser consideras.

3. Deve-se explorar paralelamente (baseado nos critérios de caminhamento do algoritmo do

caixeiro viajante) todas as permutações (rotas) possíveis dos vértices do grafo.

Requisitos Funcionais 1. O sistema deve traçar todas as combinações de rotas existentes entre as coordenadas

selecionadas pelo usuário

2. O sistema deve processar as rotas paralelamente

3. O sistema deve apresentar graficamente cada rota processada

4. O sistema deve indicar o andamento do processamento

56

Requisitos Não-Funcionais 1. O sistema deve permitir que o processamento seja encerrado a qualquer momento pelo usuário

2. O processamento das rotas deve ocorrer em paralelo à sua apresentação gráfica

3. O processamento das rotas deve ocorrer em paralelo ao indicador de processamento

4. O processamento deve possuir melhor desempenho que o processamento seqüencial

Requisitos de Interface 1. Deve existir um botão que possibilite o término do processamento pelo usuário

2. Deve existir uma barra de progresso que indique o status do processamento

Caso de uso : traça rotas

Atores : usuário

Descrição : usuário solicita ao sistema que gere todas as rotas existentes entre todas as

coordenadas por ele (usuário) selecionadas apresentando graficamente cada rota e um indicador de

processamento.

Fluxo principal

1. O usuário seleciona o número de coordenadas

2. O sistema inicia o processamento de todas as rotas possíveis entre as coordenadas.

2.1. O sistema apresenta (traça) um gráfico para cada rota processada

2.2. O sistema atualiza o indicador de processamento a partir de cada rota processada

3. O sistema encerra o processamento

Fluxo de exceção

1. Se no passo 2 do fluxo principal o usuário decidir por encerrar o processamento do sistema,

esse (sistema) deve encerrar a apresentação do gráfico imediatamente e liberar os recursos

utilizados.

57

Diagrama de caso de uso

Diagrama de classes

58

Diagrama de seqüência

4. AVALIAÇÃO E TRABALHOS FUTUROS

A principal métrica a ser adotada neste trabalho está relacionada à complexidade. Ao medir

a complexidade do software é possível prever e compreender os fatores que provocam a baixa

produtividade (LAIRD & BRENNAN, 2006). A complexidade estrutural aborda o projeto e a

estrutura do software em si. Há várias métricas de complexidade estrutural. Neste trabalho será

adotada a métrica estrutural orientada a objetos, que mensura as diferentes estruturas de programas

orientado a objetos. Pretende-se com essas métricas mensurar classes, modularidade,

encapsulamento, herança e abstração.

As métricas em questão formam um conjunto conhecido como métricas Chidamber e

Kemerer (CK) (LAIRD & BRENNAN, 2006). Esse conjunto consiste de seis métricas que são

utilizadas para mensurar o tamanho e a complexidade de classes, o uso da herança, o acoplamento

entre as classe, a coesão de classes e a colaboração entre as classes. As métricas adotadas são:

1. WMC (Weighted Methods per Class): foca o número e a complexidade de métodos

dentro da classe. Valores altos sugere que a classe deve ser dividida (ou refatorada) em

classes menores;

59

2. DIT (Depth of Inheritance Tree): mensura o grau hirárquico de uma dada classe. Valores

altos implicam em maior complexidade de projeto;

3. NOC (Number of Children): mensura o número de sucessores imediatos (classes filha)

de uma dada classe. Valores altos indicam que a abstração das classes pai estão diluídas

e, portanto, deve ser considerado rever o projeto;

4. CBO (Coupling Between Object Classes): mensura quantas classes se relacionam com

uma dada classe. Valores altos implicam em maior complexidade, manutenibilidade e

reusabilidade reduzidas;

5. RFC (Response for Class): mensura o número total de métodos potenciais a serem

executados em uma classe quando essa recebe uma mensagem e deve, por sua vez,

responder. Valores altos indicam complexidade maior; e

6. LCOM (Lack of Cohesion on Methods): mensura o número total de métodos dentro de

uma classe que referenciam uma dada variável de instância. A complexidade e

dificuldade de projeto crescem quando essa métrica é incrementada.

A modelagem do estudo de caso baseado no paradigma seqüencial culminou na criação das

classes descritas na tabela 3. As classes identificadas na modelagem baseada no paradigma paralelo

constam na tabela 4. As métricas apresentadas anteriormente foram aplicadas sobre as classes

identificadas a fim de mensurar sua complexidade baseado nos critérios que compõe aquelas

métricas:

Tabela 4: Aplicação de métricas (paradigma seqüencial)

Classe WMC DIT NOC CBO RFC LCOM

Controladora 1 0 0 2 1 1

ProjetaGrafo 4 0 0 2 1 1

ProcessaRotas 4 0 0 2 1 2

Graph 4 0 0 4 0 3

Edge 2 0 0 2 0 0

Vertex 6 0 0 2 0 0

Total 21 0 0 14 3 7

60

Tabela 5: Aplicação de métricas (paradigma paralelo)

Classe WMC DIT NOC CBO RFC LCOM

Controladora 1 0 0 2 1 1

ProjetaGrafo 3 0 0 2 1 1

ProcessaRotas 5 0 0 3 1 2

Graph 6 0 0 5 0 6

Edge 2 0 0 2 0 0

Vertex 4 0 0 2 0 0

SharedVars 5 0 0 1 1 0

EdgeLocker 3 0 0 1 1 0

Task 4 1 0 4 2 4

Total 33 1 0 22 7 14

É importante observar que a aplicação das métricas e os resultados conseguidos são válidos

exclusivamente para o estudo de caso deste trabalho. Portanto, os resultados devem ser avaliados

sob a ótica discursiva e não conclusiva.

De posse desses índices, percebe-se que no paradigma paralelo:

• Houve aproximadamente um aumento de 33% do número de classes no sistema em relação

ao paradigma seqüencial. Esse aumento é provocado pela inserção de classes responsáveis

pelo paralelismo por meio de threads (classe Task) e de classes que implementam a lógica

de monitores (classe SharedVars) e locks (classe EdgeLocker). Pode-se observar que essas

classes implementam a estrutura básica para o paralelismo. Trabalhos futuros poderiam

realizar testes em larga escala a fim de verificar que esse aumento tende a estabilizar quando

o sistema atinge tamanho de aproximadamente 35% maior que o sistema baseado no

paradigma seqüencial. Pra tal, seria necessário adotar métricas relacionadas ao tamanho do

software.

61

• A métrica WMC avalia a complexidade em relação ao número de métodos em uma classe,

indicando que valores altos sugerem que a classe deve ser dividida (ou refatorada) em

classes menores. Verifica-se que em algumas classes a complexidade diminui enquanto que

em outras, aumentam. Considerando a média entre as classes, conclui-se que no paradigma

seqüencial há uma média de complexidade igual a 3,5 por classe. No paradigma paralelo há

uma média de complexidade de 3,6 por classe. Assim, para este estudo de caso, o acréscimo

de classes necessárias à paralelização não trouxe aumentos significativos dos índices de

complexidade de acordo com a métrica WMC

• A métrica DIT refere-se ao grau hierárquica entre as classes e a complexidade agregada a

essa hierarquia. O estudo de caso não propiciou alterações relevantes nesse item de forma

que não é possível discutir essa métrica pela ausência de dados significativos. A métrica

NOC é proporcional à DIT e, por esse motivo, também não propicia discussões.

• A métrica CBO está relacionada ao acoplamento entre as classes. Valores altos indicam alto

acoplamento. Ao observar individualmente as classes que participam de ambos os

paradigmas, percebe-se que não houve alterações significativas dos índices. O mesmo é

válido ao avaliar as médias. Em ambos os paradigmas, a média foi igual a 2,4. Ou seja, a

adição de classes necessárias ao paralelismo não aumentou significativamente o

acoplamento em geral das classes.

• A métrica RFC indica o número total de métodos potenciais a serem executados em uma

classe quando essa recebe uma mensagem e deve, por sua vez, responder. Valores altos

indicam complexidade maior. Considerando as médias, verificou que não houve aumento

significativo de complexidade do paradigma paralelo em relação ao seqüencial

• A métrica LCOM refere-se à coesão das classes e indica, em valores altos, o aumento da

dificuldade de projeto e da complexidade. Essa métrica foi a que apresentou a maior

variação entre os paradigmas não sendo, no entanto, suficiente para concluir que o

paradigma paralelo acarreta em menor coesão das classes em geral. No paradigma

seqüencial, a média foi de 1,1 e no paralelo foi de 1,5.

As métricas CK auxiliam na avaliação de alguns aspectos relacionados ao desenvolvimento de

software, como a produtividade, o esforço dispensando no processo de reutilização e projeto de

62

classes, na dificuldade em implementar classes e manutenibilidade. Ao analisar os índices

conseguidos por meio das métricas apresentadas, verifica-se exclusivamente para o estudo de

caso deste trabalho que:

• A produtividade está relacionada não somente à complexidade do software; o esforço

também deve ser considerado. Portanto, não é possível apenas com a complexidade

concluir que a produtividade é afetada quando desenvolve-se software paralelo, mesmo

não havendo diferenças significativas entre os paradigmas seqüencial e paralelo.

• As reutilização não pôde ser avaliada pois está diretamente associada à métrica DIT e

NOC, não consideradas nessa avaliação devido à pouca disponibilidade de dados

• A dificuldade em implementar e projetar classes está associada diretamente com a

métrica LCOM, que mensura principalmente a coesão das classes. Houve uma diferença

entre os paradigmas, o que justifica a maior dificuldade em projetar software paralelo

(uma vez que esse obteve índices de complexidade mais altos). No entanto, é necessário

que trabalhos futuros apliquem essa métrica em larga escala a fim de verificar uma

tendência na dificuldade em projetar software paralelo

• A manutenibilidade está relacionada principalmente com a métrica CBO, que indica o

grau de acoplamento entre as classes. Não foram identificadas diferenças significativas

entre os paradigmas seqüencial e paralelo, de forma que pode-se sugerir uma discussão a

respeito dos reais impactos na manutenibilidade do sistema que programas paralelos

possam ocasionar. No caso específico deste trabalho, verifica-se que a manutenibilidade

não é atingida quando paraleliza-se o programa.

Apesar do estudo de caso não apresentar complexidade suficiente ao ponto de criar vários

cenários de use cases, foi possível aplicar as principais técnicas de análise para o estudo de caso em

questão, acarretando na produção do caso de uso “traça rotas”. As mesmas técnicas adotadas em

sistemas seqüenciais foram aplicadas para a produção de software paralelo. As características

específicas do paralelismo devem ser abordadas principalmente no levantamento de requisitos não

funcionais, onde questões como desempenho e consistência de dados são críticos. A descrição de

cenários geralmente são em nível alto de abstração. Os aspectos de mais baixo nível (paralelismo de

dados, fluxos de execução, etc.) são abstraídos. Esses aspectos ficaram mais evidentes na fase de

projeto e na implementação. Na fase de projeto, percebeu-se que as diferenças entre os aspectos

63

seqüenciais e paralelos ficam mais evidentes quando o nível de abstração é mais baixo. Essa

sentença contextualiza-se na seguinte citação de Sommerville (2003):

“os projetistas devem evitar, se possível, tomar decisões

prematuras sobre a simultaneidade (...). É melhor

decompor o sistema em módulos e, então, decidir durante a

implementação se eles devem ser executados em seqüência

ou em paralelo”.

TRABALHOS FUTUROS

A adoção de métricas é crucial para avaliar as técnicas de engenharia de software. Essas

métricas abordam o tamanho, a complexidade e o esforço para produzir software. Verificou-se que

a avaliação deve ser abrangente a fim de generalizar alguns aspectos como a produtividade, por

exemplo. Essa abrangência pode sugerir uma tendência que indique o grau de dificuldade em

produzir software paralelo em relação ao seqüencial. A aplicação das métricas CK e a conseqüente

avaliação apresentada neste trabalho é específica para o estudo de caso do caixeiro-viajante.

Portanto, trabalhos futuros poderiam aplicar métricas de tamanho, complexidade e esforço em uma

gama maior de problemas passíveis de paralelização a fim de obter índices gerais de produtividade

de software paralelo.

64

REFERÊNCIAS BIBLIOGRÁFICAS

ADL-TABATAI, Ali-Reza; KOZYRAKIS, Christos; SAHA, Bratin. A transactional programming in a multi-core environment. 2006. Disponível em: < http://csl.stanford.edu/~christos/publications/2006.unlocking_concurrency.queue.pdf >. Acesso em: 29 out. 2007. ADL-TABATABAI, Ali-Reza; KOZYRAKIS, Christos; SAHA, Bratin. Unlocking Concurrency. 2006. Disponível em: < http://www.acmqueue.org/modules.php?name=Content&pa=showpage&pid=444&page=4 > Acesso em: 15 nov. 2007. AMDAHL, Gene. Validity of the Single Processor Approach to Achieving Large-Scale Computing Capabilities. AFIPS Conference Proceedings, 1967. BARNEY, Blaise. Introduction to Parallel Computing. 2007. Disponível em: <https://computing.llnl.gov/tutorials/parallel_comp/>. Acesso em: 16 jun. 2008. BOOCH, Grady; RUMBAUGH, James; JACOBSON, Ivar. UML: guia do usuário. 2. ed. Rio de Janeiro: Campus, 2006. CARDOSO, Bruno; ROSA, Sávio R.A.; FERNANDES, Tiago M. Multicore. Campinas, 2006. Disponível em: < http://www.ic.unicamp.br/~rodolfo/Cursos/mc722/2s2005/Trabalho/g07-multicore.pdf>. Acesso em: 15 set. 2007. CHARÃO, Andréa S. Análise de desempenho de programas paralelos. Santa Maria, 2006. Disponível em: < http://www-usr.inf.ufsm.br/~andrea/elc139/slides-analise-desempenho-2006b.pdf>. Acesso em: 12 out. 2007. COLOURIS, George. Sistemas distribuídos: conceitos e projetos. 4. ed. Porto Alegre: Bookman, 2007. COM CIÊNCIA. O futuro à computação pertence. Disponível em: <http://www.comciencia.br/comciencia/handler.php?section=3&noticia=268> Acessado em: 08 set. 2007. ERIKSSON, H.; PENKER, M.; LYONS B.; FADO, D.; UML 2 Toolkit. Indianapolis, US: Wiley Publishing Inc., 2004. FAZZIO, A. Alguns aspectos da nanoeletrônica molecular. São Paulo, 2004. Disponível em: <http://www.cepa.if.usp.br/e-fisica/divulgacao/oqueefisica/fazzio.php>. Acesso em: 30 out. 2007 FOWLER, Martin. UML Essencial. 3. ed. Porto Alegre: Bookman, 2005.

65

GRUNE, Dick et al. Projeto moderno de compiladores: implementação e aplicações. Rio de Janeiro: Campus, 2001. HERCKERT, Matheus G.H. Fluidodinâmica computacional e suas aplicações. Uberlândia, 2004. Disponível em: < http://br.monografias.com/trabalhos/fluidodinamica/fluidodinamica.shtml > Acesso em: 16 nov. 2007. HENNESSY, John L. ; PATTERSON, David A. Arquitetura de computadores: uma abordagem quantitativa. 3. ed. Rio de Janeiro: Campus, 2003. INTEL. Intel Multi-core Briefing. [S.I.], 2005. Disponível em: < http://download.intel.com/pressroom/kits/pentiumee/20050418presentation.pdf >. Acesso em: 12 out. 200 INTEL. Mudança radical: processamento multi-core abre possibilidades de negócio inovadoras. Disponível em: < http://developer.intel.com/portugues/technology/magazine/computing/multi-core-software-1006.htm>. Acesso em: 21 out. 2007. INTEL. Processadores Core 2 Duo. Disponível em: < http://www.intel.com/portugues/products/processor/core2duo/index.htm >. Acesso em: 25 set. 2007. LAIRD, Linda M., BRENNAN, M. Carol. Software measurement and estimation: a practical approach. New Jersy: Wiley-Interscience, 2006. LOUDON, Kyle. Dominando algoritmos com C. Rio de Janeiro: Ciência Moderna Ltda., 2000. MA, Josué T.H. Multicore. Campinas, 2006. Disponível em: < http://www.ic.unicamp.br/~rodolfo/Cursos/mo401/2s2005/Trabalho/049180-multicores.pdf>. Acesso em: 23 set. 2007. MACHADO, F.B.; MAIA, L.P. Arquitetura de Sistemas Operacionais. 3.ed. Rio de Janeiro: Livros Técnicos e Científicos, 2002. PATTERSON, David A.; HENNESSY, John L. Organização e projeto de computadores: a interface hardware/software. 2. ed., Rio de Janeiro: Livros Técnicos e Científicos, 2000. SILBERSCHATZ, Abraham. Fundamentos de Sistemas Operacionais. 6. ed. Rio de Janeiro: Livros Técnicos e Científicos, 2004 SILVEIRA, J.F Porto. Problema do Caixeiro Viajante .2000. Disponível em: <http://www.mat.ufrgs.br/~portosil/caixeiro.html>. Acesso em: 09 maio 2008. SEBESTA, Robert W. Conceitos de linguagens de programação. 4. ed. Porto Alegre: Bookman, 2000. SHIEL, Humphrey. Is your code ready for the next wave in commodity computing?

66

JavaWorld, 2006. Disponível em: < http://www.javaworld.com/javaworld/jw-07-2006/jw-0710-multicore.html > Acesso em: 27 out. 2007 SOMMERVILLE, Ian. Engenharia de Software. 6. ed. São Paulo: Pearson Education do Brasil, 2003. TANENBAUM, Andrew S. Sistemas Operacionais Modernos. 2. ed. São Paulo: Pearson Education do Brasil, 2003. TANENBAUM, Andrew S. Organização Estruturada de Computadores. 4. ed. Rio de Janeiro: Livros Técnicos e Científicos, 2001.

WIKIPEDIA. Parallel computing. Disponível em: < http://en.wikipedia.org/wiki/Parallel_Computing#Parallel_programming_models > Acessado em: 01 nov. 2007

WIKIPEDIA. Amdahl's law. Disponível em: < http://en.wikipedia.org/wiki/Amdahl%27s_law > Acessado em: 04 nov. 2007.