• Nenhum resultado encontrado

Contribuindo para a avaliação do teste de programas concorrentes: uma abordagem usando benchmarks

N/A
N/A
Protected

Academic year: 2021

Share "Contribuindo para a avaliação do teste de programas concorrentes: uma abordagem usando benchmarks"

Copied!
267
0
0

Texto

(1)Contribuindo para a avaliação do teste de programas concorrentes: uma abordagem usando benchmarks George Gabriel Mendes Dourado.

(2)

(3) SERVIÇO DE PÓS-GRADUAÇÃO DO ICMC-USP. Data de Depósito: Assinatura: ______________________. George Gabriel Mendes Dourado. Contribuindo para a avaliação do teste de programas concorrentes: uma abordagem usando benchmarks. Dissertação apresentada ao Instituto de Ciências Matemáticas e de Computação – ICMC-USP, como parte dos requisitos para obtenção do título de Mestre em Ciências – Ciências de Computação e Matemática Computacional. VERSÃO REVISADA Área de Concentração: Ciências de Computação e Matemática Computacional Orientador: Prof. Dr. Paulo Sérgio Lopes de Souza. USP – São Carlos Novembro de 2015.

(4) Ficha catalográfica elaborada pela Biblioteca Prof. Achille Bassi e Seção Técnica de Informática, ICMC/USP, com os dados fornecidos pelo(a) autor(a). D634c. Dourado, George Gabriel Mendes Contribuindo para a avaliação do teste de programas concorrentes: uma abordagem usando benchmarks / George Gabriel Mendes Dourado; orientador Paulo Sérgio Lopes de Souza. – São Carlos – SP, 2015. 245 p. Dissertação (Mestrado - Programa de Pós-Graduação em Ciências de Computação e Matemática Computacional) – Instituto de Ciências Matemáticas e de Computação, Universidade de São Paulo, 2015. 1. Benchmark. 2. Teste de Programas Concorrentes. 3. Modelos. 4. Critérios. 5. Ferramentas. 6. Java. I. Souza, Paulo Sérgio Lopes de, orient. II. Título..

(5) George Gabriel Mendes Dourado. Evaluating the testing of concurrent programs: an approach using benchmarks. Master dissertation submitted to the Instituto de Ciências Matemáticas e de Computação – ICMCUSP, in partial fulfillment of the requirements for the degree of the Master Program in Computer Science and Computational Mathematics. FINAL VERSION Concentration Area: Computer Computational Mathematics. Science. Advisor: Prof. Dr. Paulo Sérgio Lopes de Souza. USP – São Carlos November 2015. and.

(6)

(7) AGRADECIMENTOS. Primeiramente a Deus por sempre iluminar meu caminho. Ao meu orientador, Prof. Dr. Paulo Sérgio Lopes de Souza. Agradeço pela esmerada orientação, pela confiança, paciência e contribuições para minha vida pessoal e profissional. À Profa. Dra. Simone do Rocio Senger de Souza. Agradeço por toda ajuda cedida em várias etapas do meu mestrado. Aos meus pais, Dileusa e Nilton e à minha irmã Josuana, pelo amor, palavras de apoio e carinho e pela ajuda que sempre tive. À minha amada Priscila, pelo amor, carinho, amizade, incentivo e compreensão em todos os momentos. Aos amigos de república, Elias e Kleberson, pelas longas conversas e reflexões sobre os mais variados temas. Aos amigos do grupo TestPar, em especial ao Rafael Regis, Raphael Negrisoli e Alexandre Ponce. Aos membros do LaSDPC e LABES, que colaboraram com sugestões e opiniões durante os seminários e reuniões. A todos que de alguma forma contribuíram para a construção deste trabalho. Ao CNPq pelo apoio financeiro no primeiro ano de mestrado, à CAPES no segundo e à FAPESP pelo apoio a este projeto que esteve assossiado ao processo número 2013/01818-7..

(8)

(9) “What we know is a drop, what we don’t know is an ocean.” (Isaac Newton).

(10)

(11) RESUMO GEORGE G. M. DOURADO. Contribuindo para a avaliação do teste de programas concorrentes: uma abordagem usando benchmarks. 2015. 245 f. Dissertação (Mestrado em Ciências – Ciências de Computação e Matemática Computacional) – Instituto de Ciências Matemáticas e de Computação (ICMC/USP), São Carlos – SP. O teste de programas concorrentes é uma atividade que envolve diferentes perspectivas. Uma das mais conhecidas refere-se ao desenvolvimento de novos conhecimentos sobre critérios, modelos e ferramentas de teste que auxiliem o testador nessa atividade. Outra perspectiva, igualmente importante, porém, ainda incipiente, é a avaliação da atividade de teste de programas concorrentes com relação à sua eficiência e eficácia para revelar defeitos de difícil detecção. O projeto TestPar em desenvolvimento no ICMC/USP tem abordado essas duas perspectivas ao longo dos últimos anos, onde novas tecnologias de teste vêm sendo desenvolvidas e avaliadas sistematicamente. Este trabalho inseriu-se no contexto do projeto TestPar e teve por objetivo principal contribuir para melhorar a avaliação da atividade de teste de programas concorrentes, através do desenvolvimento de benchmarks específicos para este contexto. Essa avaliação representa um desafio para a área de teste, sendo essencial a existência de benchmarks simples o bastante para serem validados manualmente, se necessário, e complexos o bastante para exercitar aspectos não triviais de comunicação e sincronização, encontrados de fato nos programas concorrentes. Assim, neste trabalho de mestrado foram desenvolvidos benchmarks livres de defeitos conhecidos e algumas versões de benchmarks com defeitos intencionalmente inseridos, baseados em taxonomias de defeitos. Esses benchmarks seguiram uma série de características bem definidas, contando ainda com uma documentação padronizada e completa. Os benchmarks foram validados através da condução de estudos experimentais, do uso em diferentes projetos de pesquisa e também com a verificação da sua aplicabilidade para fins educacionais. Os resultados obtidos demonstram que os benchmarks atingiram os objetivos para os quais foram propostos, gerando uma demanda controlada e qualificada sobre modelo, critérios e a ferramenta de teste desenvolvidos no projeto TestPar. Os experimentos realizados permitiram destacar pontos positivos e limitações desses artefatos. Outra aplicação dos benchmarks foi como recurso educacional para o ensino em disciplinas como programação concorrente. Palavras-chave: Benchmark, Teste de Programas Concorrentes, Modelos, Critérios, Ferramentas, Java..

(12)

(13) ABSTRACT GEORGE G. M. DOURADO. Contribuindo para a avaliação do teste de programas concorrentes: uma abordagem usando benchmarks. 2015. 245 f. Dissertação (Mestrado em Ciências – Ciências de Computação e Matemática Computacional) – Instituto de Ciências Matemáticas e de Computação (ICMC/USP), São Carlos – SP. The testing of concurrent programs is an activity that involves distinct perspectives. One of the most known refers to the development of new knowledge about criteria, models and testing tools to support this activity. Other perspective, as important as the first one and still incipient, is the evaluation of the testing activity of concurrent programs with respect to its efficiency and effectiveness in revealing errors hard to detect. The TestPar project under development at ICMC/USP has addressed both these two perspectives over the past years, where new testing technologies are being proposed and evaluated systematically. This project belongs to the context of the TestPar project, aiming to improve the evaluation of the testing activity of concurrent programs through the development of benchmarks specific for this context. This evaluation represents a challenge to the testing area, which must consider benchmarks simple enough to be validated manually, if necessary, but also complex enough to exercise not trivial aspects of communication/synchronization, found in programs used indeed. Thus, in this work it were developed bug-free benchmarks and some versions of faulty benchmarks with bugs inserted, based on error taxonomies. These benchmarks followed a series of well-defined features, including also a standardized and complete documentation. Benchmarks were validated by means of diferent scenarios: experimental studies, their use by different on-going research projects and also with the verification of their applicability for educational aims. The results obtained show that our benchmarks have achieved their objectives, generating a controlled and qualified demand on model, criteria and the tool developed under TestPar project. The experiments reveal strengths and limitations of these artifacts. Benchmarks have been also used as educational resources for the teaching of concurrent programs. Key-words: Benchmark, Testing of Concurrent Programs, Model, Criteria, Tools, Java..

