Curso de Engenharia da Computação
GRADE COMPUTACIONAL - DESENVOLVIMENTO DE UMA
APLICAÇÃO PARA SOLUÇÃO DE UM PROBLEMA
COMPUTACIONAL INTENSO
Felipe Augusto Ghisi
Itatiba – São Paulo – Brasil
Dezembro de 2006
ii
Curso de Engenharia da Computação
GRADE COMPUTACIONAL - DESENVOLVIMENTO DE UMA
APLICAÇÃO PARA SOLUÇÃO DE UM PROBLEMA
COMPUTACIONAL INTENSO
Felipe Augusto Ghisi
Monografia apresentada à disciplina Trabalho de
Conclusão de Curso, do Curso de Engenharia da
Computação da Universidade São Francisco, sob a
orientação do Prof. Dr. André Leon S. Gradvohl, como
exigência parcial para conclusão do curso de graduação.
Orientador: Prof. Dr. André Leon S. Gradvohl
Itatiba – São Paulo – Brasil
Dezembro de 2006
iii
Grade computacional – Desenvolvimento de uma aplicação para
solução de um problema computacional intenso
Felipe Augusto Ghisi
Monografia defendida e aprovada em 12 de dezembro de 2006 pela Banca
Examinadora assim constituída:
Prof. Dr. André Leon S. Gradvohl (Orientador)
USF – Universidade São Francisco – Itatiba – SP.
Prof. Dr. Mauricio Fabbri (Membro Interno)
USF – Universidade São Francisco – Itatiba – SP.
Prof. Luiz Roberto Wenzel (Membro Interno)
USF – Universidade São Francisco – Itatiba – SP.
iv
Agradecimentos
Durante o desenvolvimento deste trabalho, recebi a ajuda direta ou indireta de várias pessoas.
Gostaria de agradecer primeiramente a minha família, pelo apoio e compreensão, não somente
neste período, mas durante toda minha vida.
Agradeço também ao Professor Dr. André Leon S. Gradvohl, meu orientador, pela
experiência que me passou durante o desenvolvimento deste trabalho.
Por fim, as amizades que foram criadas durante esses anos todos de graduação.
Eu agradeço fraternalmente a todos.
v
Sumário
Lista de Siglas..................................................................................................................... vii
Lista de Figuras................................................................................................................. viii
Lista de Tabelas .................................................................................................................. ix
Resumo ................................................................................................................................. x
Abstract ................................................................................................................................ x
1
Introdução ..................................................................................................................... 1
1.1 Visão geral................................................................................................................ 1
1.2 Projeto ...................................................................................................................... 1
2
Computação em Grade – Grid Computing.................................................................... 3
2.1 Aplicações das grades ............................................................................................... 5
2.2 Variações das grades ................................................................................................. 7
2.2.1 Grade Computacional (Grid Computing) ............................................................ 7
2.2.2 Grade de dados/armazenamento (Data Grid) ...................................................... 7
2.2.3 Grade de Serviços (Service Grid)........................................................................ 8
2.2.4 Serviços compostos e derivados.......................................................................... 8
2.3 Computação de Alto Desempenho x Computação Oportunista .................................. 9
2.4 Trabalhos relacionados............................................................................................ 10
2.4.1 Globus .............................................................................................................. 10
2.4.2 Legion .............................................................................................................. 11
2.4.3 Condor ............................................................................................................. 12
2.4.4 MyGrid............................................................................................................. 13
2.4.4.1 OurGrid...................................................................................................... 14
2.4.5 SETI@home..................................................................................................... 14
2.4.6 BOINC ............................................................................................................. 16
3
Aplicação Desenvolvida............................................................................................... 20
3.1 Arquitetura da aplicação.......................................................................................... 20
3.2 Tecnologia utilizada ................................................................................................ 21
3.3 Resultados............................................................................................................... 22
4
Conclusão..................................................................................................................... 25
4.1 Extensões................................................................................................................ 25
Apêndice 1 – Código fonte da classe Mestre.java ............................................................. 26
Apêndice 2 – Código fonte da classe Matriz.Matriz.java ................................................. 29
Apêndice 3 – Código fonte da classe Matriz.MatrizRemoto.java .................................... 33
vi
Apêndice 4 – Código fonte da classe Matriz.MatrizTarefa.java ...................................... 35
Apêndice 5 – Código fonte da classe TrabalhadorRemoto. PoolEscravos.java............... 37
Apêndice 6 – Código fonte da classe TrabalhadorRemoto. Trabalhador.java................ 41
Apêndice 7 – Código fonte da classe TrabalhadorRemoto. TrabalhadorProxy.java...... 42
Apêndice 8 – Código fonte da classe TrabalhadorRemoto. TrabalhadorRemoto.java... 45
Apêndice 9 – Código fonte da classe TrabalhadorRemoto. VetorTrabalho.java ............ 47
Referências Bibliográficas ................................................................................................. 48
vii
Lista de Siglas
BoT
Bag of Tasks
JVM
Java Virtual Machine
RMI
Remote Method Invocation
viii
Lista de Figuras
FIGURA 1 - TEMPO DE EXECUÇÃO EM FUNÇÃO DO TAMANHO DA MATRIZ ................................ 23
ix
Lista de Tabelas
TABELA 1 - COMPUTAÇÃO DE ALTO DESEMPENHO E COMPUTAÇÃO OPORTUNISTA .................. 10
TABELA 2 – TEMPOS DE EXECUÇÃO EM MS ............................................................................ 23
x
Resumo
Poder computacional é cada dia mais necessário, em diversas áreas do conhecimento
humano. Tanto em pesquisas científicas como na indústria, muitos problemas demandam
grande capacidade de processamento, a um custo muito elevado ou precisando de um tempo
de resolução que muitas vezes não é aceitável. As grades computacionais surgem como
alternativa para estes tipos de problemas, provendo aos usuários acesso simplificado ao poder
computacional de várias máquinas combinadas.
Esta monografia tem como objetivo estudar as grades computacionais, e implementar
uma aplicação que demanda de grande poder computacional, uma multiplicação de matrizes,
avaliando os tempos de execução obtidos rodando em grade e sua viabilidade.
PALAVRAS-CHAVE: grade computacional, computação distribuída.
Abstract
Computational power is even more and more necessary in many areas of human
knowledge. In scientific research, as well as in industry, many problems require large
processing power, with very high costs or either resolution times that are unacceptable in
some cases. The computing grid comes as an alternative for this kind of problems, providing
users with simplified access to the computational power of various combined machines.
The objective of this monograph is to study these computing grids, and implements an
application that demands high computational power, multiplication of a large matrix,
evaluating the execution times obtained through the grid and its viability.
KEY WORDS: grid computing, distributed computing
1
1 INTRODUÇÃO
1.1 Visão geral
O aparecimento de redes de computadores permitiu a utilização de um novo paradigma
computacional que se mostrou, com o passar do tempo, extremamente poderoso. Este novo
paradigma é a possibilidade de distribuição do processamento em computadores diferentes.
Mais do que a simples subdivisão de tarefas, permite a repartição e a especialização das
tarefas computacionais conforme a natureza da função de cada computador.
Um sistema de processamento distribuído ou paralelo é um sistema que interliga vários
nós de processamento (computadores individuais, não necessariamente homogêneos) de
maneira que um processo de grande consumo seja executado no nó "mais disponível", ou
mesmo subdividido por vários nós. Obtém-se, portanto, ganhos óbvios nestas soluções: uma
tarefa qualquer, se divisível em várias subtarefas pode ser realizada em paralelo.
Assim, a computação distribuída consiste em adicionar o poder computacional de
diversos computadores interligados por uma rede de computadores ou mais de um
processador
trabalhando
em
conjunto
no
mesmo
computador,
para
processar
colaborativamente determinada tarefa de forma coerente e transparente, ou seja, como se
apenas um único e centralizado computador estivesse executando a tarefa [17, 18, 19, 20].
1.2 Projeto
Neste projeto foi desenvolvida uma aplicação que demanda intenso processamento: a
multiplicação de matrizes. A solução desse problema é de ordem O(N3), ou seja, é um
problema de grande complexidade computacional e exige grande poder de processamento.
Portanto, é uma aplicação típica para ser executada na forma de uma aplicação distribuída, ou
seja, dividida em vários computadores. A multiplicação de matrizes é bastante utilizada em
cálculos computacionais e científicos, por isso é importante realizá-la de uma maneira rápida
e eficiente.
2
A organização do restante do texto é a seguinte: o Capítulo 2 descreverá a Computação
em Grade e alguns dos sistemas existentes. O Capítulo 3 apresentará a aplicação
desenvolvida, descrevendo suas características, arquitetura e implementação, além dos testes
realizados. Finalmente, o Capítulo 4 as conclusões e possibilidades de trabalho futuro.
3
2 COMPUTAÇÃO EM GRADE – GRID COMPUTING
Poder computacional é uma necessidade crescente em diversas das atividades humanas.
Diversas áreas do conhecimento científico, como biologia, física e química dependem de
processamento intenso em atividades como: simulações, otimizações e mineração de dados. A
indústria também faz uso intensivo de computação em aplicações tão diversas como
simulações econômicas e mercadológicas, estudos de viabilidade de prospecção de petróleo e
geração de efeitos especiais na pós-produção de filmes. Já é difícil imaginar como algumas
atividades seriam realizadas sem o uso de computadores. Porém, a cada avanço obtido, fruto
do uso intensivo de computação, nos leva a necessitar de ainda mais computação para
podermos atingir o próximo nível de conhecimento. É um círculo virtuoso que melhora a
qualidade do conhecimento humano e, ao mesmo tempo, testa os limites do que é possível
realizar computacionalmente.
Historicamente, as atividades intensivas em computação eram realizadas em poderosos
computadores dotados de grande capacidade de processamento. Tais máquinas eram
extremamente caras e portanto, sua posse estava restrita a algumas universidades
privilegiadas, grandes corporações e poucos centros de pesquisa. Apesar do formidável
avanço nas tecnologias de tais máquinas e também da queda em seu preço, tal situação
perdura até os dias de hoje. Máquinas altamente especializadas de alto desempenho não raro
possuem custos que podem quebrar a barreira de alguns milhões de dólares. Assim, apenas
instituições que possuem grande dependência de tais equipamentos aliada a significativa
quantidade de recursos financeiros podem adquirir esse tipo de máquina. O alto preço
praticamente exclui usuários que possuem necessidades esporádicas de grandes quantidades
de computação.
Apesar do alto preço das máquinas de alto desempenho, estas não estão imunes ao
destino comum de todos os computadores: a obsolescência. Independente do preço, cedo ou
tarde todas as máquinas tem a sua utilidade reduzida, uma vez que os avanços do
conhecimento demandam maior poder de computação. Uma solução óbvia e muito adotada
para resolver tal problema é simplesmente trocar os equipamentos periodicamente. Porém, os
constantes avanços nas tecnologias de redes de computadores tornaram viável outra solução
mais barata e prática: a interligação de máquinas de diferentes laboratórios através de redes de
grande área – no final de 1995, nas vésperas da conferência Supercomputing '95, foi
4
estabelecida a I-WAY, uma infra-estrutura experimental conectando supercomputadores e
equipamentos de armazenagem de 17 laboratórios americanos em um sistema único que podia
ser acessado de qualquer uma das instituições. Nascia o primeiro sistema de Computação em
Grade.
Um sistema de Computação em Grade (Grid Computing) [1] pode ser definido de
maneira bem abrangente como uma infra-estrutura de software capaz de interligar e gerenciar
diversos recursos computacionais, possivelmente distribuídos por uma grande área geográfica,
de maneira a oferecer ao usuário acesso transparente a tais recursos, independente da
localização dos mesmos. Na maioria dos casos, o principal recurso das grades é a capacidade
de processamento, porém alguns sistemas incluem uma ampla gama de recursos como:
dispositivos de armazenamento de grande capacidade, instrumentos científicos e até
componentes de software, como bancos de dados. A origem do termo Grid Computing deriva
de uma analogia com a rede elétrica (Power Grid), e remete o objetivo de tornar o uso de
recursos computacionais distribuídos tão simples quanto ligar um aparelho na rede elétrica.
Apesar da falta de consenso sobre as características de um sistema de Computação em
Grade, podemos listar uma série de aspectos comuns a diversos desses sistemas. São eles:
•
não substituem sistemas operacionais: ao contrário de sistemas operacionais
distribuídos, nenhum dos sistemas de Computação em Grade existentes substitui os
sistemas operacionais das máquinas que compõem a Grade. Os sistemas de
Computação em Grade podem utilizar serviços do sistema operacional, porém são
estruturados como um middleware que provê serviços para os usuários e aplicações da
Grade. Alguns sistemas ainda permitem que seus módulos sejam executados com
poucos ou nenhuns privilégios de sistema, ou seja, não requerem privilégios
administrativos;
•
podem integrar recursos distribuídos e descentralizados: a maioria dos sistemas de
Computação em Grade é capaz de integrar recursos dispersos por múltiplos domínios
administrativos1 e conectados por redes de grande área. Essa característica separa os
sistemas de Computação em Grade dos sistemas de Computação em Aglomerados
1
Domínio administrativo: conjunto de máquinas que estão sujeitas a um determinado conjunto de políticas e
restrições estabelecidas por alguma autoridade local. Por exemplo, as máquinas de um laboratório sob controle
de um administrador constituem um domínio administrativo.
5
(Cluster Computing), uma vez que estes últimos normalmente são capazes de integrar
recursos em apenas um domínio administrativo. Alguns sistemas como o Condor [1]
evoluíram de gerenciadores de aglomerado para sistemas de Computação em Grade;
•
podem ser utilizados por diversas aplicações: a maioria dos sistemas de
Computação em Grade provê serviços que podem ser utilizados por diversas
aplicações, caracterizando uma arquitetura reutilizável. Alguns sistemas inclusive
prevêem suporte a diferentes classes de aplicações, como aplicações seqüenciais e
paralelas;
•
podem incluir várias plataformas de hardware e software: a maioria dos sistemas
de Computação em Grade pode integrar recursos heterogêneos, compostos por
diversas plataformas de hardware e software. Para tal, entretanto, o sistema de
Computação em Grade deve incluir mecanismos que lidem com a diversidade de tais
plataformas; apesar de poderem utilizar algumas interfaces padronizadas presentes na
maioria dos sistemas operacionais, algumas informações só podem ser obtidas através
de mecanismos específicos de cada plataforma;
•
adaptabilidade às políticas locais: apesar de integrarem recursos dispersos por vários
domínios administrativos, os sistemas de Computação em Grade devem se adaptar às
políticas e restrições de uso de cada um destes domínios. Por exemplo, é comum o
cenário onde o administrador do domínio, apesar de compartilhar os recursos com
outros domínios, deseja priorizar os usuários locais. Proprietários de estações de
trabalho, por exemplo, não aceitam que o desempenho de suas aplicações sofra devido
às aplicações da Grade que executam em seus recursos.
2.1 Aplicações das grades
Além de permitir um melhor aproveitamento dos recursos computacionais, a Grade
poderá prover novas formas de interação entre os usuários e suas aplicações. Por exemplo,
uma possibilidade de uso das grades são os chamados laboratórios virtuais, que permitem o
acesso remoto a instrumentos científicos altamente especializados e caros. Por exemplo, um
cientista no laboratório A poderá manipular remotamente um telescópio presente no
laboratório B, e receber as imagens captadas através da rede. Opcionalmente, ele poderá, de
maneira transparente, armazenar as imagens nos equipamentos de grande capacidade do
instituto C e, caso necessário, processar as imagens nos computadores disponíveis nas três
6
instituições. Também é possível vislumbrar experimentos colaborativos, onde diversos
cientistas espalhados por várias instituições colaboram para realizar experimentos e
simulações. Na indústria, além da possibilidade de se utilizar a capacidade ociosa de centenas
de estações de trabalho já existentes, as grades poderão permitir novas formas de colaboração
entre os funcionários espalhados por diversas filiais, por exemplo.
As aplicações em ambientes de grades podem ser categorizadas em cinco classes
principais:
•
Supercomputação Distribuída: é o foco deste trabalho, o qual engloba aplicações
com necessidade de grande potencial de processamento, podendo este ser alcançado
com a paralelização na execução de tarefas. Por exemplo, processamentos genéticos,
gráficos, metereológicos, neurocientíficos e outros;
•
Grande fluxo de dados: são aplicações que possuem fluxos de informação oriundos
de várias fontes, inviabilizando a concentração em um único computador. Estudos
paramétricos são exemplos desta categoria de aplicações;
•
On-demand: são aplicações que utilizam alguns instrumentos e recursos mais
específicos em determinados momentos, por exemplo, captação metereológica,
observação espacial e instrumentação inteligente;
•
Grande quantidade de dados: aplicações que utilizam grande quantidade de
informação, armazenadas em vários meios secundários, além de um número alto de
processamento. Por exemplo, mineração de dados (data mining);
•
Colaborativo: aplicações que envolvem a participação síncrona ou assíncrona de
indivíduos geograficamente distantes, tal como desenvolvimento colaborativo de
peças industriais.
Há dezenas de motivos pelos quais justificam o uso de aplicações voltada às grades de
computadores, entre eles estão:
•
A exploração da natureza distribuída inerente de algumas aplicações;
•
Diminuição do tempo de resposta de aplicações robustas;
•
Possibilidade de executar aplicações que ultrapassam as capacidades de um sistema de
arquitetura simples;
7
•
Exploração da semelhança de um recurso com função específica em uma grade, com
componentes de uma aplicação, ou seja, permite trabalhar com o paradigma orientado
a componentes, facilitando o reuso de funções e aplicações.
2.2 Variações das grades
Com o desenvolvimento dos sistemas de Computação em Grade, foi proposto uma
classificação [1] que categoriza os sistemas de Computação em Grade de acordo com a
atividade principal a qual se destinam. Algumas dessas categorias serão descritas a seguir.
2.2.1 Grade Computacional (Grid Computing)
São voltados a prover meios seguros para execução de tarefas de alguma aplicação nos
recursos distribuídos individualmente ou coletivamente. É desejável também que esses
serviços forneçam meios para monitorar e controlar as execuções das tarefas, além de permitir
o controle da alocação e reserva dos recursos disponíveis para execução das mesmas. Podem
ser focados em computação de alto desempenho e computação com grande saída de
informação (high throughput).
Outro fator importante nesse tipo de serviço é a necessidade de funções especiais
capazes de qualificar o software e o hardware dos recursos segundo suas capacidades e
permitir monitorar constantemente a disponibilidade dos mesmos com o passar do tempo.
Grades voltadas a esses serviços assemelham-se com os aglomerados pelo intuito de
ganhar desempenho na execução de programas e tarefas em geral.
2.2.2 Grade de dados/armazenamento (Data Grid)
Concentram-se em disponibilizar mecanismos seguros de acesso a repositórios de
dados e a seus gerenciadores. Para a disponibilização de armazenamentos expansíveis e
acesso aos dados consistentes, estes devem ser replicados em vários recursos de maneira a
criar uma ilusão da existência de um armazenamento em massa. O processamento desses
dados são normalmente encarregados aos serviços computacionais da grade, sendo esta
8
combinação de serviços chamada de data grids. Por outro lado, estes serviços podem apenas
representar mecanismos para colocar e retirar arquivos de meios de armazenamentos. Nesta
abordagem é essencial a existência de formas que agilizam o processo de carga e descarga das
informações, ou seja, é necessário a existência de mecanismos que permitam fazer acessos
aleatório a segmentos de arquivos em diferentes recursos, assim como mecanismos que
permitam avaliar a disponibilidade de espaço, largura de banda de rede e de disco e CPU
(Unidade de Processamento Central) dos recursos para melhor atender aos pedidos.
2.2.3 Grade de Serviços (Service Grid)
Visam o gerenciamento de aplicações e provêem acesso a softwares e bibliotecas
transparentemente. É esperado o uso de algumas tecnologias, tais como web services, para a
obtenção e implementação de serviços de aplicação, os quais são feitos baseados em serviços
de dados e serviços computacionais.
2.2.4 Serviços compostos e derivados
Baseados nos serviços que oferecem capacidades de processamento e armazenamento,
podem-se criar variações com finalidades mais específicas e restritas. É possível destacar
quatro deles:
•
Serviços de repositório de códigos: são especializações de serviços de
armazenamento que necessitam de especiais controles de versões de códigos fontes e
códigos objetos. Como exemplo desses serviços, tem-se os sistemas controladores de
versões (CVS).
•
Serviços de catálogo: representam especializações dos serviços de armazenamento
que necessitam de buscas indexadas e grande quantidade de atualizações, por
exemplo, base de dados relacional.
•
Serviços de informação: representam serviços que extraem e apresentam algum tipo
de informação de um determinado domínio de problema, sendo implementado usando
serviços de dados, serviços computacionais e/ou serviços de aplicação. As
diferenciações existentes estão relacionadas com a maneira com que as informações
são representadas, armazenadas, acessadas, compartilhadas e mantidas.
9
•
Serviços de conhecimento: concentram-se na definição da forma com que um
conhecimento é adquirido, usado, retornado, publicado e mantido para ajudar usuários
a alcançar seus objetivos particulares. Conhecimento, neste caso, é entendido como
informações aplicadas em um objetivo, em uma solução de um problema ou para a
tomada de uma decisão.
Devido às características da pesquisa realizada no contexto dessa monografia, o restante
do texto tratará apenas de Grades Computacionais. As demais categorias se encontram além
do escopo desse trabalho.
2.3 Computação de Alto Desempenho x Computação Oportunista
Em termos do aproveitamento de recursos computacionais, podemos ter dois tipos de
computação [1]. O primeiro tipo é a alocação exclusiva de um conjunto de recursos por
longos períodos de tempo, utilizando de forma intensiva os recursos computacionais para uma
dada tarefa. Este tipo de processamento denomina-se computação de alto desempenho.
Recursos disponibilizados nesta modalidade são também denominados aglomerados (clusters)
computacionais.
No lado oposto temos a computação oportunista, onde tentamos aproveitar intervalos de
ociosidade de equipamentos para realizar processamento externo. Não se exige 100% de
disponibilidade dos equipamentos. O fator primordial não é o tempo de processamento, mas
sim um melhor aproveitamento de recursos. Uma grade pode conter dentro de seus limites
tanto recursos destinados à computação de alto desempenho (aglomerados) como máquinas
utilizadas para computação oportunista. Cabe a infra-estrutura da Grade fazer a alocação das
tarefas em função das características dos equipamentos. A Tabela 1 apresenta um quadro
comparativo, destacando as principais diferenças entre computação de alto desempenho e
computação oportunista.
10
Tabela 1 - Computação de alto desempenho e computação oportunista
Computação de alto desempenho
Ambiente homogêneo em termos de
hardware e Sistema Operacional (aglomerado
de computadores).
Linguagens ou bibliotecas com recursos que
facilitem a comunicação entre processos
distribuídos.
Possui um custo inicial para a compra e
manutenção de equipamentos dedicados.
Este custo pode compensar se comparado ao
custo de aquisição de um super computador.
Não necessita de uma infra-estrutura para
gerenciamento, os programas podem utilizar
simplesmente bibliotecas paralelas para troca
de mensagens.
Uso intensivo principalmente de CPU.
Computação oportunista
Ambiente heterogêneo.
Linguagens que possam ser utilizadas em
ambientes heterogêneos.
Custo baixo de instalação, pois aproveita os
equipamentos já existentes.
Necessita de um gerenciador para catalogar
os recursos disponíveis, monitorar sua
ocupação e distribuir as tarefas.
Compartilha qualquer recurso computacional
como
arquivos,
capacidade
de
armazenamento, bancos de dados e CPU.
2.4 Trabalhos relacionados
Com a disseminação da Computação em Grade, surgiram diversos sistemas
desenvolvidos pela indústria e pela comunidade acadêmica. Nesta seção apresentaremos
alguns dos sistemas de Computação em Grade existentes. E importante salientar que os
sistemas aqui apresentados representam apenas uma pequena fração dos sistemas existentes.
2.4.1 Globus
O Globus [22] e atualmente o projeto de maior impacto na área de Computação em
Grade. Suas origens remontam a I-WAY, uma iniciativa de criar uma infra-estrutura de Grade
temporária composta por 11 redes de alta velocidade e 17 institutos de pesquisa. Tal infraestrutura foi concebida para funcionar por apenas algumas semanas, nas vésperas do
congresso Supercomputing '95. O sucesso da iniciativa incentivou a criação e o
desenvolvimento do Globus.
11
O projeto Globus atualmente é uma iniciativa que envolve diversas instituições de
pesquisa e conta com o apoio de grandes empresas, como a IBM e a Microsoft. As principais
instituições de pesquisa envolvidas incluem o Laboratório Nacional de Argonne (ANL),
Universidade de Chicago, Universidade do Sul da Califórnia (USC) e o Laboratório de
Computação de Alto Desempenho da Universidade do Norte de Illinois, todos esses situados
nos Estados Unidos, alem da Universidade de Edimburgo.
O sistema de Computação em Grade desenvolvido no contexto do projeto Globus é
denominado Globus Toolkit e provê uma serie de funcionalidades que permitem a
implantação de sistemas de Computação em Grade assim como o desenvolvimento de
aplicações para tais sistemas. A abordagem de caixa de ferramentas (toolkit) permite a
personalização das grades e aplicações, permitindo a criação incremental de aplicações que a
cada nova versão utilizem mais recursos da Grade. Por exemplo, pode-se inicialmente utilizar
Globus apenas para coordenar a execução de aplicações sem que estas sejam alteradas, e
posteriormente e possível modificá-las para que usem serviços da Grade, como por exemplo
transferência de arquivos.
2.4.2 Legion
O Projeto Legion [1], desenvolvido na universidade da Virginia, objetivou construir
um Sistema de Metacomputação, ou seja, um ambiente que integrasse diversos recursos
computacionais espalhados, provendo a usuários e desenvolvedores de aplicações a ilusão de
que estivessem utilizando um único e poderoso computador. Sistemas de Metacomputação e
de Computação em Grade possuam semelhanças e acabaram convergindo, sendo o termo
Computação em Grade o mais atual. Apesar de colaborações com outras universidades e
centros de pesquisa, a maioria da pesquisa e desenvolvimento de Legion foi feita na
Universidade de Virginia, e os principais pesquisadores envolvidos no projeto foram Andrew
Grimshaw, Bill Wulf, Jim French, Marty Humphrey e Anand Natrajan. O projeto Legion foi
iniciado em 1993, porem o desenvolvimento do sistema começou de fato apenas em 1996, e a
primeira implantação se deu em 1997. Tal intervalo entre o início do projeto e o início da
implementação foi devido a adoção de uma extensa fase de análise de requisitos e projeto do
sistema. Tal fase de análise é devida em parte ao princípio de arquitetura adotado por Legion:
construir uma fundação básica sobre a qual os serviços de maior nível da Grade pudessem ser
construídos.
12
A característica marcante do sistema Legion e sua arquitetura orientada a objetos:
todas as entidades do sistema, tais como computadores, dispositivos de armazenamento,
aplicações e serviços da Grade são representadas por objetos. Tais objetos fazem uso da
fundação básica provida por Legion, denominada camada de objetos núcleo. Essa camada
provê serviços básicos que todo objeto necessita, como chamada de métodos e propagação de
exceções, e por sua vez, faz uso de serviços de camadas inferiores, como transporte de
mensagens. Tal arquitetura provê um modelo elegante e flexível de desenvolvimento do
sistema de Grade, porem foi responsável pela relativa demora em produzir um sistema
funcional: antes que qualquer serviço de alto nível pudesse ser desenvolvido, era necessário
desenvolver completamente a camada que servia de substrato.
O projeto Legion em si foi encerrado: em 2001, uma companhia posteriormente
nomeada Avaki [1] adquiriu os direitos legais de Legion da Universidade de Virginia. Assim,
o sistema Legion foi renomeado para Avaki, o qual mantém a arquitetura e alguns serviços de
Legion, porem teve alguns de seus serviços e funcionalidades removidos, uma vez que Avaki
e voltado para aplicações comerciais. Alguns dos principais arquitetos de Legion fazem parte
da companhia.
2.4.3 Condor
O sistema Condor [4, 21], desenvolvido na Universidade de Wisconsin em Madison,
possibilita a integração de diversos computadores em aglomerados, de maneira a permitir a
utilização eficaz dos recursos computacionais de tais maquinas. Como e reconhecido
amplamente, grande parte da capacidade de processamento dos computadores, especialmente
estações de trabalho, permanece inutilizada durante grande parte do tempo. Assim, o principal
objetivo de Condor é utilizar tal capacidade para executar programas, preservando o
proprietário do recurso de perdas de desempenho.
Condor é o sistema mais antigo aqui apresentado, tendo sua origem em 1988 como
derivado de um sistema ainda mais antigo, RemoteUnix, que possibilitava a integração e o
uso remoto de estações de trabalho. O sistema está em produção há vários anos, com
instalações tanto no ambiente acadêmico como na indústria.
Originalmente, o usuário alvo de Condor é aquele que possui necessidade de grandes
quantidades de computação ao longo de um grande espaço de tempo, como dias, semanas ou
meses. Normalmente tais usuários possuem necessidades de processamento que superam em
13
muito a capacidade disponível. Assim, normalmente é a capacidade disponível de
processamento que define o quanto da computação e feita. Tal categoria de uso e denominada
High Throughput Computing. Para esses usuários, qualquer recurso adicional é de grande
valia. Uma vez que tradicionalmente as computações intensivas são feitas em recursos caros e
dedicados, torna-se atraente a idéia de utilizar recursos compartilhados, baratos e já existentes
em grande quantidade na instituição.
2.4.4 MyGrid
MyGrid [1] objetiva construir um ambiente simplificado para a execução de aplicações
sobre recursos computacionais distribuídos. O principal objetivo de MyGrid é simplificar ao
máximo o processo de implantação da Grade, permitindo que qualquer usuário tome a
iniciativa de instalar uma grade computacional com os recursos que dispõe. Em MyGrid,
normalmente é o próprio usuário que deseja executar aplicações o responsável por implantar o
sistema, o que contrasta com as demais iniciativas de grade já apresentadas, onde a
implantação é tipicamente feita por um administrador. MyGrid pode ser instalado facilmente
em quaisquer recursos que o usuário tenha acesso, não requerendo nenhum privilégio de
administrador. MyGrid é desenvolvido na Universidade Federal de Campina Grande, e o
principal pesquisador envolvido é Walfredo Cirne.
A arquitetura simplificada de MyGrid também implica na limitação do tipo de
aplicação que pode ser executada no sistema. Dessa maneira, MyGrid é um ambiente voltado
para a execução de aplicações Bag-of-Tasks (BOTs). Uma aplicação Bag-of-Tasks é composta
por uma ou mais tarefas que podem ser executadas de forma independente, ou seja, não existe
comunicação entre as tarefas.
A aplicação pode ser composta de tarefas iguais ou diferentes, porem sempre
independentes. Aplicações Bag-of-Tasks são empregadas em estudos paramétricos, buscas
exaustivas por partição do espaço, como quebra de chaves criptográficas, biologia
computacional e processamento de imagens. A ausência de comunicação entre as tarefas da
aplicação facilita a execução de aplicações sobre recursos conectados por redes de grande
área, as quais muitas vezes apresentam limitações na capacidade de comunicação.
14
2.4.4.1 OurGrid
MyGrid é uma solução voltada para o usuário utilizar os recursos dos quais dispõe.
Porém, não há nenhuma maneira direta que permita que um usuário utilize os recursos de
terceiros, a menos que o usuário explicitamente negocie o acesso aos recursos com seus
proprietários, o que costuma ser difícil. Dessa maneira, os desenvolvedores de MyGrid
arquitetaram OurGrid [1], uma comunidade peer-to-peer para compartilhamento de recursos.
OurGrid continua seguindo o princípio básico de MyGrid: oferecer suporte apenas as
aplicações BoT, visando a simplicidade.
O principal objetivo de OurGrid é criar um modelo econômico simplificado para o
compartilhamento de recursos. Tal modelo está sendo estruturado como uma rede de favores,
ou seja, um modelo no qual fornecer recursos para outro peer é considerado um favor, que
eventualmente será recompensado em caso de necessidade. O modelo econômico de OurGrid,
baseado no empréstimo de recursos ociosos, não prevê outros mecanismos de troca: por
exemplo, não e possível que algum usuário sem recursos computacionais pague em dinheiro
para utilizar a Grade. Tal limitação na pratica não é tão grave, uma vez que não existem
soluções amplamente adotadas para o pagamento de serviços dessa natureza. Finalmente, o
modelo de OurGrid visa atenuar a questão de usuários que consomem mas não compartilham
recursos: o objetivo de OurGrid e que esse usuário seja marginalizado, consumindo recursos
apenas quando não existem outros usuários requisitando os mesmos.
2.4.5 SETI@home
SETI@home [1] foi desenvolvido na Universidade da Califórnia em Berkeley, com o
objetivo de realizar a busca por inteligência extraterrestre através da análise de ondas de radio,
nas quais realizam-se buscas por padrões que possam evidenciar atividades de vida
inteligente. Esse problema demanda muito em termos de computação, uma vez que os dados
são analisados intensivamente. O projeto necessitava de uma quantidade de computação que
dificilmente iria conseguir através da abordagem tradicional, ou seja, através do uso de
supercomputadores dedicados ao projeto. Assim, consideraram a possibilidade de utilizar
parte dos milhões de computadores pessoais distribuídos ao redor do globo.
15
A arquitetura do sistema e bem simples: dados capturados por um radiotelescópio em
Arecibo são gravados em fitas de 35 GB que são enviadas por correio a um laboratório na
Califórnia. Os dados de cada fita são quebrados em pequenos pacotes de 350 KB,
denominados unidades de trabalho (workunits). Os clientes SETI@home são executados em
computadores pessoais espalhados pelo mundo. Basicamente eles requisitam uma unidade de
trabalho, processam a mesma, enviam os resultados para o servidor central e nesse momento
recebem outra unidade de trabalho. O protocolo de comunicação entre os clientes e o servidor
é baseado em HTTP, de maneira a permitir que máquinas atrás de firewalls consigam contatar
o servidor. Não há dependência entre as unidades de trabalho, o que implica não existir
comunicação entres os clientes. Alem disso, a conexão a Internet é necessária apenas no
momento de envio dos dados e recepção dos pacotes, permitindo o processamento
desconectado. O cliente periodicamente salva em disco o estado da computação, de maneira a
permitir o progresso mesmo se a maquina é freqüentemente ligada e desligada.
Ao utilizar poder de processamento de centenas de milhares de máquinas,
SETI@home está sujeito a falhas de processamento, intencionais ou não. Por exemplo, falhas
de processamento podem gerar resultados errôneos, e o software cliente pode ser modificado
de maneira a prover resultados forjados ou simplesmente errados. Diversas soluções foram
propostas para amenizar tais problemas, e SETI@home aplica a mais simples delas: um
mecanismo de redundância que consiste em enviar a mesma unidade de trabalho a mais de um
cliente. Dessa maneira, os resultados podem ser comparados de maneira a determinar sua
veracidade. O servidor responsável pela distribuição de unidades de trabalho contém um
coletor de lixo responsável por apagar unidades de trabalho do disco. Tal coletor já operou
sob duas políticas: a primeira consistia em apagar uma unidade de trabalho apenas quando N
resultados para tal unidade de trabalho forem recebidos. Isso garante o nível de redundância
desejado, porém o custo de armazenamento é muito grande, pois as unidades permanecem em
disco ate serem processadas por N clientes. A segunda política, e a mais recentemente
utilizada, consiste em apagar uma unidade de trabalho quando esta foi enviada para M
clientes, M > N. Tal abordagem diminui o custo de armazenamento, porem é provável que
algumas unidades nunca sejam processadas, caso enviadas a clientes que não completem a
computação por quaisquer motivos. Note que aumentar M em relação a N aumenta a
probabilidade de uma unidade de trabalho ser processada.
O maior êxito de SETI@home deu-se em sua grande capacidade em arregimentar
usuários para o sistema. Atualmente existem mais de 5,2 milhões de usuários cadastrados
sendo 900 mil desses considerados ativos. É considerado o problema que mais recebeu tempo
16
de computação na história. Tais números são expressivos se considerarmos que a participação
no projeto requer uma postura ativa dos usuários, isto é, eles têm de baixar o cliente para
participar. Outras razões que explicam o êxito do projeto em atrair usuários incluem a
simplicidade de instalação e manutenção do cliente, poucos problemas de segurança relatados,
sendo estes de pouco impacto, e uma atenção especial na informação dos usuários sobre o
andamento do projeto e os resultados obtidos. Tal informação se da através do oferecimento
de um protetor de tela visualmente atraente que mostra informações referentes ao pacote
sendo analisado, como origem do pacote, resultados obtidos na análise e assim por diante.
Além disso, o site do projeto traz informações globais, como a porcentagem total dos dados
coletados e analisados.
Apesar do seu sucesso, a arquitetura do sistema apresenta muitas limitações. A
aplicação é fortemente acoplada ao sistema, não permitindo que se executem outros
problemas além do SETI. Como foi projetado especialmente para resolver apenas um
problema, varias categorias de problemas não seriam suportadas mesmo que fosse possível
trocar a aplicação sendo executada. Por exemplo, problemas que requerem comunicação entre
os nós da aplicação não poderiam executar no sistema. Alem disso, falta um mecanismo que
permita um uso mais eficiente dos recursos: quando a máquina não esta totalmente ociosa o
único controle que pode-se fazer sobre o cliente e colocá-lo em baixa prioridade.
2.4.6 BOINC
Visando sanar as limitações características de SETI@home, mas tentando preservar
seu êxito relativo ao grande apoio por parte de usuários, foi criado o sistema BOINC
(Berkeley Open Infrastructure for Network Computing) [1]. BOINC realiza progressos
importantes em aspectos que eram deficitários em SETI@home. BOINC é um arcabouço para
a construção de sistemas distribuídos que façam uso de recursos computacionais de terceiros.
Assim, torna-se possível utilizar BOINC para construir diversas aplicações, ao contrario do
que acontecia em SETI@home, onde a aplicação era embutida no sistema. Entretanto, as
aplicações a serem executadas em BOINC são da mesma categoria que SETI - aplicações
altamente paralelizáveis sem comunicação entre os nós, do tipo Bag-of-Tasks.
BOINC cria o conceito de Projeto, um agrupamento de programas do lado cliente e
servidor que visam resolver determinado problema. Um usuário pode participar de vários
projetos simultaneamente, especificando quanto de seus recursos ociosos deseja compartilhar
17
com cada um. Cada projeto opera um conjunto de servidores próprio, e é responsável por
desenvolver as aplicações que serão enviadas para os clientes BOINC. Alem disso, os projetos
também devem criar as unidades de trabalho, ou seja, o conjunto de parâmetros e arquivos de
entrada a serem submetidos para execução, um validador, que implementa alguma política de
verificação de resultados e atribuição de créditos, e um assimilador, responsável por tratar as
unidades de trabalho já processadas.
A arquitetura de BOINC é simples e também inspirada em seu predecessor. No lado
servidor, existe um banco de dados relacional, que armazena diversas informações referentes
a um projeto, como usuários cadastrados, unidades de trabalho disponíveis, enviadas e
processadas, etc. Cada projeto também possui um back-end, responsável por distribuir as
unidades de trabalho e tratar os resultados recebidos. Os servidores de dados são responsáveis
pela distribuição dos arquivos de dados e pela coleta de arquivos de saída, quando presentes.
Os escalonadores controlam o fluxo de entrega das unidades de trabalho aos clientes
conforme a produtividade de cada um. Finalmente, são disponibilizadas interfaces Web para a
interação com os desenvolvedores e usuários. O lado cliente é composto pelo núcleo, que se
mantém comum como fundação do sistema, e o código cliente específico de um determinado
projeto.
A idéia de unidade de trabalho introduzida em SETI@home retorna em BOINC,
representando um subconjunto do problema que se deseja resolver. Porem, uma vez que
diversos projetos poderão utilizar a mesma infra-estrutura de software, as unidades de
trabalho passaram a ser variáveis de acordo com cada projeto, em termos de tamanho dos
arquivos de entrada e saída, e consumo de memória, por exemplo. Quando requisitam uma
unidade de trabalho, os clientes BOINC informam aos escalonadores as características
estáticas da maquina. Dessa maneira, uma maquina só recebe unidades de trabalho que
possam ser processadas considerando os recursos disponíveis.
Como a disponibilidade das maquinas e dinâmica e pode variar sem aviso prévio,
BOINC fornece uma API para checkpointing, a qual permite que o estado de execução da
aplicação seja salvo e retomado posteriormente. A aplicação deve estar ciente dos momentos
de checkpointing, ou seja, ela deve indicar explicitamente os pontos no qual o estado de
execução deva ser salvo, se possível. Também é responsabilidade da aplicação decidir o que
deve ser salvo para posteriormente retomar a computação.
Varias questões de segurança são abordadas por BOINC. Uma vez que o cliente pode
executar diversas aplicações, diferentemente de SETI@home, mecanismos de segurança
tornam-se necessários. Por exemplo, para impedir falsificação de resultados, BOINC utiliza
18
redundância para diminuir a chance de ocorrência desse problema: cada unidade de trabalho é
distribuída para vários clientes, e os resultados são verificados em busca de eventuais
discrepâncias. Para eliminar a distribuição forjada de aplicações, ou seja, a possibilidade de
um atacante distribuir um código cliente como se pertencente a um projeto no qual o usuário
participa, BOINC usa assinatura de código, ou seja, cada projeto possui um par de chaves
criptográficas que são usadas para autenticar os programas que distribui. Finalmente, para
impedir ataques de negação de serviço ao servidor de dados, nos quais um mal-intencionado
envia arquivos de saída gigantescos de maneira a ocupar todo o disco do servidor, BOINC
permite que os desenvolvedores da aplicação informem o tamanho máximo esperado dos
arquivos de saída, impedindo assim tal ataque. Entretanto, BOINC não toma medidas para
impedir que uma aplicação distribuída por um projeto cause danos intencionais ou acidentais
ao sistema dos usuários: não há nenhum tipo de proteção no estilo sandbox2 que limite as
ações de aplicações pertencentes a um projeto, ou seja, o participante precisa confiar
plenamente nos responsáveis por um projeto.
Assim como em SETI@home, BOINC não se preocupa somente em construir uma
infra-estrutura para a computação distribuída, mas também em criar características que
atraiam usuários aos eventuais projetos que utilizarão tal arcabouço. Assim, BOINC oferece a
possibilidade aos desenvolvedores de projetos de gerar gráficos em OpenGL que serão
apresentados aos usuários fornecedores de recursos, servindo como um atrativo. Também
deixam claro que projetos precisam ter apelo público para serem bem sucedidos na formação
de uma grande base de colaboradores.
Alguns projetos que utilizam o BOINC são:
•
Climateprediction.net [1]: objetiva aumentar a precisão das previsões sobre as
mudanças climáticas;
•
Einstein@home [1]: busca por sinais gerados por estrelas de nêutrons, os Pulsares;
•
Folding@home [1]: executa intensivas simulações computacionais da montagem de
proteínas, o que permite o melhor entendimento do desenvolvimento de muitas
doenças, incluindo o mal de Alzheimer e o câncer;
•
LHC@home [1]: simulação de um acelerador de partículas com o objetivo de
aperfeiçoar o projeto do acelerador de partículas Hadron (Large Hadron Collider);
2
Sandbox (”caixa de areia”) é um ambiente protegido. Programas executados em um Sandbox possuem poucos
privilégios e não podem fazer diversas alterações no sistema.
19
•
Predictor@home [1]: realiza estudos para a previsão da estrutura espacial de proteínas
a partir de seqüências protéicas;
•
SETI@home [1]: assim como a versão original, analisa os dados captados por
radiotelescópios em busca de indícios de inteligência extraterrestre. Foi migrado para
o BOINC.
20
3 APLICAÇÃO DESENVOLVIDA
A aplicação desenvolvida neste projeto tem como objetivo a multiplicação de matrizes,
uma das operações fundamentais em álgebra linear e que serve como principal bloco de
construção para diferentes algoritmos, incluindo solução de sistemas de equação linear,
inversão de matrizes, avaliação da determinante da matriz e o fecho transitivo de um grafo.
Em diversos casos, a complexidade assintótica destes algoritmos dependem diretamente da
complexidade da multiplicação da matriz, o que motivo o estudo das possibilidades de
acelerar a multiplicação. A inclusão de multiplicação de matrizes em diversos benchmarks
aponta seu papel como fator para a determinação de desempenho de computação de alta
velocidade [1].
3.1 Arquitetura da aplicação
A aplicação está dividida em módulos, o Mestre e o Trabalhador Remoto, ou nó. Cabe
ao mestre gerenciar e dividir os trabalhos para as outras máquinas da grade. Nele são
informadas as matrizes que serão multiplicadas, e feita a divisão destas em pedaços menores,
que serão as unidades de trabalho.
O Trabalhador Remoto se conecta ao servidor para se registrar, receber as unidades de
trabalho, efetuar o processamento, devolver resultados e solicitar por novas unidades.
Esta divisão de trabalho é conhecida como arquitetura mestre-escravo, onde subdividimos
um problema em partes a serem computadas em paralelo. Cabe ao processo mestre definir a
quebra do problema e criar os escravos que farão a computação. Após o término do cálculo,
os escravos enviam os resultados para o mestre que faz a concatenação e expõe o resultado.
Os processos escravos não precisam ser necessariamente iguais, podendo realizar
processamento diferenciado.
O fator que caracteriza a arquitetura mestre-escravo é a presença de um processo (mestre)
coordenando o trabalho. Durante o processamento remoto, os processos filhos (escravos)
podem estabelecer comunicação entre si para a troca de dados.
21
Foram desenvolvidos módulos utilizando a linguagem Java e a tecnologia RMI (Remote
Method Invocation). O método de entrada das matrizes são arquivos de texto, com os valores
de precisão double separados por espaço em branco. Cada arquivo representa uma matriz, e
para a multiplicação estas precisam conter o mesmo número de linhas e colunas. Para a
criação das matrizes usadas como massa de dados, foi escrito um programa que, informado a
quantidade de linhas/colunas desejadas, gerava um arquivo de saída preenchido com valores
aleatórios.
A primeira matriz é dividida, inicialmente, em várias matrizes menores. Essas matrizes são
então enviadas para os vários nós participantes, junto a segunda matriz. Quando um nó
específico termina um processamento, envia de volta o resultado.
Assim, temos módulos para organizar a comunicação envolvida e para o cálculo
propriamente dito. Os principais módulos estão descritos a seguir:
Mestre: responsável por criar a divisão das matrizes, receber conexões dos nós e registrálos.
PoolEscravos: invocado pelo Mestre, é onde os nós são registrados, e também
responsável por iniciar o processamento chamando o TrabalhadorProxy.
TrabalhadorProxy: responsável por iniciar e parar o processamento nos nós registrados,
gerenciando o processamento.
TrabalhadorRemoto: conecta-se ao servidor, e recebe as informações para o
processamento.
Matriz: classe que representa uma matriz, e contém a lógica para multiplicação.
MatrizRemoto: executa tarefas conforme solicitação do servidor.
MatrizTarefa: cria as tarefas que serão executadas.
Todo o código fonte, incluindo comentários, está disponível nos apêndices.
3.2 Tecnologia utilizada
Todos os módulos foram desenvolvidos utilizando a linguagem Java, da Sun
Microsystems. Java é uma linguagem de programação que permite o desenvolvimento de
aplicações para uma série de plataformas [1]. É possível ter softwares em Java em telefones
celulares e em mainframes, por exemplo. Por isso foi escolhida, pois se ajusta a
22
heterogeneidade das grades computacionais. O ambiente de desenvolvimento utilizado é o
J2SE, que é voltado a PCs e servidores.
Para a comunicação foi utilizada a tecnologia de Remote Method Invocation (RMI). RMI
pode ser encarado como uma forma sofisticada de passagem de mensagens através de
chamada remota de função. De fato, RMI é baseado numa tecnologia mais antiga e
amplamente utilizada, a RPC (Remote Procedure Call), que permite que um programa
procedural chame de forma transparente funções que serão executadas remotamente. A infraestrutura RPC é responsável por todo o marshalling e comunicação através da rede envolvida
no processo. Uma limitação dessa tecnologia é que ela suporta somente tipos básicos de
dados. RMI é a implementação da Sun de RPC para comunicação distribuída entre objetos
Java (Java-object-to-Java-object). Uma vez registrado, um método (que nada mais é que uma
função no mundo orientado a objetos) pode ser procurado e utilizado por um cliente. Todo
comunicação pela rede e todo o marshalling, inclusive de objetos complexos, é realizado pelo
RMI. A sintaxe de chamada de um método remoto é idêntica à da chamada de um método de
um objeto local. Isso ocorre devido à criação de um objeto proxy local, que na verdade está
referenciando um objeto remoto, deixando assim todo o processo transparente para quem
chama [1].
A aplicação é um exemplo típico de BoT, mas optou-se por desenvolvê-la
completamente desde o início, sem utilizar-se de middlewares já existentes, para maior
compreensão dos processos envolvidos.
3.3 Resultados
Foram realizados diversos testes utilizando para isso matrizes 10 x 10, 100 x 100, 1000
x 1000, numa progressão exponencial. Não foi possível realizar os testes com uma matriz
10000 x 10000, pois a memória da máquina utilizada como servidor não era suficiente para
carregar o arquivo representando as matrizes, gerando um erro de falta de espaço para o heap
space da Máquina Virtual Java (Java Virtual Machine, JVM). A maior matriz possível
utilizada para os testes foi 2000x2000, sendo também utilizada.
Foram utilizadas as seguintes configurações de ambiente:
•
Servidor: AMD Atlhon 64 3000+, 1GB de memória RAM, Microsoft Windows
2000 Service Pack 4
23
•
Nós: Intel Pentium 4 3,20 GHz, 1GB de memória RAM, Microsoft Windows
XP Professional Service Pack 2
As máquinas estavam ligadas através de uma rede wireless de 108mbps padrão 802.11g.
Para medição dos tempos de execução utilizou-se o tempo do sistema, obtido através do
comando System.currentTimeMillis() ao início e ao final da execução. Foi considerado como
início o momento em que o primeiro nó se registra no servidor.
Para efeitos de comparação, o mesmo algoritmo utilizado para multiplicação
implementado nos trabalhadores remotos foi utilizado para execução em uma máquina
somente, o servidor. Neste caso, é medido o tempo de execução do algoritmo puro, sem
overhead do RMI ou da rede. Esse tempo é identificado como Local na Figura 1 e Tabela 2.
Também foram feitos testes com somente um nó, para observar qual o overhead ocasionado
pelo RMI. Neste caso, servidor e trabalhador remoto foram executados no servidor, para que
não houvesse o overhead de rede. A Figura 1 apresenta os resultados da multiplicação.
Tempo de execução
1400000
Tempo (ms)
1200000
1000000
Local
1 nó
2 nós
4 nós
800000
600000
400000
200000
0
10x10
100x100
1000x1000
2000x2000
Tamanho matriz
Figura 1 - Tempo de execução em função do tamanho da matriz
Tabela 2 – Tempos de execução em ms
Matriz
10x10
Local
1 nó
2 nós
4 nós
0
1016
1047
1485
15
10047
8594
4875
1000x1000
35938
162109
304735
244000
2000x2000
311812
509813 1293609
1061156
100x100
24
Como podemos perceber, a execução local acabou demonstrando-se mais rápida, assim
como a execução utilizando somente um nó. Isto pode ser explicado pelo overhead imposto
pelo RMI e pela comunicação de rede. A diferença entre o processamento local e em um nó é
praticamente constante entre os tamanhos das matrizes, demonstrando que foi ocasionado
pelo RMI. Já a diferença de velocidade entre a multiplicação em vários nós (dois ou quatro)
para a somente utilizando um foi ocasionada pela necessidade de broadcast da segunda matriz
completa, como demonstra os tempos entre a multiplicação 1000x1000 e 2000x2000. O
tamanho de um arquivo representando uma matriz 1000x1000 é de 3,71MB, enquanto o de
uma matriz 2000x2000 ficava em 14,8MB.
Apesar disso, houve ganho de tempo com o acréscimo de nós.
25
4 CONCLUSÃO
O objetivo desta monografia é apresentar a tecnologia das grades computacionais, seus
usos e tecnologias. Através dela foi possível conhecer vários projetos relacionados a esta área.
Além disso, foi desenvolvida uma aplicação baseada nos princípios das grades, com o
objetivo de demonstrar sua utilização. Os resultados obtidos não foram os esperados, pois
deduzia-se que a execução em vários nós seria mais rápida que a localmente. As máquinas
utilizadas para os testes eram bem novas, e a velocidade de processamento acabou sendo
menor que os tempos envolvidos na divisão das tarefas e comunicação entre os nós. Mas
dependendo das configurações disponíveis, a distribuição de tarefas é uma alternativa
altamente viável.
Apesar de não haver ganho de desempenho, a experiência é útil do ponto de vista
tecnológico, pois foram utilizadas tecnologias de sistemas distribuídos heterogêneos, como a
linguagem Java e RMI.
4.1 Extensões
Este trabalho pode ser continuado de diversas maneiras. A área de computação em
grade é relativamente nova e existe muito campo a ser explorado, além dos outros tipos de
grades existentes (grade de serviços, grade de dados, etc.).
Já na aplicação desenvolvida, diversas melhorias podem ser implementadas, como:
•
Alteração do algoritmo, visando diminuir os gastos de rede;
•
Implementação utilizando um middleware existente: usar as ferramentas já existentes,
como o Globus ou MyGrid para melhorar a aplicação;
•
Criação de um sistema base para aplicações distribuídas: baseado no sistema BOINC,
é possível criar um aplicativo que sirva como base para aplicações distribuídas, que
controle toda a parte de comunicações. Seria de responsabilidade do usuário somente
os processamentos específicos.
26
Apêndice 1 – Código fonte da classe Mestre.java
import ghisi.Matriz.Matriz;
import ghisi.Matriz.MatrizTarefa;
import ghisi.TrabalhadorRemoto.PoolEscravos;
import ghisi.TrabalhadorRemoto.TrabalhadorProxy;
import ghisi.TrabalhadorRemoto.TrabalhadorRemoto;
import java.io.File;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;
public class Mestre extends Thread{
Socket entrada;
Vector <String>listaEscravos = new Vector<String>();
static ServerSocket server = null;
final static int port = 5505;
String urlClasse = "TrabalhadorRemoto";
String nomeClasse = "ghisi.Matriz.MatrizRemoto";
PoolEscravos escravos = new PoolEscravos();
long inicio=0;
MatrizTarefa tarefas[];
Matriz produto;
MatrizTarefa multiplicando;
TrabalhadorProxy p[];
private MatrizTarefa[] criarProblema(String matrizA, String matrizB ) {
try {
Matriz t1 = Matriz.lerArquivo(new File(matrizA));
Matriz t2 = Matriz.lerArquivo(new File(matrizB));
multiplicando = new MatrizTarefa( t2, null, 0 );
produto = new Matriz( t1.getQtdLinhas(), t1.getQtdColunas() );
int n_escravo;
//decide em qtas matrizes a matrizA será dividida
if(t1.getQtdLinhas()<=10){
n_escravo = 1;
} else if (t1.getQtdLinhas()<=100){
n_escravo = 10;
} else {
27
n_escravo = 100;
}
Matriz partes[] = t1.parte( n_escravo);
MatrizTarefa[] t = MatrizTarefa.criarTarefas( partes, produto );
return t;
} catch (Exception e) {
System.out.println("Classe Mestre metodo criarProblema erro : " +
e.getMessage());
e.printStackTrace();
}
return null;
}
/** aguarda conexões dos escravos, e os inclui no pool **/
public void run(){
System.out.println("Aguardando conexões .");
while(true){
try{
entrada = server.accept();
listaEscravos.add(entrada.getInetAddress().getHostAddress());
System.out.println(entrada.getInetAddress().getHostAddress() + "
conectado");
int ultimo =
escravos.addEscravo(entrada.getInetAddress().getHostAddress(), TrabalhadorRemoto.nomeServico);
if(inicio==0){
inicio = System.currentTimeMillis();
}
escravos.iniciarProxy(ultimo, urlClasse, nomeClasse,
multiplicando);
} catch (Exception e) {
System.out.println("Mestre run(): ERRO " +
e.getClass().getName() + "(" + e.getMessage() + ")" );
}
}
}
public Mestre(String matrizA, String matrizB) {
int k, n_tarefas;
long fim;
try {
System.out.println("Mestre: init [" + urlClasse + ":" + nomeClasse +
"]");
//cria as tarefas para sere resolvidas
tarefas = criarProblema(matrizA, matrizB);
n_tarefas = tarefas.length;
//adiciona tarefa ao pool de escravos
for(k=0;k<n_tarefas;k++) {
escravos.addPronto( tarefas[k] );
}
start();
28
escravos.esperarEscravos();
fim = System.currentTimeMillis();
System.out.println("\n******\nMestre Finalizado\n");
//
System.out.println("RESULTADO " + produto.toString() );
System.out.println("RESULTADO salvo no arquivo resultado.txt");
produto.salvarArquivo();
System.out.println("Tempo de execução: " + (fim-inicio) + "ms");
System.exit(0);
} catch (Exception e) {
System.out.println("Mestre: ERRO " + e.getClass().getName() + "(" +
e.getMessage() + ")" );
}
}
public static void main( String[] args ) {
try {
server = new ServerSocket(port);
String matrizA = args[0];
String matrizB = args[1];
new Mestre(matrizA, matrizB);
} catch (Exception e) {
System.out.println("Não foi possível inicializar o servidor");
e.printStackTrace();
}
}
}
29
Apêndice 2 – Código fonte da classe Matriz.Matriz.java
package ghisi.Matriz;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.Serializable;
import java.util.StringTokenizer;
public class Matriz implements Serializable {
private static final long serialVersionUID = 5767004386937377598L;
private double m[][];
public Matriz( double x[][] ) {
m = x;
}
public Matriz( int nr, int nc ) {
m = new double[nr][nc];
}
public Matriz(Matriz m){
this.m = m.getMatrizDouble();
}
private double[][] getMatrizDouble() {
return this.m;
}
public Matriz getMatriz() {
return this;
}
public int getQtdLinhas() { return m.length; }
public int getQtdColunas() { return m[0].length; }
public Matriz multiplicar( Matriz b ) throws Exception {
double soma = 0.0;
int nra = getQtdLinhas();
int nca = getQtdColunas();
int nrb = b.getQtdLinhas();
int ncb = b.getQtdColunas();
30
if ( nca != nrb ) throw new Exception("Matriz: dimensoes incompatives");
Matriz c = new Matriz( nra, ncb );
for(int j=0;j<nra;j++) {
for( int k=0;k<ncb;k++ ) {
soma = 0.0;
for( int p=0;p<nca;p++ ) {
soma += m[j][p]*(b.m[p][k]);
}
c.m[j][k] = soma;
}
}
return c;
}
public void setLinhas( int comecoLinha, Matriz x ) throws Exception {
double b[][] = x.m;
int n_linhas = x.getQtdLinhas();
int tamanhoLinha = x.getQtdColunas();
if( getQtdColunas() != tamanhoLinha ) {
throw new Exception("setLinhas: dimensao incompativel");
}
int j, k, p;
for(j=comecoLinha,p=0;p<n_linhas;j++,p++) {
for(k=0;k<tamanhoLinha;k++) {
m[j][k] = b[p][k];
}
}
}
/** Processa um resultado retornado de um escravo **/
public void processarResultado( Matriz r, MatrizTarefa t ) {
Matriz x = r.getMatriz();
Matriz produto = t.getMatrizB();
try {
produto.setLinhas( t.getLinhaInicio(), x );
} catch( Exception e ) {
System.out.println("processarResultado: erro (" + e.getMessage() + ")"
);
}
}
private Matriz criarParte( int offset, int linhas ) {
int j, k, nc = getQtdColunas();
Matriz x = new Matriz( linhas, nc );
for( j=0,k=offset;j<linhas;j++,k++ ) {
for( int p=0;p<nc;p++) {
(x.m)[j][p] = m[k][p];
}
}
return x;
}
31
/** criar partes da matriz **/
public Matriz[] parte( int n ) {
int linhas_pb = getQtdLinhas() / n;
int nr = getQtdLinhas();
int offset = 0;
Matriz b[] = new Matriz[ n ];
for( int k=0;k<n;k++ ) {
b[k] = criarParte( offset, linhas_pb );
offset += linhas_pb;
if ( offset >= nr ) break;
if ( (offset+linhas_pb) >= nr ) {
linhas_pb = nr - offset;
}
}
return b;
}
public String toString() {
StringBuffer s = new StringBuffer();
s.append("MATRIZ (" + getQtdLinhas() + "x" + getQtdColunas() + ")\n" );
for(int j=0;j<getQtdLinhas();j++) {
for( int k=0;k<getQtdColunas();k++ ) {
s.append( " " + m[j][k] );
}
s.append('\n');
}
return s.toString();
}
public static Matriz lerArquivo(File arquivo) throws Exception{
FileReader fileReader;
BufferedReader br;
StringTokenizer st;
int linhas = 0;
String linha;
fileReader = new FileReader(arquivo);
br = new BufferedReader(fileReader);
do {
linha = br.readLine();
linhas++;
} while (linha!=null);
double[][] m = new double[linhas-1][linhas-1];
linhas = 0;
int colunas;
br.close();
fileReader.close();
32
fileReader = new FileReader(arquivo);
br = new BufferedReader(fileReader);
do {
linha = br.readLine();
if (linha!=null){
st = new StringTokenizer(linha," ");
colunas = 0;
while(st.hasMoreElements()){
String valor = st.nextToken();
if(!valor.equalsIgnoreCase("")){
m[linhas][colunas] = Double.valueOf(valor);
colunas++;
}
}
}
linhas++;
} while (linha!=null);
br.close();
fileReader.close();
return new Matriz(m);
}
public File salvarArquivo() throws Exception{
File resultado;
resultado = new File("resultado.txt");
FileWriter fw = new FileWriter(resultado);
BufferedWriter bw = new BufferedWriter(fw);
bw.write(toString());
bw.close();
fw.close();
return resultado;
}
}
33
Apêndice 3 – Código fonte da classe Matriz.MatrizRemoto.java
package ghisi.Matriz;
import java.rmi.RemoteException;
import java.rmi.server.RemoteObject;
public class MatrizRemoto extends RemoteObject{
private static final long serialVersionUID = -2023924134904162374L;
private Matriz b;
public MatrizRemoto() throws RemoteException {
super();
}
/** Indica matriz a ser multiplicada
* evita que seja necessário enviar a segunda matriz todas as vezes para o escravo
*/
public void init( MatrizTarefa params ) throws RemoteException {
b = ((MatrizTarefa)params).getMatriz();
if ( b == null ) {
throw new RemoteException( "Multiplicando null", null );
}
}
/** Executa a multiplicao das matrizes indicadas na tarefa, e retorna o resultado
* para quem chamou
*/
public Matriz executarTarefa( MatrizTarefa tarefa ) {
System.out.println("MatrizRemoto: iniciando .... " );
Matriz a = ((MatrizTarefa)tarefa).getMatriz();
//
System.out.println("MatrizRemoto: multiplicar " +
((a==null)?"null":a.toString()));
Matriz c;
try {
c = a.multiplicar( b );
} catch( Exception e ) {
System.out.println("MatrizRemoto: erro (" + e.getMessage() + ")" );
c = null;
}
return c ;
}
34
public String toString() {
String s = "MatrizRemoto: b ";
if ( b == null ) s = s + "null";
else s = s + "set";
return s;
}
}
35
Apêndice 4 – Código fonte da classe Matriz.MatrizTarefa.java
package ghisi.Matriz;
import java.io.Serializable;
public class MatrizTarefa implements Serializable{
private static final long serialVersionUID = 8521561741884587382L;
public static final int IDLE = -1;
private int indiceEscravo = IDLE;
private Matriz b, produto;
private int linhaInicio;
public MatrizTarefa( Matriz b, Matriz c, int linhaInicio ) {
this.b = b;
this.linhaInicio = linhaInicio;
produto = c;
}
/** Processar o resultado retornado de um trabalhador remoto
*/
public void processarResultado( Matriz r ) {
//
System.out.println("Resultado " +((r==null)?"null":r.toString()));
try {
produto.setLinhas( linhaInicio, r );
} catch ( Exception e ) {
System.out.println("processarResultado erro (" + e.getMessage() + ")" );
}
}
public Matriz getMatriz() { return b; }
public Matriz getMatrizB() { return produto; }
public int getLinhaInicio() { return linhaInicio; }
public static MatrizTarefa[] criarTarefas( Matriz[] b, Matriz c ) {
MatrizTarefa mt[] = new MatrizTarefa[ b.length ];
int offset = 0;
for( int j=0;j<b.length;j++ ) {
mt[j] = new MatrizTarefa( b[j], c, offset );
offset += b[j].getQtdLinhas();
}
return mt;
}
36
public void setEscravo( int k ) { indiceEscravo = k; }
public int getIndiceEscravo() {
return indiceEscravo;
}
}
37
Apêndice 5 – Código fonte da classe TrabalhadorRemoto.
PoolEscravos.java
package ghisi.TrabalhadorRemoto;
import ghisi.Matriz.MatrizTarefa;
import java.rmi.Naming;
import java.util.ArrayList;
import java.util.Vector;
/** Pool de escravos, referencia para os escravos registrados **/
public class PoolEscravos {
private ArrayList<Trabalhador> w;
private int ativo;
private ArrayList<TrabalhadorProxy> p;
private VetorTrabalho pronto, completo, trabalhando;
public PoolEscravos(){
w = new ArrayList<Trabalhador>();
p = new ArrayList<TrabalhadorProxy>();
ativo = 0;
pronto = new VetorTrabalho( ativo );
completo = new VetorTrabalho( ativo );
trabalhando = new VetorTrabalho( ativo );
}
/** Constroi referencias aos escravos **/
public PoolEscravos( Vector nomes, String servico ) throws Exception {
int k, nw = nomes.size();
w = new ArrayList<Trabalhador>();
ativo = k = 0;
// Tenta encontrar os trabalhadores remotos
for(;k<nw;k++) {
String nome = "//" + nomes.get(k) + "/" + servico;
System.out.print("Tentando [" + nome + "] "); System.out.flush();
try {
w.add((Trabalhador)(Naming.lookup( nome )));
ativo++;
}catch( Exception e ) {
System.out.println("\nPoolEscravos:procura falhou " +
e.getClass().getName() );
System.out.println("\nPoolEscravos:procura falhou (" +
e.getMessage() + ")" );
e.printStackTrace();
throw e;
38
}
}
pronto = new VetorTrabalho( ativo );
completo = new VetorTrabalho( ativo );
trabalhando = new VetorTrabalho( ativo );
this.criarProxy();
System.out.println("PoolEscravos: " + ativo + " trabalhadores encontrados");
}
/** adiciona referência a um escravo **/
public int addEscravo( String nomes, String servico ) throws Exception {
String nome = "//" + nomes + "/" + servico;
System.out.print("Tentando [" + nome + "] "); System.out.flush();
try {
w.add((Trabalhador)(Naming.lookup( nome )));
ativo++;
}catch( Exception e ) {
System.out.println("\nPoolEscravos:procura falhou " +
e.getClass().getName() );
System.out.println("\nPoolEscravos:procura falhou (" + e.getMessage() +
")" );
e.printStackTrace();
throw e;
}
p.add(this.addProxy());
System.out.println("PoolEscravos: " + ativo + " trabalhadores encontrados");
return p.size()-1;
}
/** cria proxies para todos os escravos **/
private void criarProxy() {
p = new ArrayList<TrabalhadorProxy>();
for( int k=0;k<ativo;k++ ) {
p.add(new TrabalhadorProxy( k, w.get(k), pronto, trabalhando,
completo));
}
}
/** cria um proxy **/
private TrabalhadorProxy addProxy() {
return new TrabalhadorProxy( w.size(), w.get(w.size()-1), pronto, trabalhando,
completo);
}
/**dá inicio a todos os proxies registrados **/
public void iniciarProxy( String url, String nomeClasse, MatrizTarefa params ) {
int k, n = p.size();
System.out.println("PoolEscravos:iniciarProxy [" + url + "][" + nomeClasse );
try {
for(k=0;k<n;k++) {
System.out.println("PoolEscravos: proxy " + k );
39
p.get(k).setTarefa( url, nomeClasse );
System.out.println("PoolEscravos: proxy " + k + " tarefa
assignada" );
p.get(k).init( params );
System.out.println("PoolEscravos: proxy " + k + " chamado init"
);
}
for(k=0;k<n;k++) {
p.get(k).start();
System.out.println("PoolEscravos: proxy " + k + " chamando run"
);
}
} catch( Exception e ) {
System.out.println("PoolEscravos: erro " + e.getClass().getName() + " ("
+ e.getMessage() + ")" );
}
}
/** inicia um proxy especifico **/
public void iniciarProxy(int proxy, String url, String nomeClasse, MatrizTarefa params
) {
System.out.println("PoolEscravos:iniciarProxy [" + url + "][" + nomeClasse );
try {
System.out.println("PoolEscravos: proxy " + proxy );
p.get(proxy).setTarefa( url, nomeClasse );
System.out.println("PoolEscravos: proxy " + proxy + " tarefa assignada"
);
p.get(proxy).init( params );
System.out.println("PoolEscravos: proxy " + proxy + " chamado init" );
p.get(proxy).start();
System.out.println("PoolEscravos: proxy " + proxy + " chamando run" );
} catch( Exception e ) {
System.out.println("PoolEscravos: erro " + e.getClass().getName() + " ("
+ e.getMessage() + ")" );
}
}
public void addPronto( MatrizTarefa t ) {
pronto.add(t);
}
public void esperarEscravos() {
int k;
System.out.println("PoolEscravos: esperando" );
do {
int rc = pronto.getContador();
int wc = trabalhando.getContador();
40
if ( (rc == 0) && ( wc == 0) ) {
break;
}
try { Thread.sleep( 1000L ); } catch( Exception e ) {}
}
while( true );
// Completo, mandar proxy parar
System.out.println("Mestre: sinalizando proxies" );
for( k=0;k<p.size();k++) {
p.get(k).setFinalizou();
}
int alive_cnt = 0;
System.out.println("Mestre: esperando proxies sairem" );
do {
alive_cnt = 0;
for( k=0;k<p.size();k++) {
if( p.get(k).isAlive() ) { alive_cnt++; Thread.yield(); }
}
System.out.println("Ainda vivo: " + alive_cnt );
} while ( alive_cnt > 0 );
}
public int getContador() { return ativo; }
}
41
Apêndice 6 – Código fonte da classe TrabalhadorRemoto.
Trabalhador.java
package ghisi.TrabalhadorRemoto;
import ghisi.Matriz.Matriz;
import ghisi.Matriz.MatrizTarefa;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Trabalhador extends Remote {
public static final int IDLE = 0;
public static final int BUSY = 1;
public static final int UNKNOWN = 2;
public void setTarefa() throws RemoteException;
public Matriz executarTarefa( MatrizTarefa matrizTarefa ) throws RemoteException;
public int getStatus() throws RemoteException;
public void stop() throws RemoteException;
public void init( MatrizTarefa t ) throws RemoteException;
}
42
Apêndice 7 – Código fonte da classe TrabalhadorRemoto.
TrabalhadorProxy.java
package ghisi.TrabalhadorRemoto;
import ghisi.Matriz.Matriz;
import ghisi.Matriz.MatrizRemoto;
import ghisi.Matriz.MatrizTarefa;
public class TrabalhadorProxy extends Thread {
//referencia ao escravo
private int idRemoto;
private Trabalhador trabalhador;
private MatrizRemoto resolve;
//filas de trabalho
private VetorTrabalho preparado, trabalhando, completo;
//status atuasl
private MatrizTarefa atual;
private boolean acabouTarefas = false;
private boolean progressoDiag = true;
private String ids;
//constroi um proxy para um escravo
public TrabalhadorProxy( int id, VetorTrabalho r, VetorTrabalho w, VetorTrabalho c ) {
System.out.println("TrabalhadorProxy " + r.getContador() + " tarefas");
idRemoto = id;
preparado = r;
trabalhando = w;
completo = c;
ids = "[" + idRemoto + "]TrabalhadorProxy:";
}
public TrabalhadorProxy( int id, Trabalhador s, VetorTrabalho r, VetorTrabalho w,
VetorTrabalho c) {
this(id,r,w,c);
trabalhador = (Trabalhador)s;
resolve = null;
}
public void setTarefa( String urlTarefa, String nomeClasse ) throws Exception {
System.out.println(ids + "Tarefa: [" + urlTarefa + "]" );
System.out.println(ids + "Classe: [" + nomeClasse + "]" );
if ( trabalhador != null ) trabalhador.setTarefa();
}
43
public void init( MatrizTarefa t ) throws Exception {
if ( trabalhador != null ) trabalhador.init( t );
}
public void restart( String trabalhadorTarefas, String nomeClasse, MatrizTarefa params
) throws Exception {
if ( trabalhadorTarefas != null ) {
//para a tarefa atual, se tiver alguma executando
if( trabalhador.getStatus() == Trabalhador.BUSY ) {
trabalhador.stop();
}
trabalhador.setTarefa();
}
//manda a segunda matriz para o escravo
if( params != null ) {
if ( trabalhador != null ) trabalhador.init( params );
else resolve.init( params );
}
}
/** executa a thread
* percorre a fila de trabalhos prontos até o master mandar parar
*/
public void run() {
Matriz r;
if( progressoDiag ) {
System.out.println(ids + "Proxy " + ids + " iniciado");
System.out.println(ids + "Vetores: preparados " + preparado + ",
trabalhando " + trabalhando +", completos " + completo );
}
do {
atual = preparado.extrair();
//outro escravo pode ter pego a última unidade de trabalho
if ( atual != null ) {
//adiciona a fila de trabalhando
trabalhando.add( atual );
atual.setEscravo( idRemoto );
if( progressoDiag ) {
System.out.println(ids + " tarefa (" + atual + ")
start");
}
try {
System.out.println(ids + "Executar RMI - trabalhador " +
((trabalhador==null)?"null":"OK") +
" resolve " + ((resolve==null)?"null":"OK") );
System.out.println(ids + " tarefa (" + atual + ")
iniciada");
r = trabalhador.executarTarefa( atual );
atual.processarResultado(r);
44
//finalizado, mover para fila dos completos
completo.mover( atual, trabalhando );
if( progressoDiag ) {
System.out.println( ids + " tarefa (" + atual + ")
completa");
}
try { System.out.println( ids + " espera" );
Thread.sleep( 1000L ); } catch( Exception e ) {}
} catch( Exception e ) {
System.out.println(ids + "run - erro " +
e.getClass().getName() + " (" + e.getMessage() + ")" );
}
}else {
if( progressoDiag ) {
System.out.print(ids + " id:");
System.out.print( " pronto " + preparado.getContador() );
System.out.print( " trabalhando " +
trabalhando.getContador() );
System.out.print( " completo " + completo.getContador()
);
System.out.println();
}
// Yield - espera o mestre colocar outra tarefa na fila de
pronto
// ou aborta esta
try {
sleep( 1000L );
}catch( Exception e ) {}
yield();
}
} while( !acabouTarefas );
if( progressoDiag ) {
System.out.println(ids + "parando **********");
}
}
/** chamado pelo mestre quando não houver mais tarefas nas filas de pronto ou
trabalhando
* marca o TrabalhadorProxy para ser finalizado
*/
public void setFinalizou() {
if( progressoDiag ) {
System.out.println(ids + "setFinalizou chamado");
}
acabouTarefas = true;
}
}
45
Apêndice 8 – Código fonte da classe TrabalhadorRemoto.
TrabalhadorRemoto.java
package ghisi.TrabalhadorRemoto;
import ghisi.Matriz.Matriz;
import ghisi.Matriz.MatrizRemoto;
import ghisi.Matriz.MatrizTarefa;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class TrabalhadorRemoto extends UnicastRemoteObject implements Trabalhador {
private static final long serialVersionUID = -5370539922103358793L;
private static final String ID_STRING = "TrabalhadorRemoto v 0.0.1";
public static String nomeServico = "TrabalhadorRemoto";
private MatrizRemoto resolve;
private int status = Trabalhador.IDLE;
private static int port = 5505;
public TrabalhadorRemoto() throws RemoteException {
super();
System.out.println("TrabalhadorRemoto: servico [" + nomeServico + "]");
System.out.flush();
try {
//registra o servico no servidor RMI
Naming.rebind( nomeServico, this );
} catch ( MalformedURLException e ) {
System.out.println("Erro: " + e.getClass().getName() + " {" +
e.getMessage() +"]" );
}
}
/** seta a tarefa que o trabalhador ira executar **/
public void setTarefa(
) throws RemoteException {
try {
resolve = new MatrizRemoto();
} catch( Exception e ) {
System.out.println("Erro [" + e.getClass().getName() + "(" +
e.getMessage() + ")] ");
throw new RemoteException( "setTarefa: ", e );
}
}
46
/** seta o segunda matriz, que sera usada para todas as multiplicoes
* evitando que ela seja enviada todas as vezes
*/
public void init( MatrizTarefa params ) throws RemoteException {
resolve.init( params );
}
/** executa a tarefa, retornado o resultado para quem chamou **/
public Matriz executarTarefa( MatrizTarefa tarefa ) throws RemoteException {
return resolve.executarTarefa( tarefa);
}
/**retorna o status do trabalhador **/
public int getStatus() throws RemoteException {
return status;
}
/** para com o processamento remoto, se houver **/
public void stop() throws RemoteException {}
public static void main( String[] args ) {
System.out.println( ID_STRING );
Socket conexao = new Socket();
try {
//executa o registro do servidor RMI
java.rmi.registry.LocateRegistry.createRegistry(1099);
System.out.println("Registro RMI realizado.");
new TrabalhadorRemoto();
System.out.println("Tentando estabelecer conexão com " +
args[0]+":"+port);
while(true){
//tenta estabelecer conexão com o mestre
try {
conexao.connect(new InetSocketAddress(args[0], port));
System.out.println("Conexão estabelecida");
break;
} catch (IOException e1) {
try { Thread.sleep(1000);} catch (InterruptedException e)
{}
System.out.print(".");
}
}
} catch (Exception e) {
System.out.println("TrabalhadorRemoto:main - erro " +
e.getClass().getName() + " (" + e.getMessage() + ")" );
e.printStackTrace();
}
}
}
47
Apêndice 9 – Código fonte da classe TrabalhadorRemoto.
VetorTrabalho.java
package ghisi.TrabalhadorRemoto;
import ghisi.Matriz.MatrizTarefa;
import java.util.Vector;
public class VetorTrabalho {
private Vector vetor;
public VetorTrabalho( int n ) {
if(n==0){
vetor = new Vector();
} else {
vetor = new Vector(n);
}
}
public synchronized void add( MatrizTarefa t ) {
vetor.add(t);
}
public synchronized MatrizTarefa extrair() {
MatrizTarefa t;
int n = vetor.size();
if ( n > 0 ) {
t = (MatrizTarefa)(vetor.remove(0));
} else {
t = null;
}
return t;
}
public synchronized void mover( MatrizTarefa t, VetorTrabalho de ) {
(de.vetor).removeElement( t );
vetor.add(t);
}
public int getContador() { return vetor.size(); }
public boolean isEmpty() { return vetor.size() == 0; }
public String toString() {
String s = "(" + getContador() + " posições)";
return s;
}
}
48
Referências Bibliográficas
[1] Avaki. <http://www.avaki.com>. Último acesso em Novembro/2006.
[2] BOINC. <http://boinc.berkeley.edu>. Último acesso em Novembro/2006.
[3] Climateprediction.net. <http://climateapps2.oucs.ox.ac.uk/>. Último acesso em Novembro/2006.
[4] Condor. <http://www.cs.wisc.edu/condor>. Útimo acesso em Novembro/2006.
[5] DEITEL, H. M.; DEITEL, P. J. Java, como programar. 6ª edição. São Paulo: Prentice-Hall, 2005.
[6] EHRLING, E. Fast Parallel Matrix Multiplication - Strategies for Practical Hybrid Algorithms,
Stockholm: Royal Institute of Technology. Disponível em on-line em <http://www.f.kth.se/~f95eeh/exjobb/background.html>. Útimo acesso em Novembro/2006.
[7] Einstein@Home. <http://einstein.phys.uwm.edu/>. Último acesso em Novembro/2006.
[8] Folding@home<http://folding.stanford.edu/>. Último acesso em Novembro/2006
[9] FOSTER, I.;KESSELMAN, C. The Grid 2: Blueprint for a New Computing Infrastructure. Morgan
Kaufmann Publishers Inc., 2003
[10] KRAUTER, K.; BUYYA, R.; MAHESWARAN, M. A taxonomy and survey of grid resource
management systems for distributed computing: Software - Practice and Experience. Disponível em:
<http://citeseer.ist.psu.edu/article/krauter02taxonomy.html>. Último acesso em Novembro/2006.
[11] Legion. <http://legion.virginia.edu/>. Último acesso em Novembro/2006.
[12] LHC@home. <http://athome.web.cern.ch/athome>. Último acesso em Novembro/2006.
[13] MyGrid/OurGrid. <http://www.ourgrid.org>. Último acesso em Novembro/2006.
[14] Predictor@home. <http://predictor.scripps.edu>. Último acesso em Novembro/2006.
[15] SETI@home (Versão baseada no BOINC). <http://setiweb.ssl.berkeley.edu>. Último acesso em
Novembro/2006.
[16] SETI@home. <http://setiathome.ssl.berkeley.edu>. Último acesso em Novembro/2006
[17] STALLINGS, W. Data and computer communications. 7ª. edição. New Jersey: Prentice-Hall, 2004.
[18] TANENBAUM, A. S. Distributed operating systems. New Jersey: Prentice-Hall, 1995.
[19] TANENBAUM, A. S. Redes de Computadores. 4ª edição. São Paulo: Campus, 2003.
[20] TANENBAUM, A.. S. Sistemas Operacionais Modernos. Rio de Janeiro: Prentice Hall do Brasil,
2003.
[21] THAIN D., et all. "Condor and the Grid", Grid Computing: Making the Global Infrastructure a
Reality, Wiley: Berman, Fox and Hey, 2003.
[22] The Globus Project, The Open Grid Service Architecture, <www.globus.org> Último acesso em
Novembro/2006.