(14)

(15) LISTA DE ILUSTRAÇÕES. Figura 1 – Quadro da taxonomia de Flynn com suas arquiteturas (SISD, SIMD, MISD e MIMD) e configurações de fluxo de instruções e de dados (QUINN, 2003). .. 11. Figura 2 – (a) Arquitetura MIMD com memória compartilhada apresentando um espaço de memória acessível e comum a todos os processadores (b) Arquitetura MIMD com memória distribuída apresentando um espaço de memória exclusivo para cada processador (EL-REWINI; ABD-EL-BARR, 2005). . . . . .. 11. Figura 3 – Cenário da atividade de teste (DELAMARO; MALDONADO; JINO, 2007). 26. Figura 4 – Código fonte do programa sequencial Identifier que determina quando um dado de entrada é um identificador válido ou não (este código contém ao menos um defeito). A numeração à esquerda dos comandos representam blocos indivisíveis de execução que serão mapeados em vértices do CFG (MALDONADO et al., 2004). . . . . . . . . . . . . . . . . . . . . . . . .. 29. Figura 5 – CFG da função main do programa Identifier, em que os vértices representam os blocos indivisíveis de comandos e as arestas representam os possíveis fluxos de controle entre os vértices (MALDONADO et al., 2004). . . . . . .. 30. Figura 6 – Grafo Def-Uso da função main do programa Identifier em que é possível perceber onde as definições, usos computacionais e usos predicativos ocorrem (MALDONADO et al., 2004). . . . . . . . . . . . . . . . . . . . . . . . .. 32. Figura 7 – Código fonte do programa concorrente FactorySim (FS) que simula uma fábrica capaz de produzir uma quantidade finita de elementos solicitados por um cliente. Este programa utiliza os paradigmas de passagem de mensagem e memória compartilhada e contém um defeito introduzido intencionalmente. A numeração à esquerda dos comandos representam blocos indivisíveis de execução que serão mapeados em vértices do PCFG (SOUZA et al., 2013). .. 35. Figura 8 – PCFG do programa concorrente FactorySim (FS), em que os nós representam os blocos indivisíveis de comandos e as arestas representam o fluxo de controle, as comunicações e sincronizações entre os nós. Alguns nós de código fonte não são mostrados porque eles representam os fluxos sequenciais que não são relevantes para o critérios. (SOUZA et al., 2013). . . . . . . . .. 36. Figura 9 – Arquitetura da ferramenta ValiPar. Os módulos estão representados por retângulos enquanto as entradas e saídas de cada módulo estão representadas por elipses. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 39. Figura 10 – Elementos requeridos considerados pela ValiPar. . . . . . . . . . . . . . . .. 40.

(16) Figura 11 – Saída do módulo ValiEval para o benchmark de passagem de mensagem 002_micro_blocking_mp_pp. . . . . . . . . . . . . . . . . . . . . . . . . . Figura 12 – Informações de cobertura para o benchmark de passagem de mensagem 002_micro_blocking_mp_pp após executar o terceiro nível da árvore de variantes, resultando no aumento da cobertura das arestas de sincronização (synchronization edges) de 4 para 12. . . . . . . . . . . . . . . . . . . . . . Figura 13 – Exemplo da dependência cíclica para o benchmark TR_Same_Primitives. . . Figura 14 – Arquivo de rastro do benchmark TR_Same_Primitives. . . . . . . . . . . . Figura 15 – Arquivo de rastro da execução livre do benchmark Parallel_GCD. . . . . . . Figura 16 – Arquivo de rastro da execução determinística do benchmark Parallel_GCD. Figura 17 – Arquivo de rastro da execução livre do benchmark Non_Blocking_MP. . . . Figura 18 – Arquivo de rastro da execução determinística do benchmark Non_Blocking_MP. Figura 19 – Exemplo que ilustra o descarte de variante para o benchmark Master_Slave.. 41. 42 70 71 72 72 72 73 76.

(17) LISTA DE TABELAS. Tabela 1 – Elementos e critérios relacionados ao programa Identifier (MALDONADO et al., 2004). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 33. Tabela 2 – Comparação entre os benchmarks. Legenda: (l) contempla; (w) contempla parcialmente; (m) não contempla. . . . . . . . . . . . . . . . . . . . . . . .. 49. Tabela 3 – Lista dos benchmarks desenvolvidos. . . . . . . . . . . . . . . . . . . . . .. 50. Tabela 4 – Informações que ilustram como o aumento de processos e threads nos benchmarks flexíveis influenciam na quantidade de elementos gerados pela ferramenta ValiPar, tais como: número de primitivas sends, receives e barreiras (colunas Nr. de Sends, Nr. de Receives, Nr. de Barreiras), na soma das quantidades de nós e arestas (Nr. de Nós e Arestas), na soma do número de operações que envolvem fluxo de dados (Nr. de Oper. sobre Dados), na quantidade total das informações do modelo (Total de Infor. do Modelo) e na quantidade de arestas de sincronização geradas pelo módulo ValiInst (Nr. de Arestas de Sinc.). O símbolo (*), presente em algumas células, indica que não foi possível obter os valores com o apoio da ferramenta ValiPar devido uma limitação da mesma. . . . . . . . . . . . . . . . . . . . . . . . . . . .. 52. Tabela 5 – Tipos de defeitos inseridos nos benchmarks. . . . . . . . . . . . . . . . . .. 60. Tabela 6 – Semeadura de defeitos nos benchmarks e tipo de erro observado. . . . . . .. 61. Tabela 7 – Características de hardware e software do cluster onde foram realizados os experimentos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 66. Tabela 8 – Informações sobre as execuções livres e determinísticas, da geração de rastros e da geração de variantes. Células preenchidas com parcial ou não, indicam que a execução não ocorreu com sucesso para todos os casos de teste do benchmark devido as limitações da ferramenta. Células preenchidas com (?) indicam que não foi possível gerar variantes para o benchmark devido à utilização de primitivas coletivas, não suportadas pela geração de variantes.. 68. Tabela 9 – Informações sobre elementos cobertos/elementos requeridos considerando a cobertura dos critérios All-Uses (AU), All-Shared-Uses (ASU), All-intrAMessage-Uses (AAMU) e All-intEr-Message-Uses (AEMU). . . . . . . . .. 74.

(18) Tabela 10 – Informações sobre a quantidade total de arestas de sincronização, quantidade de arestas de sincronização executáveis (ou factíveis), quantidade de arestas de sincronização consideradas não executáveis e quantidade média de variantes geradas. Células preenchidas com (?) indicam que não é possível gerar variantes para o benchmark devido a utilização de primitivas coletivas, não suportadas pela geração de variantes. . . . . . . . . . . . . . . . . . . . . . 75 Tabela 11 – Informações sobre a capacidade da ferramenta em revelar o defeito, quantidade de casos de teste que revelaram o defeito, necessidade, ou não, do auxílio da geração de variantes para se revelar o defeito, e em caso afirmativo, a quantidade de variantes geradas até revelar e, por fim, a possibilidade, ou não, de se revelar o defeito observando a cobertura das arestas de sincronização. 78.

(19) SUMÁRIO. 1. INTRODUÇÃO. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 1. 1.1. Contextualização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 1. 1.2. Motivação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 4. 1.3. Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 4. 1.4. Organização deste Texto . . . . . . . . . . . . . . . . . . . . . . . . . .. 4. 2. PROGRAMAÇÃO CONCORRENTE . . . . . . . . . . . . . . . . . .. 7. 2.1. Considerações Iniciais . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 7. 2.2. Contextualização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 7. 2.3. Arquiteturas Paralelas . . . . . . . . . . . . . . . . . . . . . . . . . . .. 10. 2.4. Técnicas de Decomposição . . . . . . . . . . . . . . . . . . . . . . . .. 12. 2.5. Modelos de Algoritmos Paralelos . . . . . . . . . . . . . . . . . . . . .. 13. 2.6. Ferramentas para o Desenvolvimento de Programas Concorrentes .. 14. 2.6.1. Programação Concorrente em Java . . . . . . . . . . . . . . . . . . .. 16. 2.6.2. Criação, Inicialização e Finalização de Threads em Java . . . . . . .. 16. 2.6.3. Sincronização de Threads Java . . . . . . . . . . . . . . . . . . . . . .. 17. 2.6.3.1. Blocos e Métodos Synchronized . . . . . . . . . . . . . . . . . . . . . . . .. 17. 2.6.3.2. Wait, Notify e NotifyAll . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 17. 2.6.3.3. Semáforo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 18. 2.6.3.4. Bloqueio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 19. 2.6.3.5. Barreira . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 19. 2.6.3.6. Variáveis de Condição . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 20. 2.6.4. Memória Distribuída/Passagem de Mensagem . . . . . . . . . . . . .. 21. 2.6.4.1. Sockets TCP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 21. 2.6.4.2. Sockets UDP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 22. 2.6.4.3. Comunicação Coletiva . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 23. 2.7. Considerações Finais . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 24. 3. TESTE DE SOFTWARE . . . . . . . . . . . . . . . . . . . . . . . . 25. 3.1. Considerações Iniciais . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 25. 3.2. Terminologia e Conceitos Básicos . . . . . . . . . . . . . . . . . . . .. 25. 3.3. Teste Estrutural de Programas Sequenciais . . . . . . . . . . . . . . .. 28. 3.4. Teste Estrutural de Programas Concorrentes . . . . . . . . . . . . . .. 33.

(20) 3.5. A Ferramenta ValiPar . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 38. 3.6. Considerações Finais . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 41. 4. BENCHMARKS PARA AVALIAR A ATIVIDADE DE TESTE DE PROGRAMAS CONCORRENTES . . . . . . . . . . . . . . . . . . . 43. 4.1. Considerações Iniciais . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 43. 4.2. Trabalhos Relacionados . . . . . . . . . . . . . . . . . . . . . . . . . . .. 43. 4.3. Características dos Benchmarks. . . . . . . . . . . . . . . . . . . . . .. 46. 4.4. Benchmarks Desenvolvidos e Adequação às Características . . . . .. 49. 4.5. Considerações Finais . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 53. 5. INSERÇÃO DE DEFEITOS . . . . . . . . . . . . . . . . . . . . . . . 55. 5.1. Considerações Iniciais . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 55. 5.2. Taxonomia de Erros e Defeitos . . . . . . . . . . . . . . . . . . . . . .. 55. 5.3. Defeitos em Programas Concorrentes Java . . . . . . . . . . . . . . .. 57. 5.4. Inserção de Defeitos nos Benchmarks . . . . . . . . . . . . . . . . . .. 60. 5.5. Considerações Finais . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 63. 6. EXPERIMENTOS E ANÁLISE DOS RESULTADOS . . . . . . . . . 65. 6.1. Considerações Iniciais . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 65. 6.2. Metodologia dos Experimentos . . . . . . . . . . . . . . . . . . . . . .. 65. 6.3. Experimentos com os Benchmarks Livres de Defeitos Conhecidos .. 68. 6.3.1. Análise das Execuções . . . . . . . . . . . . . . . . . . . . . . . . . . .. 68. 6.3.2. Análise dos Critérios . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 73. 6.3.3. Análise das Arestas de Sincronização . . . . . . . . . . . . . . . . . .. 74. 6.4. Experimentos com os Benchmarks com Defeitos Inseridos . . . . .. 77. 6.5. Ameaças à Validade dos Experimentos . . . . . . . . . . . . . . . . .. 80. 6.6. Uso dos Benchmarks Desenvolvidos em Outras Pesquisas . . . . . .. 80. 6.7. Considerações Finais . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 82. 7. CONCLUSÕES E CONTRIBUIÇÕES . . . . . . . . . . . . . . . . . 85. 7.1. Caracterização da Pesquisa Realizada . . . . . . . . . . . . . . . . . .. 85. 7.2. Contribuições . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 87. 7.3. Produção Científica . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 88. 7.4. Trabalhos Futuros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 88. REFERÊNCIAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 APÊNDICE A. DOCUMENTAÇÃO DOS BENCHMARKS . . . . . . . 99. APÊNDICE B. ADEQUAÇÃO ÀS CARACTERÍSTICAS . . . . . . . . 241.

(21) B.1 B.2 B.3 B.4. Benchmarks Benchmarks Benchmarks Benchmarks. Rungta: . Helgrind: Inspect: . IBM: . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. . . . .. 241 242 243 244.

(22)

(23) 1. CAPÍTULO. 1. INTRODUÇÃO. 1.1. Contextualização. A programação concorrente tem sido fundamental para atender as expectativas da computação de alto desempenho e distribuída. Ela está sendo aplicada em várias áreas, como em servidores web, que devem ser capazes de manipular centenas de milhares de conexões concorrentes e atender dezenas de milhares de requisições por segundo (MARLOW, 2013), ou em data centers em que a recuperação de informações pode ser feita de modo concorrente em operações multiusuários (BERKA; HAGENAUER; VAJTERšIC, 2012), ou ainda em algoritmos numéricos para resolver equações de sistemas lineares, métodos de álgebra linear paralelos usados em mineração de dados, multiplicação de matrizes (TROBEC; VAJTERŠIC; ZINTERHOF, 2009), dentre outros exemplos. Entretanto, escrever programas de computador, seja ele sequencial ou concorrente, pode ser uma atividade complexa, a depender das características de dimensionamento e propósito do sistema. A construção de softwares depende, em grande parte, da habilidade, da interpretação e da capacidade de execução das pessoas envolvidas na implementação do mesmo e, portanto, está sujeito a enganos, mesmo com o uso de métodos e ferramentas de engenharia de software especificamente desenvolvidas para minimizar tais enganos (DELAMARO; MALDONADO; JINO, 2007). Quando se trata de programas sequenciais essa dificuldade é reduzida (apesar de ainda ser complexa), pois esse tipo de programa se caracteriza pela execução linear dos comandos existentes em sua definição. Nesta categoria de programas, apenas uma instrução pode ser executada em um dado instante e todos os acontecimentos são previsíveis, o que evidencia seu comportamento determinístico (para execuções de um programa sequencial com um mesmo conjunto de entrada deverá haver sempre o mesmo conjunto de saída). Na abordagem de programas concorrentes novos desafios são inseridos, pois diferente-.

(24) 2. Capítulo 1. Introdução. mente dos programas sequenciais, eles são compostos por diversas linhas de execução (processos ou threads) que podem, inclusive, serem executadas em um mesmo instante de tempo em processadores distintos. Tais threads são vistas como subconjuntos de programas sequenciais que são executados de maneira independente e que podem interagir (comunicar e sincronizar), o que torna o desenvolvimento de aplicações concorrentes mais complexo e, consequentemente, mais propenso a ocorrências de defeitos. Dada a complexidade da construção de software (concorrente ou não) e a provável ocorrência de defeitos em seu desenvolvimento, uma série de atividades conhecidas conjuntamente por “Validação, Verificação e Teste” ou “VV&T” contribui para garantir que o modo de construção do software e o produto final em si estejam de acordo com o especificado (DELAMARO; MALDONADO; JINO, 2007). O Teste compreende uma das atividades da VV&T e é um dos tópicos que está altamente relacionado à qualidade de software. No entanto, enquanto os modelos, critérios e ferramentas para teste de programas sequenciais têm sido estudados ao longo dos anos e estão bem definidos, o mesmo não ocorre com o teste de programas concorrentes, necessitando-se mais pesquisas e estudos nesta área. Neste sentido, o Projeto TestPar vem sendo desenvolvido no Instituto de Ciências Matemáticas e de Computação (ICMC/USP), a fim de diminuir a defasagem de conhecimento existente no teste de programas concorrentes. O projeto TestPar investiga o teste de programas concorrentes de maneira abrangente, considerando modelos, critérios e ferramentas de teste estrutural no contexto de aplicações baseadas nos paradigmas de memória compartilhada e passagem de mensagem. O modelo permite criar uma abstração do programa, geralmente como um grafo dirigido, que tem como objetivo representar os fluxos de controle, de dados e sincronização dos sistemas reais (PEZZÈ; YOUNG, 2009). Um critério indica se determinados níveis de cobertura de teste são cumpridos, tais como cobertura de todos os comandos ou desvios de um programa, entre outros. Os critérios também auxiliam na seleção de um conjunto menor de dados de teste, que poderá revelar a maior quantidade de defeitos possíveis (DENVIR; HERMAN; WHITTY, 2012). Por fim, a ferramenta é o que implementa o modelo, os critérios e demais artefatos, permitindo a automatização de diversas partes da atividade de teste. A ferramenta de teste de software ValiPar (SOUZA et al., 2008) desenvolvida no projeto TestPar, apoia a aplicação dos modelos e critérios de teste de programas concorrentes baseados na técnica estrutural que leva em consideração o código fonte do programa e seus elementos básicos como fluxo de controle (instruções e desvios condicionais) e fluxo de dados (definição e uso de variáveis). Há versões desta ferramenta instanciadas para o teste de programas concorrentes tanto na abordagem de passagem de mensagem quanto para a de memória compartilhada. São representantes do primeiro grupo as ferramentas ValiPVM (SOUZA et al., 2008), ValiMPI (HAUSEN; VERGILIO, 2005) e ValiBPEL (ENDO et al., 2008) que testam programas concorrentes escritos em PVM, MPI e BPEL, respectivamente. No segundo grupo está a ValiPThread (SARMANHO.

(25) 1.1. Contextualização. 3. et al., 2008) que testa programas implementados em C/Pthread. Recentemente foi desenvolvida uma nova versão da ValiPar que unifica os modelos de passagem de mensagem e memória compartilhada (SOUZA et al., 2013). Esta nova versão da ferramenta prevê a modularização de diversas atividades desenvolvidas durante a atividade de teste, a fim de garantir a reusabilidade de código, bem como favorecer a flexibilidade da ferramenta para testar diferentes linguagens de programação concorrente. Esta nova versão da ValiPar foi instanciada inicialmente para a linguagem Java. Além de desenvolver modelos, critérios e ferramentas de teste estrutural para programas concorrentes, o projeto TestPar investiga também a eficiência e a eficácia dessa atividade de teste. A eficiência considera a relação custo-benefício do teste de programas concorrentes proposto. A eficácia, por sua vez, considera quão bons são os critérios de teste para revelar defeitos ainda não conhecidos no código fonte. Essa avaliação de desempenho do teste de programas concorrentes é feita em diferentes perspectivas, tais como: (1) eficácia de um determinado critério em revelar defeitos, (2) eficiência dos modelos em representar os programas, (3) observação do desempenho das ferramentas de teste frente a códigos concorrentes simples e complexos, do ponto de vista de LOC (Lines Of Code), número de processos/threads e da interação entre threads, (4) abrangência do teste em relação aos diferentes paradigmas de comunicação e sincronização (passagem de mensagem e memória compartilhada), (5) abrangência com relação às diferentes linguagens de programação (C, MPI, PVM, PThreads, Java, OpenMP, BPEL e outras). Neste contexto, benchmarks (YANG, 2013; VALGRIND-DEVELOPERS, 2013; RUNGTA; MERCER, 2009) têm sido usados por pesquisadores em teste de software como material de apoio para a realização de estudos na avaliação de teste de programas concorrentes. Entretanto, os autores relatam algumas dificuldades encontradas ao utilizarem tais benchmarks. Dentre elas estão a pouca flexibilidade e a pouca escalabilidade dos mesmos. Para reverter essa situação entende-se que os benchmarks devem permitir que seus programas concorrentes apresentem códigos com demandas variadas, utilizem diversos casos de teste para satisfazer os critérios, possam ser executados em diferentes plataformas e sejam implementados utilizando as novas primitivas suportadas pelas linguagens. Os benchmarks voltados para o teste de programas concorrentes devem também ser compostos por códigos corretos (sem defeitos conhecidos) e, alternativamente devem ser compostos por códigos com defeitos conhecidos, especialmente projetados e inseridos para exercitar a capacidade dos critérios de teste em revelar tais defeitos. Assim, este trabalho de mestrado insere-se no contexto do projeto TestPar e visa contribuir com a avaliação da atividade de teste de programas concorrentes através do desenvolvimento de benchmarks que possuam características bem definidas e que permitam avaliar modelos, critérios e ferramentas de teste. Além de ser aplicado diretamente no projeto TestPar, espera-se,.

(26) 4. Capítulo 1. Introdução. igualmente, que os benchmarks desenvolvidos neste projeto sejam utilizados por outros grupos de pesquisa no mesmo tema.. 1.2. Motivação. Com as pesquisas realizadas neste trabalho foi possível perceber que os benchmarks encontrados são pouco flexíveis em relação à escalabilidade e que muitos já não contemplam as novas primitivas suportadas pelas linguagens. Observou-se, também, que não há benchmarks para o teste de programas concorrentes que sejam aplicados ao uso simultâneo dos paradigmas de comunicação e sincronização e que avaliem o teste de programas concorrentes de maneira abrangente, considerando modelos, critérios e ferramentas. Neste contexto torna-se viável a elaboração de benchmarks que contemplem estes aspectos, facilitando a avaliação dos modelos, critérios e ferramentas de teste de programas concorrentes, desenvolvidos tanto no projeto TestPar quanto por outros grupos de pesquisa, viabilizando a comparação de resultados gerados e ainda contribuindo para a melhoria da qualidade dos testes de programas concorrentes.. 1.3. Objetivos. Este projeto de mestrado tem como objetivo geral contribuir para a avaliação de desempenho da atividade de teste de programas concorrentes, usando a técnica de aferição por benchmarks. Por desempenho, neste contexto, entende-se a avaliação de modelos, critérios e ferramentas de teste que são utilizados para testar programas concorrentes que interagem simultaneamente por passagem de mensagem e memória compartilhada. Associados a este objetivo geral há os seguintes objetivos específicos: (1) especificar, desenvolver, validar e disponibilizar um conjunto de benchmarks com programas concorrentes que viabilizem a avaliação por aferição proposta, considerando aspectos de escalabilidade e flexibilidade; (2) projetar e inserir defeitos nos benchmarks desenvolvidos para que tais defeitos coloquem à prova os critérios e ferramentas de teste; (3) conduzir e disponibilizar estudos que avaliem a atividade de teste de programas concorrentes; (4) indicar eventuais problemas na metodologia de teste desenvolvida no projeto TestPar, a fim de fundamentar futuras pesquisas que otimizem a qualidade dos programas concorrentes.. 1.4. Organização deste Texto. No Capítulo 2 abordam-se os principais conceitos e terminologias a respeito da programação concorrente, suas principais arquiteturas e paradigmas de programação (memória compartilhada e passagem de mensagem). No Capítulo 3 são apresentados conceitos referentes ao teste estrutural de programas concorrentes, à terminologia utilizada, principais critérios de teste estrutural, além de descrever a ferramenta de apoio ao teste estrutural ValiPar. No Capítulo.

(27) 1.4. Organização deste Texto. 5. 4 são apresentados os benchmarks desenvolvidos neste trabalho de mestrado, destacando suas principais características e em que eles se diferenciam dos benchmarks existentes. No Capítulo 5 é discutida a inserção de defeitos em benchmarks, evidenciando taxonomias de defeitos, dando ênfase à classificação de defeitos em programas concorrentes escritos em Java, além de apresentar como e quais defeitos foram inseridos nos benchmarks desenvolvidos neste trabalho de mestrado. No Capítulo 6 são apresentados os resultados dos experimentos realizados tanto com os benchmarks livres de defeitos conhecidos quanto com os benchmarks com defeitos inseridos, além da análise de tais resultados e também destaca quais outros trabalhos fizeram uso de tais benchmarks. O Capítulo 7 apresenta as conclusões deste projeto de mestrado destacando as principais contribuições e possíveis trabalhos futuros. O Apêndice A traz as documentações dos benchmarks desenvolvidos. Por fim, o Apêndice B mostra o resultado do estudo da adequação das características apresentadas no Capítulo 4, sobre os benchmarks desenvolvidos por outros grupos de pesquisa..

(28)

(29) 7. CAPÍTULO. 2. PROGRAMAÇÃO CONCORRENTE. 2.1. Considerações Iniciais. Como este projeto de mestrado visa contribuir com o teste de programas concorrentes através da elaboração de benchmarks, este capítulo tem por objetivo mostrar as principais características e desafios que envolve a construção de programas concorrentes, saber em que eles se diferenciam dos programas sequenciais e quais as primitivas que a linguagem Java oferece para o desenvolvimento de programas desse tipo, tendo em vista que os benchmarks foram feitos nessa linguagem. Dessa forma, este capítulo apresenta alguns dos principais conceitos relacionados ao desenvolvimento de programas concorrentes, abordando termos usados na área, arquiteturas que permitem executar programas concorrentes, questões para o projeto de algoritmos concorrentes e, por fim, algumas das ferramentas que a linguagem Java oferece para o desenvolvimento de programas concorrentes. O capítulo está estruturado como segue: na Seção 2.2 é apresentado o contexto da programação concorrente, abordando os principais termos e conceitos que a envolvem. Na Seção 2.3 é apresentada a classificação das arquiteturas de hardware seguindo a taxonomia de Flynn. A Seção 2.4 aborda as principais técnicas de decomposição. A Seção 2.5 relaciona alguns modelos para a criação de programas concorrentes. A Seção 2.6 especifica as principais ferramentas para a construção de programas concorrentes em Java.. 2.2. Contextualização. No mundo da computação o termo concorrência diz respeito a um conjunto de processos que executam simultaneamente em um ou vários computadores. O conceito de concorrência foi a base para a implementação de sistemas operacionais multiprogramados, aqueles que permitem.

(30) 8. Capítulo 2. Programação Concorrente. passar ao usuário a sensação de que vários programas estão sendo executados simultaneamente, mesmo que exista apenas um processador (CARTER, 2002). Tais sistemas surgiram a partir das limitações observadas em sistemas monoprogramáveis nos quais os recursos computacionais eram subutilizados, prejudicando o desempenho final do sistema. Em sistemas monoprogramáveis um único programa pode ser executado por vez e o processador permanece dedicado exclusivamente a esta tarefa. Este fato resulta em desperdício no uso do processador no momento em que operações de E/S são solicitadas, já que tais operações tendem a ser muito mais lentas se comparadas à velocidade do processador (MACHADO; MAIA, 2007). Em sistemas multiprogramados vários programas podem estar armazenados em memória e prontos para concorrer pela utilização do processador. Neste cenário, caso uma operação de E/S seja solicitada por um processo, outros poderão utilizar o processador fazendo com que ele fique menos tempo ocioso (MACHADO; MAIA, 2007). Quando existe um único processador no sistema, a concorrência pode ser vista como um pseudoparalelismo (TANENBAUM, 2003). Apesar de melhorar a utilização do processador e de se obter tempos de resposta menores para as computações com o pseudoparalelismo, o anseio por maior poder computacional fez com que, ao invés de se construir processadores mais rápidos e complexos de núcleo único, se desenvolvessem estruturas compostas por múltiplos processadores. Estas novas estruturas de múltiplos processadores permitiram a execução simultânea de vários processos em um mesmo instante de tempo, cada um em um processador diferente. Isso caracteriza um paralelismo real (PACHECO, 2011). Os conceitos de processos concorrentes e paralelos são distintos. Dois ou mais processos são ditos concorrentes se em um determinado instante no tempo eles começaram sua execução e não a finalizaram (ALMASI; GOTTLIEB, 1994). Esta definição não implica em uma execução simultânea em diferentes processadores. Por outro lado, dois ou mais processos concorrentes são ditos paralelos se, em um determinado instante do tempo, podem estar todos em execução em processadores (ou núcleos de processamento) distintos. Dessa maneira, o significado do termo concorrência é mais abrangente do que o de paralelismo e, portanto, todos os programas paralelos são concorrentes, mas nem todos os programas concorrentes são paralelos (BUTTLAR; FARRELL; NICHOLS, 1996). Em outras palavras, o paralelismo é um tipo de concorrência. O entendimento de como a programação concorrente funciona parte do conhecimento de alguns termos e conceitos. Um programa de computador, por exemplo, é definido como um conjunto de instruções que podem ser executadas por um computador; é tão somente o código fonte. Quando o programa é de fato executado em um computador ele passa a ser conhecido como processo. Este processo manterá consigo todas as informações necessárias para sua execução como conteúdos de registradores gerais e de uso específico (contexto de hardware), especificações de limites e características dos recursos que poderão ser alocados pelo processo (contexto de software), e por fim, uma área de memória onde instruções e dados dos programas são armazenados para a execução (espaço de endereçamento) (MACHADO; MAIA, 2007)..

(31) 2.2. Contextualização. 9. Toda criação de um processo demanda a alocação de recursos e um consumo de tempo considerável da unidade de processamento para esta atividade. A finalização de um processo também implica em gasto de tempo para a liberação dos recursos que foram alocados. O conceito de thread foi proposto para reduzir a perda de tempo com a criação, finalização e troca de contexto de processos em aplicações concorrentes. Cada thread possui seu próprio contexto em relação à linha de execução do processo (como por exemplo o PC (Program Counter) e o SP (Stack Pointer)), porém as instruções do programa e as variáveis globais são compartilhadas entre todas as threads. Threads também são conhecidos como processos leves (MACHADO; MAIA, 2007). A programação concorrente tem como estratégia dividir a computação em partes menores que possam ser executadas de modo simultâneo e mais rapidamente. O processo de divisão de uma computação é conhecida como decomposição e pode ser efetuada sob diferentes técnicas. A técnica escolhida para se decompor uma computação pode afetar seu desempenho e por isso seu conhecimento é fundamental. A decomposição de um problema maior gera unidades conhecidas como tarefas. Tais tarefas podem apresentar comportamentos independentes ou possuírem dependência umas com as outras, geralmente, imposta pela utilização de dados que são produzidos por uma ou mais tarefas. Estas dependências entre tarefas e a ordem pela qual são executadas podem ser representadas em um grafo de dependência de tarefas (GRAMA et al., 2003). Outro conceito importante é o de granularidade. Ele é determinado pela quantidade e o tamanho das tarefas que foram decompostas a partir do problema original. Uma decomposição que dá origem a um grande número de tarefas pequenas é conhecida como granularidade fina, no caso em que é gerado um número pequeno de grandes tarefas a granularidade grossa se faz presente. A granularidade está relacionada ao grau de concorrência que determina o número máximo de tarefas capazes de serem executadas em paralelo. Na maioria absoluta dos casos, o grau máximo de concorrência é menor do que o número total de tarefas ao qual o problema foi decomposto devido à dependência existente entre as tarefas. A interação entre tarefas que são executadas em máquinas distintas também é um fator importante na programação concorrente, já que esta comunicação exige um tempo para ser realizada (GRAMA et al., 2003). A concorrência traz consigo, na maioria das vezes, a capacidade de tornar as computações mais ágeis. Para medir a capacidade que um programa concorrente tem em resolver mais rapidamente os problemas, algumas métricas são bastantes usadas. Uma das principais é o speedup que determina o quão rápido é um programa concorrente quando comparado à sua versão sequencial, considerando a execução em um mesmo computador. Idealmente a versão concorrente deve ser mais rápida que a versão sequencial, entretanto as características do problema em questão ou até o modo que o programa concorrente foi construído podem contrariar esta expectativa. Outra métrica empregada é a eficiência que é dada pela divisão do speedup.

(32) 10. Capítulo 2. Programação Concorrente. pelo número de processadores da máquina em questão. A eficiência determina quanto cada processador está sendo usado para a computação analisada. Como a maioria dos programas paralelos apresentam grandes variações de desempenho a depender do hardware da máquina ao qual o programa é executado, a métrica eficiência permite mensurar mais facilmente a qualidade de um programa concorrente em diferentes ambientes (DUFFY, 2008).. 2.3. Arquiteturas Paralelas. O desenvolvimento de aplicações concorrentes eficientes depende, dentre outros aspectos, das arquiteturas de hardware e de suas características. Ao longo dos anos as arquiteturas dos computadores passaram por muitas inovações as quais estão relacionadas à quantidade de unidades de processamento, e ao número de instruções e dados que podem tratar por vez. Estas inovações resultaram em diversas configurações de hardware distintas, sendo necessária uma maneira de classificá-las em grupos bem definidos. A taxonomia de Flynn (FLYNN; RUDD, 1996) tornou-se o modelo de classificação mais conhecido e utilizado, e se baseia na quantidade de fluxo de instruções e de dados. Nessa taxonomia as arquiteturas são divididas em SISD, SIMD, MISD e MIMD (Figura 1), as quais possuem as seguintes características: • SISD (Single Instruction, Single Data Stream): remete ao modelo de von Neumann (NULL; LOBUR, 2010) usado em computadores sequenciais convencionais englobando sistemas monoprocessados que possuem um único fluxo de instrução e um único fluxo de dados. • SIMD (Single Instruction, Multiple Data Stream): refere-se a computadores com um único fluxo de instrução, porém com múltiplos fluxos de dados, ou seja, uma Unidade de Controle permite a geração de um fluxo de instruções e diferentes Unidades de Processamento (Arithmetic Logic Unit (ALU), Floating Point Unit (FPU), entre outras) executam uma mesma instrução de maneira simultânea sobre diferentes valores de dados (QUINN, 2003). • MISD (Multiple Instruction, Single Data Stream): são computadores com múltiplos fluxos de instrução, porém com um único fluxo de dados, ou seja, vários elementos de processamento recebendo diferentes conjuntos de instruções mas operando sobre o mesmo conjunto de dados. Alguns autores afirmam não existirem exemplos práticos desta arquitetura (SHARMA, 2009; KONTOGHIORGHES, 2010), outros reconhecem as arquiteturas sistólicas1 como representantes deste grupo. • MIMD (Multiple Instruction, Multiple Data stream): é uma arquitetura que remete a computadores com múltiplos fluxos de instrução e múltiplos fluxos de dados, ou seja, cada processador é capaz de carregar uma instrução diferente e trabalhar sobre um conjunto independente de dados. Esta é a arquitetura que permite ótimo grau de paralelismo, com 1. Arquitetura sistólica, também conhecida como arranjo sistólico, é um tipo de arquitetura na qual os dados fluem de modo ritmado entre os vários elementos de processamento..

(33) 2.3. Arquiteturas Paralelas. 11. flexibilidade na execução; é a mais comum atualmente (MARSHALL, 2011; TOP500, 2014).. Simples. M últiplo. SI SD. SI MD. Uniprocessadores. Processadores Vetoriais Processadores Matriciais. MI SD. MI MD. Arquiteturas Sistólicas. Multiprocessadores Multicomputadores. M últiplo. Fluxo de I nstr uções. Simples. Fluxo de Dados. Figura 1 – Quadro da taxonomia de Flynn com suas arquiteturas (SISD, SIMD, MISD e MIMD) e configurações de fluxo de instruções e de dados (QUINN, 2003).. M. M. M. M. M. Rede de I nt er c onex ão. P. P. P = Processador M = M emór ia (a). P. .... P. Rede de I nt er c onex ão. P. P. P. M. M. M. .... P. M. (b). Figura 2 – (a) Arquitetura MIMD com memória compartilhada apresentando um espaço de memória acessível e comum a todos os processadores (b) Arquitetura MIMD com memória distribuída apresentando um espaço de memória exclusivo para cada processador (EL-REWINI; ABD-EL-BARR, 2005).. A arquitetura MIMD pode ainda ser dividida em duas classes distintas: as com memória compartilhada (multiprocessadores) e as com memória distribuída (multicomputadores). A diferença entre elas encontra-se no modo de acesso à memória. Arquiteturas MIMD com memória compartilhada (Figura 2 (a)) tem como característica a existência de um único espaço de dados acessível e comum a todos os processadores os quais podem interagir, implicitamente, através da modificação dos dados (variáveis compartilhadas) armazenadas neste espaço de endereçamento. Arquiteturas MIMD com memória distribuída (Figura 2 (b)), por sua vez, trabalham com um espaço de memória exclusivo para cada processador e a interação entre os processadores pode ser.

(34) 12. Capítulo 2. Programação Concorrente. feita através de passagem de mensagem (HAGER; WELLEIN, 2010). Este trabalho de mestrado concentra-se no contexto da arquitetura MIMD com memória distribuída e compartilhada.. 2.4. Técnicas de Decomposição. A divisão da computação em partes menores é crucial para permitir a resolução concorrente dos problemas. Entretanto, o próprio problema pode conter características de dependências e interações que necessitam da aplicação de técnicas de decomposição específicas para alcançar um melhor grau de concorrência. Grama et al. (2003) apresentam algumas técnicas de decomposição as quais são descritas abaixo: • Decomposição Recursiva: é um método de decomposição que é utilizado para induzir a concorrência em problemas que podem ser solucionados através da estratégia de dividir para conquistar. Nesta técnica, um problema é resolvido primeiramente dividindo-se o cenário original em um conjunto de subproblemas que sejam independentes entre si. Estes subproblemas, por sua vez, podem ser resolvidos através da aplicação recursiva da mesma divisão que resultam em subproblemas ainda menores seguidos pela combinação de seus resultados. O ponto chave deste método são os diferentes subproblemas, que por serem independentes, podem ser resolvidos concorrentemente. • Decomposição por Dado: é um método bastante utilizado para induzir a concorrência em algoritmos que realizam algum tipo de computação em grandes estruturas de dados. Neste método a computação é decomposta em dois passos. No primeiro, os dados em que a computação será realizada são particionados, e no segundo passo, os dados particionados são usados para induzir um particionamento das computações em tarefas. Geralmente as operações que estas tarefas executam são as mesmas, porém atuando em diferentes porções dos dados. Um problema que pode ser facilmente resolvido com este método de decomposição é a multiplicação de matrizes. • Decomposição Exploratória: é um método usado para decompor problemas cujas computações subsequentes correspondem a uma pesquisa de um espaço para obter as soluções. Em decomposições exploratórias o espaço de busca é dividido em partes menores e a busca é feita em cada parte de modo concorrente até que as soluções desejadas sejam encontradas. Um exemplo resolvível mais eficientemente através deste método é o problema 15-puzzle (quebra-cabeça para ordenar peças móveis com números que vão de 1 a 15). • Decomposição Especulativa: é um método usado quando um programa pode assumir um dos vários possíveis ramos computacionalmente alcançáveis em função da saída de computações anteriores. Nesta técnica de decomposição quando uma tarefa está executando uma computação, cuja saída será usada para auxiliar na decisão da próxima computação,.

(35) 2.5. Modelos de Algoritmos Paralelos. 13. outras tarefas podem iniciar a computação de fases seguintes de modo concorrente. Esta técnica é útil em simulações de eventos discretos. A decomposição recursiva e a por dado são métodos de propósitos mais gerais, que podem ser utilizados para decompor uma série de problemas, por outro lado, a decomposição explorativa e especulativa são técnicas mais restritas e que se aplicam em classes mais específicas de problemas.. 2.5. Modelos de Algoritmos Paralelos. Para a criação de programas paralelos é comum a utilização de modelos que auxiliam na estruturação dos algoritmos através da seleção de técnicas de decomposição apropriadas e também da aplicação de estratégias que visam minimizar a interação entre as tarefas. Dentre os modelos apresentados por Grama et al. (2003) estão: • Modelo de Paralelismo de Dados: é considerado o modelo mais simples e tem como característica o mapeamento estático ou semiestático das tarefas em processos, na qual cada tarefa é responsável por realizar a mesma operação sobre diferentes dados. Este tipo de paralelismo, também conhecido como SPMD (Single Program Multiple Data), resulta de operações idênticas sendo aplicadas de modo concorrente em diferentes itens de dados. • Modelo de Grafo de Tarefas: Este modelo é utilizado para a resolução de problemas em que a quantidade de dados que se deve tratar é relativamente mais significativa que a quantidade de computação a ser realizada. Neste tipo de paralelismo, conhecido também como paralelismo de tarefas, um único processo pode conter n tarefas tendo como critério de agrupamento suas dependências. A quantidade de dependências entre as tarefas é utilizada para reduzir o custo das interações. Um exemplo de algoritmo que se baseia neste modelo é o quicksort paralelo. • Modelo Work Pool de Tarefas: se caracteriza por um mapeamento dinâmico das tarefas em que qualquer tarefa pode potencialmente ser desenvolvida por qualquer processo tendo como objetivo principal o balanceamento de carga. Em programas que utilizam o paradigma de passagem de mensagem, este modelo pode ser empregado quando a quantidade de dados associados às tarefas é relativamente menor que a quantidade de computação a ser realizada permitindo que tarefas se movam entre os processos sem causar muito overhead. • Modelo Mestre-Escravo: neste modelo um ou mais processos mestres são responsáveis por gerenciar a alocação de tarefas para os processos escravos, sendo necessário ter atenção para que os processos mestres não se tornem o gargalo do sistema o que pode acontecer de duas formas: se as tarefas forem muito pequenas ou se os escravos forem relativamente.

(36) 14. Capítulo 2. Programação Concorrente. rápidos. Um modo de diminuir a possibilidade da existência deste tipo de gargalo é através da escolha da granularidade das tarefas que deve ser adequada o suficiente para que o custo para a realização da tarefa seja predominante sobre o custo de transferência da tarefa e do custo de sincronização juntos. • Modelo Pipeline: este é um modelo no qual o fluxo de dados é repassado por uma sucessão de processos ordenados em cadeia e que cada um realiza alguma tarefa sobre os dados. Cada processo no pipeline pode ser visto como consumidor de uma sequência de dados para o processo que o precede e como produtor de dados para o processo que o sucede. Neste modelo os processos podem estar organizados de forma linear ou dispostos como árvores, grafos ou arrays multidimensionais e o balanceamento de carga fica a cargo da definição da granularidade das tarefas a serem realizadas pelos processos. • Modelos Híbridos: corresponde à aplicação de mais de um modelo para a solução de um problema. Um modelo híbrido pode ser composto pela aplicabilidade de múltiplos modelos de modo hierárquico ou sequencial sobre diferentes etapas do algoritmo paralelo.. 2.6. Ferramentas para o Desenvolvimento de Programas Concorrentes. Uma série de linguagens de programação, bibliotecas e padrões têm sido desenvolvidos para dar suporte à programação concorrente e paralela. Tais ferramentas permitem o tratamento de situações não vistas em programas sequenciais como execução de processos, comunicação e sincronização entre vários processos e threads. Em programas concorrentes a comunicação e a sincronização entre os processos são essenciais. Através da comunicação os diferentes processos podem interagir entre si pela troca de mensagens ou pela modificação de variáveis compartilhadas. A sincronização permite controlar a entrada dos processos às regiões críticas do código impedindo que haja inconsistências no acesso aos dados compartilhados. No paradigma de memória compartilhada o sincronismo dos processos pode ser feito por mecanismos como semáforos, monitores, mutex e variáveis de condição, as quais são definidas como: • Semáforos: neste método proposto por Dijkstra (1965) variáveis podem ser decrementadas ou incrementadas a partir das operações down e up, respectivamente. A execução de uma operação down em um semáforo fará com que seu valor seja decrementado em uma unidade, desde que ele seja maior que zero. Se o valor for zero, o processo ou a thread que tentou realizar o down será colocado para dormir, ou seja, entrará em um estado de espera sem terminar o down. É esta situação que evita a condição de disputa. A operação up faz com que o valor do semáforo seja incrementado em uma unidade. Deste modo, uma.

(37) 2.6. Ferramentas para o Desenvolvimento de Programas Concorrentes. 15. operação up em um semáforo que estava com o valor zero acordará os eventuais processos que estariam dormindo naquele semáforo, o sistema operacional escolherá um deles para finalizar a execução do down e continuar sua computação (TANENBAUM, 2003). • Mutex: é uma variável que pode estar em apenas dois estados: livre ou ocupado. Considerando sua semântica, um Mutex é um semáforo binário. Um processo que queira acessar uma região crítica, deve chamar a primitiva mutex lock. Estando o mutex livre, ou seja, a região crítica estando disponível, o processo poderá entrar. Caso contrário, o processo é bloqueado permanecendo neste estado até que o processo que tem a posse da região crítica saia dela e execute a primitiva mutex unlock. A presença de vários processos bloqueados no mutex faz com que apenas um deles seja escolhido para entrar na região crítica garantindo a exclusão mútua (TANENBAUM; WOODHULL, 2006). • Monitores: neste outro método proposto por Hoare (1974) e Hansen (1973) a exclusão mútua é dada pela propriedade que diz que apenas um processo pode estar ativo em um monitor em um instante de tempo, ou seja, se um processo x chamar um procedimento do monitor e outro processo y estiver ativo dentro do monitor, o processo x será suspenso até o momento que o processo y deixar o monitor. Assim, se não houver outro processo utilizando o monitor, o processo x poderá entrar. A diferença entre semáforos e monitores é que estes são primitivas de alto nível, ao passo que semáforos são primitivas de mais baixo nível, sendo o programador responsável por determinar a ordem de suas operações, o que torna mais fácil a ocorrência de enganos (TANENBAUM; WOODHULL, 2006). • Variáveis de Condição: são variáveis especiais que fornecem métodos que permitem que as threads esperem até que uma condição seja verdadeira. Primitivas como signal e signalAll são utilizadas para notificar uma ou todas as threads, respectivamente, de que a referida condição ocorreu (TANENBAUM; WOODHULL, 2006).. No paradigma de passagem de mensagem a comunicação é feita através de primitivas send para o envio de uma mensagem a outro processo e de primitivas receive para o recebimento de mensagens de outro processo. A comunicação entre primitivas send/receive pode ser do tipo síncrona ou assíncrona, bloqueante ou não bloqueante, com buffer ou sem buffer. Na comunicação síncrona quando um processo executa um send o processo remetente é bloqueado até que a primitiva receive seja executada. Na comunicação assíncrona a primitiva send não necessita esperar até que a primitiva receive seja executada, basta que o dado tenha sido copiado para o buffer. Ao utilizar primitivas send e receive bloqueantes o controle retorna ao processo apenas depois do processamento da primitiva e até que o conteúdo da mensagem esteja seguro. Quando o send e o receive são não bloqueantes o controle retorna ao processo imediatamente após a invocação da primitiva, ou seja, mesmo antes do conteúdo estar seguro..

(38) 16. Capítulo 2. Programação Concorrente. Almasi e Gottlieb (1994), Rauber e Rünger (2013) especificam três modelos teóricos bem difundidos para a criação e finalização de processos ou threads em programas paralelos, são eles: • Fork/Join: é um conceito que remete à criação de processos ou threads. A partir deste conceito uma thread t qualquer cria threads filhas utilizando a declaração de uma primitiva fork. Tais threads executarão partes de um dado programa de modo paralelo. A thread pai, por sua vez, pode também executar uma computação e então esperar, através do uso de uma primitiva join, até que as threads filhas terminem. • Cobegin/Coend: assim como o fork/join o cobegin/coend é utilizado para a criação e finalização de processos ou threads. Estas primitivas, que também são conhecidas como parbegin/parend, permitem que instruções sejam executadas concorrentemente. Apesar de todos os comandos entre um cobegin e um coend serem executados concorrentemente, seu modo de funcionamento totalmente aninhado faz com que a execução do referido cobegin/coend só termine quanto todos os processos em seu interior terminarem suas execuções. Isto limita, de certa maneira, o paralelismo e o torna menos flexível, entretanto a utilização destas primitivas deixa o código bem estruturado, aumentando sua legibilidade. • ForAll: também conhecido como DoAll, possui um uso mais restrito a sistemas numéricos devido à sua característica de paralelizar instruções que se encontram dentro de laços;. 2.6.1. Programação Concorrente em Java. Java é uma linguagem de programação de alto nível amplamente usada e que suporta o desenvolvimento de programas concorrentes nos paradigmas de memória compartilhada e passagem de mensagem. Antes de sua versão 5.0 ela já fornecia pacotes que permitiam a desenvolvimento de aplicações concorrentes através de primitivas mais simples, entretanto, com a versão 5.0 a adição de um novo pacote introduziu uma séria de primitivas que facilitaram e ampliaram as possibilidades para a construção de programas concorrentes com a linguagem. A atual versão da ferramenta ValiPar desenvolvida no ICMC/USP está sendo instanciada para esta linguagem de programação e os benchmarks desenvolvidos neste trabalho de mestrado serviram para validar a ferramenta bem como os modelos e critérios por ela implementados. Logo os programas foram desenvolvidos em Java abrangendo várias das primitivas que a linguagem oferece para criação, inicialização e finalização, sincronismo e comunicação entre processos e threads.. 2.6.2. Criação, Inicialização e Finalização de Threads em Java. Todo programa em Java possui pelo menos uma thread em execução conhecida como thread principal ou main. Ela é responsável por executar o método main() da classe que é.

(39) 2.6. Ferramentas para o Desenvolvimento de Programas Concorrentes. 17. dada como argumento de entrada para a Java Virtual Machine (JVM), entretanto, para que se desenvolva um programa concorrente, pelo menos uma nova thread deve ser criada de modo explícito (RAUBER; RÜNGER, 2013). Existem duas formas de se criar threads em Java: estendendo a classe Thread e sobrescrevendo o método run() ou então através da construção de uma classe que implemente a Interface Runnable. Posteriormente se cria um objeto da classe Thread passando um objeto Runnable como parâmetro. Caso se opte por utilizar a primeira maneira, não será possível estender outra classe, pois o Java não permite herança múltipla (GONZÁLEZ, 2012). Para que uma thread criada seja inicializada e comece realmente a executar suas tarefas é necessário chamar o método start(). Neste momento pelo menos duas threads estão em execução paralelamente: a thread criada e a thread original main. O processo de finalização de uma thread pode ocorrer quando ela executa uma instrução de retorno, quando ela executa a última instrução presente no corpo do método run() ou quando uma exceção é lançada (OAKS; WONG, 2004).. 2.6.3. Sincronização de Threads Java. 2.6.3.1 Blocos e Métodos Synchronized As threads em Java acessam um espaço de endereço compartilhado, e por isso são disponibilizados blocos e métodos synchronized para que se garanta a exclusão mútua de uma região crítica (ORACLE, 2014). Java implementa a sincronização atribuindo a cada objeto uma variável mutex implícita. Assim, blocos e métodos synchronized em Java funcionam através de bloqueios implícitos sobre uma variável mutex. O modo de funcionamento de um método synchronized pode ser resumido da seguinte maneira: quando uma thread x quer acessar um método synchronized, ela tenta implicitamente bloquear a variável mutex do objeto. Se for verificado que a variável mutex do referido objetivo já foi bloqueada por outra thread y, a thread atual (x) é bloqueada. A thread x só sairá do estado bloqueado e irá para o estado de pronto quando a thread y liberar a variável mutex, ou seja, deixar de usar o método synchronized. A thread y ao liberar a variável mutex permite que a thread x a bloqueie tendo acesso ao método synchronized. Quando a thread x sai do método synchronized, ela implicitamente libera a variável mutex do objeto, fazendo com que outra thread possa adquirir o bloqueio e entrar no método (RAUBER; RÜNGER, 2013). 2.6.3.2 Wait, Notify e NotifyAll A primitiva wait() é um mecanismo de espera utilizado para ajudar na comunicação entre threads. Com ela uma thread que necessita que uma determinada condição ocorra pode esperar até que outra thread crie esta condição e a notifique através da primtiva notify(). Várias threads podem estar esperando para que esta condição ocorra. Desse modo, ao executar o notify,.

(40) 18. Capítulo 2. Programação Concorrente. apenas uma das threads na espera poderá continuar sua execução. A primitiva notifyAll() pode ser usada para acordar todas as threads que estavam na espera (OAKS; WONG, 2004; RAUBER; RÜNGER, 2013; ORACLE, 2014). 2.6.3.3 Semáforo Um semáforo pode ser construído utilizando-se primitivas wait(), notify() e métodos synchronized, como visto em Garg (2005). Antes do Java 5.0 esta era a única maneira; entretanto, com o Java 5.0 e a adição do pacote java.util.concurrent, a classe Semaphore foi incorporada permitindo criar semáforos facilmente. Um objeto semáforo mantém internamente um contador que conta o número de permissões. Alguns dos principais métodos dessa classe são (CALVERT; DONAHOO, 2008; ORACLE, 2014):. void acquire() void release() boolean tryAcquire() boolean tryAcquire(int permits, long timeout,TimeUnit unit) O método acquire() é equivalente a um down, sendo utilizado para adquirir uma das permissões do semáforo, e caso ele tenha chegado em seu limite inferior e uma thread executar o método acquire() ela será bloqueada até que uma permissão esteja disponível. O método release() é equivalente a um up, sendo responsável por liberar uma permissão, devolvendo-a ao semáforo. O método tryAcquire() permite adquirir uma permissão deste semáforo, apenas se estiver disponível no momento da invocação. Ao adquirir uma permissão que está disponível, o método retorna o valor true e reduz o número de permissões em uma unidade. No caso da permissão não estar disponível o método retorna o valor false. O método tryAcquire(int permits, long timeout,TimeUnit unit) adiciona parâmetros que especificam um número de permissões a serem adquiridas a partir deste semáforo (permits), o tempo máximo de espera para adquirir as permissões (timeout) e a unidade de tempo do argumento timeout (unit). Este método tem como retorno o valor true se todas as permissões forem adquiridas a tempo, e false se o tempo de espera acabar antes de todas as permissões serem adquiridas (CALVERT; DONAHOO, 2008; ORACLE, 2014)..

Referências

Documentos relacionados

Resultados (8) – Agressividade (Professor) tempo 2 1 Estimated Margi n al Means 1,6 1,5 1,4 1,3 controlo intervenção intercont. Estimated Marginal Means

Este documento pode ser acessado no endereço eletrônico http://www.tst.jus.br/validador sob código 1003F3D2A712044F47... Firmado por assinatura digital em 09/12/2020 pelo

General: Knowing the conceptual and methodological foundations of the main projective methods; Identify and understand the operational concepts of projection and distress; Identify

II - os docentes efetivos, com regime de trabalho de 20 (vinte) horas semanais, terão sua carga horária alocada, preferencialmente, para ministrar aulas, sendo o mínimo de 8 (oito)

Projetil encamisado por uma camisa pré-sulcada de latão endurecido, contendo chumbo não endurecido no seu interior, dotado de uma ponta oca. HYDRA SHOCK centro, que

Quando um canal não mantém o seu eixo-árvore master, no arranque do CNC e depois de um reset, o canal aceita como eixo-árvore master o primeiro eixo-árvore definido nos parâmetros

15, estão representados os teores médios de safrol contido em óleo essencial obtido, no decorrer do progresso de extração, da biomassa aérea de pimenta longa procedente de cultivos

(a) deve terminar com return 0 para indicar que não ocorreu um erro (b) deve terminar com return 0 para indicar que ocorreu um erro (c) não pode conter a instrução return. As fases