FUNDAÇÃO DE ENSINO EURÍPIDES SOARES DA ROCHA CENTRO UNIVERSITÁRIO EURÍPIDES DE MARÍLIA - UNIVEM PROGRAMA DE MESTRADO EM CIÊNCIA DA COMPUTAÇÃO
ANDRÉ LUIS GOBBI PRIMO
SERIALIZAÇÃO E PERFORMANCE EM AMBIENTES DISTRIBUÍDOS USANDO MPI
MARÍLIA 2005
ANDRÉ LUIS GOBBI PRIMO
SERIALIZAÇÃO E PERFORMANCE EM AMBIENTES DISTRIBUÍDOS USANDO MPI
Dissertação apresentada ao Programa de Mestrado do Centro Universitário Eurípides de Marília, mantido pela Fundação de Ensino Eurípides Soares da Rocha, para obtenção do Título de Mestre em Ciência da Computação. Orientador: Prof. Dr. Marcos Luiz Mucheroni
MARÍLIA 2005
PRIMO, André Luis Gobbi Primo Serialização e Performance em Ambientes Distribuídos Usando MPI / André Luis Gobbi Primo; orientador: Marcos Luiz Mucheroni. Marilia, SP, 2005.
118 f. Dissertação (Mestrado em Ciência da Computação) ⎯ Centro Universitário
Eurípides de Marília – Fundação de Ensino Eurípides Soares da Rocha.
1. Introdução 2. Computação Distribuída e Paralela com MPI 3.Performance em Amniente MPI usando Java. 4. Conclusão. 5. Referência Bibliográfica. Anexo.
CDD: 005.115
AGRADECIMENTOS
Aos meus familiares, em especial a minha mãe e o meu pai (em
memória), que sempre me apoiaram nos momentos de desânimo e
de alegria e deram-me forças para continuar o meu caminho.
A todos os meus amigos em especial Cesar A. Cusin (Itararé),
Ricardo Veronesi (RV), Fabio Modesto (Fabião), Marcio Cardim
(Perpétua) e Vagner Scamati, pela convivência e pela troca de
experiências que com certeza me ajudaram a evoluir um pouco
mais como ser humano e como profissional.
Aos professores pelo conhecimento passado.
Um agradecimento em especial ao meu orientador, o Professor
Marcos Luiz Mucheroni, por ser esta pessoa maravilhosa que é,
dando não só a mim, mas a todos os seus orientados o apoio,
carinho, ajuda e compreensão nos momentos difíceis,.
A Fundação Educacional de Fernandópolis, por ter disponibilizado
o uso dos Laboratórios para a construção do cluster.
PRIMO, André Luis Gobbi. Serialização e Performance Em Ambientes Distribuídos Usando MPI. 2005. 118 f. Dissertação (Mestrado em Ciência da Computação) - Centro Universitário Eurípides de Marília, Fundação de Ensino Eurípides Soares da Rocha, Marília, 2005.
RESUMO
O desenvolvimento de aplicações computacionais paralelas e distribuídas tem passado por constantes evoluções ultimamente. Várias metodologias e modelos têm sido propostos para atender novas necessidades a partir do crescimento de aplicações que usam grande volume de dados e precisam de alto desempenho. Algumas delas são: controle de tráfego aéreo, simulações biológicas, previsão do tempo, CAD, imagens médicas, etc; necessitam de máquinas com grande poder computacional e capacidade de armazenamento. As estações de processamento paralelo de grande poder computacional, na sua grande maioria, são proprietárias e possuem um alto custo, o que as torna inacessíveis para a grande maioria dos usuários, principalmente da comunidade científica. A computação paralela distribuída visa a melhorar o desempenho de aplicações que demandam maior potência computacional, através do emprego de sistemas computacionais distribuídos. Assim, os computadores que compõem o sistema são vistos como elementos de processamento de uma máquina paralela virtual. Máquinas paralelas virtuais possuem diferenças significativas em relação às máquinas verdadeiramente paralelas, principalmente no que tange à heterogeneidade dos recursos. Essas diferenças, aliadas às diferentes necessidades dos usuários que utilizam o sistema, criam necessidades adicionais para o software distribuído. Para que exista uma cooperação, deve haver a possibilidade de sincronismo e comunicação entre os processos que estão sendo executados concorrentemente. A comunicação entre os processos pode ser realizada através de passagem de mensagem utilizando o padrão para a troca de mensagens MPI. Este trabalho tem como objetivo o aprimoramento no que se diz respeito aos métodos de envio de mensagens da implementação mpiJava, uma das implementações do padrão MPI utilizando a linguagem Java, e, em colaboração com outro trabalho, o tratamento do transporte de mensagens usando serialização de objetos, avaliando o desempenho desta implementação. Palavras-chave: MPI, JMPI, MPICH, Java, mpiJava, Serialização de Objetos, Computação Paralela e Distribuída.
PRIMO, André Luis Gobbi. Serialização e Performance Em Ambientes Distribuídos Usando MPI. 2005. 118 f. Dissertação (Mestrado em Ciência da Computação) - Centro Universitário Eurípides de Marília, Fundação de Ensino Eurípides Soares da Rocha, Marília, 2005.
ABSTRACT
The distributed parallels computing applications development have passed for constant evolutions nowadays. Several methodologies and models have been proposed to attend the new necessities from the increasing of applications that use a great data capacity and need high performance. Some of them are: air traffic control, biologics simulations, forecast, CAD, medical images, etc; they need machines with more computing power and storage capacity. The parallel computing stations of high computing power, in its majority, are proprietors and have a high cost that makes them inaccessible for almost all users, mainly the scientific community. The distributed parallel computing aim to improve the applications performance that demand a higher computing power through the utilization of distributed computing system. Therefore, the computers that compose the system are seen as elements of a virtual parallel machine processing. Virtual parallel machines have meaningful differences regarding truly parallel machines, mainly in the heterogeneity resources. These differences, joint different needs from the users that use the system, create more necessities to the distributed software. To exist cooperation, it would have the synchronism possibility and communication between the processes that have been executed. The communication between the processes can be accomplished through the message passing; using the pattern to exchange messages is MPI. This work have the purpose of improvement in the message sending method of mpiJava implementation, one of the MPI pattern implementation using the Java language, and, in collaboration with another work, the treatment of messages transportation using objects serialization, assessing this implementation performance.
Keywords: MPI, JMPI, MPICH, Java, mpiJava, Objects Serialization, Parallel and Distributed Computing.
LISTA DE ILUSTRAÇÕES
Figura 1 - Demonstração do fluxo de Instruções e o fluxo de Dados em uma máquina SIMD............................. 22
Figura 2 - Demonstração o fluxo de Instruções e o fluxo de Dados em uma máquina MIMD. ............................ 23
Figura 3 - Demonstração do Fluxo de Instruções e do Fluxo de Dados em uma máquina SISD. ......................... 23
Figura 4 - Demonstração do Fluxo de Instruções e do Fluxo de Dados em uma máquina MISD......................... 24
Figura 5 - Demonstração da relação entre os modelos da taxonomia de Flynn..................................................... 24
Figura 6 - Demonstração de como são numerados os processos quando utilizado o MPI. ................................... 26
Figura 7 - Blocking Synchronous Send e Blocking Receive (BARBOSA, 2004)................................................. 29
Figura 8 - Blocking Ready Send e Blocking Receive (BARBOSA, 2004) .......................................................... 30
Figura 9 - Blocking Buffered Send e Blocking Receive (BARBOSA, 2004) ...................................................... 31
Figura 10 - Blocking Standard Send e Blocking Receive para Message <= 4K (BARBOSA, 2004) .................. 32
Figura 11 - Blocking Standard Send e Blocking Receive Message > 4K (BARBOSA, 2004) ............................. 32
Figura 12 - Non-Blocking Standard Send e Non- Blocking Receive com Message <= 4K (BARBOSA, 2004). 34
Figura 13 - Non-Blocking Standard Send e Non- Blocking Receive Message > 4K (BARBOSA, 2004) ............ 34
Figura 14 - Exemplo das rotinas do MPICH ......................................................................................................... 44
Figura 15 - Principais classes do mpiJava ............................................................................................................. 55
Figura 16 – Implemetação método public byte[] Object_Serialize ....................................................................... 65
Figura 17 – Implemetação do método public void Object_Deserialize................................................................. 66
Figura 18 – Implemetação do método public void Rsend...................................................................................... 66
Figura 19 – Implemetação do método public void Object_Ibsend ........................................................................ 67
Figura 20 – Implemetação do método public void Object_Irecv........................................................................... 67
Figura 21 – Cenário de funcionamento do projeto. ............................................................................................... 68
Figura 22 - Gráfico dos resultados obtidos com os testes entre os ambientes. ...................................................... 69
Figura 23 - Gráfico da simulação de processamento entre figura JPG e no padrão DICOM. ............................... 72
LISTA DE TABELAS Tabela 1 - Funções básicas do MPI ....................................................................................................................... 36 Tabela 2 – datatypes básicos do mpiJava .............................................................................................................. 55
SUMÁRIO
1. INTRODUÇÃO ............................................................................................................................. 10
2. COMPUTAÇÃO DISTRIBUÍDA E PARALELA COM MPI .......................................................... 14
2.1. Máquinas Paralelas ...................................................................................................14 2.1.1. Tipos de Paralelismo ........................................................................................20 2.1.2. Taxonomia de máquinas paralelas....................................................................21
2.2. MPI e MPI-Java........................................................................................................25 2.2.1. Conceitos e Funcionamento do MPI ................................................................26 2.2.2. Aspectos de implementações do MPI...............................................................38 2.2.3. Problemas de desempenho em MPI..................................................................45 2.2.4. Medidas de Desempenho e Métricas ................................................................46 2.2.5. Análise de Performance de Programas Seriais .................................................48 2.2.6. Análise de Performance de Programas Paralelos .............................................50 2.2.7. O custo da comunicação...................................................................................52
2.3. Aspectos de Implementação do MPI em Java ..........................................................54 2.3.1. mpiJava.............................................................................................................54 2.3.2. Implementações avançadas com o mpiJava .....................................................56 2.3.3. Outros Ambientes em Java ...............................................................................60
3. PERFORMANCE EM AMBIENTE MPI USANDO JAVA ............................................................ 62
CONCLUSÕES E TRABALHOS FUTUROS ....................................................................................... 74
REFERÊNCIA BIBLIOGRÁFICA ......................................................................................................... 76
ANEXO.................................................................................................................................................. 79
10
1. INTRODUÇÃO
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, o
surgimento das redes e as possibilidades de conexões. Isso também se refere à possibilidade
de distribuição do processamento entre computadores diferentes, desde processamento de e-
mail e da Web até processos complexos como previsão metereológica. Mais do que a simples
subdivisão de tarefas, este paradigma permite a repartição e a especialização das tarefas
computacionais conforme a natureza da função de cada computador. Um exemplo típico é a
chamada arquitetura cliente/servidor (SIMON, 1997).
Atualmente há uma tendência crescente de uso de sistemas de computação paralela e
distribuída em uma vasta gama de aplicações. Esses sistemas são compostos por vários
processadores que operam concorrentemente, cooperando na execução de uma determinada
tarefa. Em desenvolvimento de arquiteturas paralelas, o objetivo principal é o aumento da
capacidade de processamento, utilizando o potencial oferecido por um grande número de
processadores. A comunicação dos processadores é realizada através de redes especiais de
conexão ou por meio de uma memória compartilhada, implicando estruturas fisicamente
concentradas. Por outro lado, nas arquiteturas distribuídas o atrativo principal é a
flexibilidade, obtida pela integração de computadores de diversos tipos em um mesmo
sistema, sem restrições quanto à distribuição física dos componentes de software e quanto ao
confinamento ou distribuição física das estruturas do hardware.
O desafio é dominar essa flexibilidade e usá-la na construção de sistemas que
atendam às necessidades atuais, bem como democratizá-la, possibilitando-lhe o acesso ao
usuário comum.
11
A necessidade do paralelismo tem quase sempre recaído sobre problemas científicos
específicos, e o número de máquinas necessárias para estes problemas é sempre limitado
(tanto pelo preço quanto pelo número de centros de processamento que requerem estes
serviços). Isso inviabilizou a indústria das máquinas paralelas. Entretanto, o número cada vez
maior de dados, tanto em tamanho quanto em volume torna cada vez mais necessária a
utilização desse tipo de hardware.
Muitos problemas interessantes de otimização não podem ser resolvidos de forma
exata, utilizando a computação convencional dentro de um tempo razoável. E um grande
número de aplicações e exemplos precisam desse desempenho e dessa capacidade de
armazenamento que não é conseguida pela arquitetura de Von Neuman. Tal desempenho e
capacidade é que tornam mais fácil a utilização do software pelo usuário. Um exemplo
simples e claro, ainda ao nível de aplicações restritas (que podem ser popularizadas), são as
aplicações gráficas sofisticadas.
No âmbito do grande público, facilidades de busca e armazenamento poderão tornar
disponíveis um número grande de dados arquivados em repositórios, bibliotecas e grandes
centros de dados. Dados geográficos, meteorológicos e médicos, são os primeiros exemplos
disto. Isso já está em pleno desenvolvimento.
Embora os computadores estejam cada vez mais velozes, existem limites físicos e a
velocidade dos circuitos não poderá continuar melhorando indefinidamente a menos que haja
novos avanços tecnológicos. Por outro lado nos últimos anos se tem observado uma crescente
aceitação e uso de implementações paralelas nas aplicações de alto desempenho, motivados
pelo surgimento de novas arquiteturas que integram dezenas de processadores de baixo custo,
tais como o clustering e o grid computing.
Uma grande possibilidade de se obter máquinas paralelas a baixo custo é utilizar a
capacidade ociosa de máquinas disponíveis em ambientes industriais ou educacionais. Pode-
12
se, por exemplo, conectá-las entre si de modo rápido e simples, disponibilizando facilidades
ao usuário viabilizando os projetos de clustering.
Estes tipos de arquiteturas já estão disponíveis e, em geral, compõem ambientes com
memória distribuída. Um dos exemplos são as estações de trabalho ou PCs conectados em
rede (FILHO, 2002).
Uma outra característica importante em sistemas distribuídos são as tecnologias
utilizadas para se interligar os computadores. Uma que se destaca bastante é o PVM (Parallel
Virtual Machine), que é um software que permite que um conjunto heterogêneo ou
homogêneo de computadores seja visto como uma única máquina, sendo a portabilidade uma
de suas características principais – as bibliotecas de rotinas de comunicação entre processos
são “standard” de fato. A independência de plataforma que o PVM disponibiliza é
indubitavelmente interessante; um software pode ser executado em ambientes diferentes, este
fato gera segurança para desenvolvedores de software criarem aplicações paralelas, tendo em
vista a portabilidade possível. Uma outra tecnologia importante no conceito de computação
distribuída é o padrão MPI, que será a base de estudo para esse trabalho. O MPI é um padrão
de interface para a troca de mensagens em máquinas paralelas com memória distribuída. Não
se deve confundi-lo com um compilador ou um produto específico. O projeto do MPI teve
início em 1992 com um grupo de pesquisadores de várias nacionalidade e fabricantes de
computadores do mundo inteiro. Ele é uma tentativa de padronizar a troca de mensagem entre
equipamentos.
Um exemplo de implementação do padrão MPI é o MPICH. Foi projetada para ser
portável e eficiente. O “CH” referenciado no nome vem de Chameleon (Camaleão), símbolo
de adaptabilidade - e portanto, de portabilidade - para o ambiente onde está sendo utilizado.
As duas características acima foram desenvolvidas nas linguagens C e FORTRAN. Contudo,
com o surgimento da linguagem Java, também foram desenvolvidas ferramentas para
13
computação distribuída. O JMPI é um exemplo disso sendo um projeto de propósito
comercial da MPI Software Technology, Inc., feito a partir do trabalho de mestrado de Steven
(MORIN, 2000) com o intuito de desenvolver um sistema de passagem de mensagem em
ambientes paralelos utilizando a linguagem Java. O JMPI combina as vantagens da linguagem
Java com as técnicas de passagem de mensagem entre processos paralelos em ambientes
distribuídos (DINCER, 1998).
Uma outra implementação do MPI em Java é o mpiJava (CARPENTER, 2000).
Baseando-se neste padrão e nas tecnologias para ele desenvolvidas que este trabalho objetiva
aprimorar os métodos de envio de mensagens da implementação mpiJava. Esta
implementação será chamada de JMPI-PLUS. Isso será feito em colaboração com outro
trabalho, que trata do transporte de mensagens que usam serialização de objetos e avaliam o
desempenho dessa implementação.
Serão apresentados resultados experimentais da execução de uma aplicação Java
utilizando o JMPI-PLUS e uma aplicação em C utilizando MPICH, ambos em uma
arquitetura Beowulf.
Este documento está organizado da seguinte forma: no Capítulo 2 será apresentada as
características da computação distribuída e paralela utilizando MPI, bem como uma descrição
do padrão MPI levando em consideração a implementação do MPI realizada na linguagem C
e na linguagem Java; no Capítulo 3 será apresentado o objetivo do trabalho, os resultados e
testes obtidos com o MPICH e o JMPI-PLUS com relação ao transporte de imagens médicas,
será apresentado também as principais classes do JMPI-PLUS; no Capítulo 4 são apresentadas
as conclusões e as propostas de trabalhos futuros.
14
2. COMPUTAÇÃO DISTRIBUÍDA E PARALELA COM MPI
Para a compreensão da computação paralela e concorrente é necessária a
compreensão das classificações existentes, as bibliotecas e ambientes paralelos mais
populares que tornaram viável este avanço desse tipo de tecnologia nos últimos anos.
A seguir analisam-se algumas das diversas formas de classificação de máquinas
paralelas.
2.1. Máquinas Paralelas Segundo TANENBAUM, 1999, os computadores paralelos, do ponto de vista
prático, podem ser divididos em duas categorias principais SIMD (Simple Instruction Multiple
Data) e MIMD (Multiple Instructions Multiple Data).
As máquinas SIMD executam uma instrução de cada vez, sobre diversos conjuntos
de dados; nessa categoria estão as máquinas vetoriais e as matriciais. As máquinas MIMD
rodam programas diferentes, em processadores diferentes, e podem ser divididas em
multicomputadores que compartilham a memória principal e os multicomputadores que não
compartilham nenhuma memória. Os multicomputadores podem ser divididos em máquinas
simples conectadas em redes, COWs (Cluster of Workstations) e MPPs (Massively Parallel
Processor).
Os MPPs são os supercomputadores que utilizam processadores padrão como o IBM
RS/6000, a família Dec Alpha ou a linha Sun UltraSPARC. Uma outra característica dos
MPPs é o uso de redes de interconexão proprietárias, de alta performance, baixa latência e
banda passante alta.
Um COW é composto de algumas centenas de PCs ou estações de trabalho, ligados
por uma rede comercial. Este ambiente paralelo já é uma realidade em empresas e
15
universidades. Eles são mais acessíveis que outros computadores paralelos de alto custo
comercial. Deng & Korobka no ano de 2001 (IGNÁCIO, 2002) apresentaram um sistema
COW chamado Galaxi, implementado sobre uma rede de alta velocidade (Fast e Gigabit
Ethernet, com velocidades superiores a 100 MBPS). No ano de 2002, a NTT Data Corp
colocou em teste uma super-rede de computadores com a Intel, a Silicon Graphics (SGI), a
Nippon Telegraph e a Telephone East Corp., chamada NTT East, envolvendo cerca de um
milhão de PCs, com o intuito de criar um supercomputador virtual com capacidade de
processamento cinco vezes maior que o mais rápido computador (MIYAKE, 2001).
Além da rede de comunicação, é necessária uma camada de software que possa
gerenciar o uso paralelo destas máquinas. Para tanto existem bibliotecas especializadas para
tratamento da comunicação entre processos e a sincronização de processos concorrentes. De
uma forma geral, as bibliotecas são utilizadas sem maior dificuldade tanto nas máquinas MPP
quanto nas máquinas COW, de maneira que as aplicações podem ser transferidas entre ambas
as plataformas.
Os dois sistemas baseados na troca de mensagens mais usados em
multicomputadores são o MPI (Message Passing Interface) e PVM (Parallel Virtual
Machine). O PVM é um sistema de mensagens de domínio público, projetado inicialmente
para rodar em máquinas COW, tendo diversas modificações implementadas para rodar em
máquinas MPP. Neste trabalho será apresentado o MPI, que oferece mais recursos que o
PVM, com mais opções e mais parâmetros por chamada, e que vem se tornando o padrão das
implementações paralelas do tipo MPP e do tipo COW (IGNÁCIO, 2002).
Um dos principais objetivos do desenvolvimento de aplicações paralelas é a redução
do tempo computacional. Contudo não se deve buscar simplesmente otimizar uma simples
medida de aceleração (razão entre o tempo do programa seqüencial e o tempo de execução da
16
versão paralela) em detrimento a outros aspectos da comunicação. Devem ser também
consideradas outras medidas como a eficiência e escalabilidade.
A eficiência é usada para medir a qualidade de um algoritmo paralelo e é definida
como a fração do tempo em que os processadores estão ativos (razão entre a aceleração e o
número de processadores) caracterizando a utilização dos recursos computacionais,
independentemente do tamanho do problema. A escalabilidade é uma medida de desempenho
que indica a variação do tempo de execução e da aceleração, com o acréscimo do número de
processadores e/ou tamanho do problema (FILHO, 2002).
Uma outra análise importante a ser feita é com relação aos ambientes de
programação paralela.
Para se utilizar e explorar todos os recursos disponíveis em um computador paralelo
deve-se conhecer as linguagens e os ambientes de programação disponíveis, bem como os
modelos e paradigmas de programação existentes. Tais conhecimentos são importantes para
escrever programas paralelos eficientes. A classificação dos ambientes está baseada nos
paradigmas de programação que podem ser utilizados em cada um deles.
Um ambiente importante é o de memória compartilhada, que é caracterizado pela
presença de vários processadores, compartilhando o acesso a uma única memória. Os
processadores podem funcionar de forma independente, mas qualquer mudança no conteúdo
das variáveis armazenadas na memória será visível a todos os outros processadores (FILHO,
2002).
Baseado no tempo de acesso à memória, gasto por cada processador pertencente ao
sistema, pode-se dividir as máquinas de memória compartilhada em 2 classes: UMA (Uniform
Memory Access) e NUMA (Non Uniform Memory Access) (LAINE, 2003).
Nas máquinas pertencentes à arquitetura UMA, o tempo gasto no acesso a uma
mesma posição de memória é igual para qualquer processador. Infelizmente, com o aumento
17
natural do número de processadores que compõem essas máquinas, o barramento de acesso à
memória pode ficar saturado e se tornar um gargalo para o sistema.
A arquitetura NUMA surgiu para solucionar a saturação de barramento mencionada
anteriormente. Cada processador possui um módulo de memória associado, utilizado somente
por tarefas locais. O conjunto de todos esses módulos de memória formam a memória global
do sistema. Ao contrário das máquinas da arquitetura UMA, nessa classe de memória
compartilhada os processadores não possuem o mesmo tempo de acesso à memória. A
comunicação entre os processadores acontece através da leitura e escrita de dados na memória
compartilhada pelo sistema.
O ambiente de memória compartilhada oferece algumas vantagens e desvantagens
sobre os demais ambientes de programação paralela.
Como vantagem deste ambiente pode-se citar:
• existência de um espaço de endereçamento global torna a programação
nesse ambiente bastante amigável para o programador; • _
• com a memória próxima à CPU, o compartilhamento dos dados entre as
tarefas é rápido e uniforme. (LAINE, 2003)
Como desvantagem deste ambiente pode-se citar: • _
• baixa escalabilidade do número de processadores, uma vez que o aumento
demasiado de CPUs pode congestionar o barramento de acesso à memória;
• dificuldade e necessidade em manter a coerência de cache; • _
• sincronização entre os acessos à memória global é de responsabilidade do
programador; • _
• o preço para projetar e produzir máquinas de memória compartilhada é
muito alto. (LAINE, 2003)
Um outro ambiente importante é o de Memória Distribuída.
18
Nesse modelo, cada processador possui sua própria memória local e não existe uma
memória compartilhada pelo sistema. Com isso, os processadores podem trabalhar
independentemente, acessando somente sua memória local sem afetar os dados utilizados
pelos demais processadores (FILHO, 2002).
Eventualmente, um processador precisa acessar dados armazenados na memória
local de outros processadores. Quando isso for necessário, cabe ao programador definir,
explicitamente, como e quando o dado será acessado. Para isso, barreiras de sincronização
entre as tarefas que estão sendo executadas em cada processador devem ser definidas, a fim
de coordenar as atividades (LAINE, 2003).
Esses ambientes têm impulsionado a utilização dos paradigmas de passagem de
mensagem, tais como o PVM e o MPI. Esses paradigmas utilizam primitivas de comunicação,
como send e receive, para realizar a transferência de dados ou de mensagens entre os
processadores distribuídos pelo sistema (LAINE, 2003).
Esse ambiente também possui algumas vantagens: _
• a quantidade de memória do sistema aumenta com a adição de novas
CPUs, podendo melhorar o desempenho das aplicações. Para inserir um
novo processador ao sistema computacional também é necessário
adicionar uma memória local; • _
• cada processador pode acessar rapidamente sua memória local, sem
nenhuma interferência dos demais processadores; • _
• ausência da necessidade de manter a coerência de cache. (LAINE, 2003)
Algumas desvantagens também são observadas neste ambiente: _
• tempo de acesso à memória não é uniforme (NUMA); • _
19
• muitos detalhes associados à comunicação existente entre os processadores
ficam sob a responsabilidade do programador; • _
• dificuldade em mapear estruturas de dados já existentes em ambientes de
memória global. (LAINE, 2003)
Existem outras formas mais práticas que não estão diretamente ligadas à taxonomia
anterior, que embora acadêmica, possuem aspectos específicos do paralelismo e devem ser
analisadas e pormenorizadas.
Computação Paralela refere-se ao conceito de aumento de velocidade na execução de
um programa através da divisão deste em pequenos fragmentos que são distribuídos e
processados paralelamente, cada fragmento em um processador. Com isto obtém-se um ganho
de performance na execução de uma tarefa. A premissa é a de que se executando uma tarefa
dividida entre vários processadores se conseguirá executa-la muito mais rápido (DIETZ,
1998).
Esses sistemas são compostos por vários processadores que operam
concorrentemente na execução de uma determinada tarefa. Nas chamadas arquiteturas
paralelas o objetivo principal é o aumento da capacidade de processamento, utilizando o
potencial oferecido por um grande número de processadores (STERLING, 1999).
O processamento paralelo pode ser entendido como um problema que pode ser
quebrado em diversas partes menores. Esses pedaços menores são processados
individualmente por diversos processadores, paralelamente, aumentando assim a performance
e diminuindo o tempo de processamento. Esse tipo de processamento é amplamente usado,
atualmente, nas máquinas chamadas de supercomputadores. (BRUNO, 2003)
20
2.1.1. Tipos de Paralelismo Para entender o que é processamento paralelo, deve-se entender melhor, os diversos
tipos de paralelismo tanto no sentido prático quanto nos modelos teóricos.
a) Paralelismo de dados
Nesse tipo de paralelismo os dados do problema são divididos em grupos e as tarefas
são entregues a cada componente da aplicação. Ele é bastante comum. Hoje podem ser
encontrados problemas cujo paralelismo pode ser expandido até milhões de componentes.
Presume-se que até o ano 2007 podem surgir problemas com paralelismo na ordem dos
bilhões de componentes. Um exemplo deste tipo de paralelismo é o algoritmo para calcular as
chaves de um sistema de criptografia como RSA. Neste caso, cada componente da aplicação
tenta descobrir os fatores da chave em um subconjunto do espaço total de soluções
(IGNÁCIO, 2002).
Problemas como este, são resolvidos eficientemente na Internet, onde são necessárias
grande quantidade de recursos e a comunicação entre os componentes é praticamente nula.
Outros problemas deste tipo podem precisar de um maior grau de comunicação entre os
componentes. Nesse caso, supercomputadores e máquinas paralelas virtuais em redes mais
rápidas podem ser as melhores opções (FILHO, 2002).
b) Paralelismo funcional
Paralelismo tipo linhas de controle (threads). Na solução do problema são
superpostas várias operações, por exemplo, descomprimir uma imagem e receber
simultaneamente um arquivo através da rede. Em geral, os componentes nesta forma de
paralelismo são de tamanho moderado (maior do que algumas instruções, menor que uma
aplicação). Tipicamente, este tipo de paralelismo não aparece em quantidades muito grandes
21
nas aplicações. Este tipo de problema é implementado melhor em máquinas paralelas com
memória compartilhada (BRUNO, 2003).
c) Paralelismo de objetos
Paralelismo presente em simuladores de eventos discretos. Por exemplo, em uma
simulação militar. Os objetos podem ser veículos, soldados, armamentos, etc. Este tipo de
paralelismo é parecido com o paralelismo de dados, apesar das unidades de dados aqui serem
maiores. Em geral, este tipo de paralelismo requer grande interação entre os componentes;
portanto os melhores resultados são atingidos em supercomputadores e em máquinas paralelas
virtuais implementadas sobre redes rápidas (BRUNO, 2003).
Entretanto existe uma forma mais tradicional de classificar as máquinas paralelas
quanto ao hardware, denominado Taxonomia de Flynn.
2.1.2. Taxonomia de máquinas paralelas De acordo com a taxonomia de Flynn, podem-se dividir os sistemas computacionais
em quatro grandes grupos: SIMD, MIMD, SISD e MISD.
Esta classificação tem uma função apenas didática, pois alguns modelos não são
práticos embora representem um exercício intelectual interessante.
No primeiro modelo de Flynn, uma instrução trata múltiplos dados, sendo chamada
SingleI Instruction Multiple Data stream. Trata-se de uma arquitetura de computadores que
executa uma operação sobre conjuntos múltiplos de dados. Um computador (ou processador)
opera como controlador, enquanto os outros, ligados a ele, executam a mesma instrução
(IGNÁCIO, 2002).
Processadores executam, em sincronismo, a mesma instrução sobre dados diferentes.
Essa taxonomia utiliza vários processadores especiais mais simples, organizados em geral de
22
forma matricial. Ela é muito eficiente em aplicações onde cada processador pode ser
associado a uma sub-matriz independente de dados (processamento de imagens, algoritmos
matemáticos, etc.), a Figura 1 ilustra o modelo de uma maquina SIMD.
Figura 1 - Demonstração do fluxo de Instruções e o fluxo de Dados em uma máquina SIMD.
O segundo modelo de Flynn, utiliza múltiplas instruções tratando múltiplos dados, a
chamada Multiple Instruction, Multiple Data stream. Uma máquina MIMD é um conjunto de
processadores que executa simultaneamente diferentes seqüências de instruções sobre
diferentes dados. Os sistemas multiprocessadores e sistemas paralelos podem ser inclusos
nessa categoria (IGNÁCIO, 2002).
A grande maioria dos super computadores atualmente pode ser incluída nesta
categoria. A Figura 2 ilustra o modelo de uma maquina MIMD.
23
Figura 2 - Demonstração o fluxo de Instruções e o fluxo de Dados em uma máquina MIMD.
O terceiro modelo de Flynn utiliza uma simples instrução tratando simples dados.
Por isso é chamada de SISD, Single Instruction, Single Data stream. Um único processador
executa uma única seqüência de instruções que operam sobre dados armazenados numa única
memória. Veja-se abaixo um exemplo de computador seqüencial simples, ou monoprocessado
(IGNÁCIO, 2002). A Figura 3 ilustra o modelo de uma maquina SISD.
Figura 3 - Demonstração do Fluxo de Instruções e do Fluxo de Dados em uma máquina SISD.
O quarto modelo de Flynn utiliza múltiplas instruções tratando simples dados.
Nomeada MISD, Multiple Instruction, Single Data Stream. Sua performance ocorre quando
um único dado é transmitido a um conjunto de processadores, cada um dos quais executando
24
diferentes instruções. Essa categoria aproxima-se de uma arquitetura denominada de array
sistólico (BRUNO, 2003). A Figura 4 ilustra o modelo de uma maquina MISD.
Figura 4 - Demonstração do Fluxo de Instruções e do Fluxo de Dados em uma máquina MISD.
Para um melhor entendimento dos modelos da taxonomia de Flynn a figura abaixo
ilustra a relação dos fluxos de dados e instruções de cada modelo.
Single InstructionSingle Data
SISD
Single InstructionMultiple Data
SIMD
Multiple InstructionSingle Data
MISD
Multiple InstructionMultiple Data
MIMD
Fluxo de dados
Fluxo de instruções
Único Múltiplo
Único
Múltiplo
Distribuído
Single InstructionSingle Data
SISD
Single InstructionMultiple Data
SIMD
Multiple InstructionSingle Data
MISD
Multiple InstructionMultiple Data
MIMD
Single InstructionSingle Data
SISD
Single InstructionMultiple Data
SIMD
Multiple InstructionSingle Data
MISD
Multiple InstructionMultiple Data
MIMD
Fluxo de dadosFluxo de dados
Fluxo de instruçõesFluxo de instruções
Único Múltiplo
Único
Múltiplo
Distribuído
Figura 5 - Demonstração da relação entre os modelos da taxonomia de Flynn.
25
2.2. MPI e MPI-Java O MPI é um padrão de interface para a troca de mensagens em máquinas paralelas
com memória distribuída e não se devendo confundi-lo com um compilador ou um produto
específico.
Antes de se mostrar as características básicas do MPI, é apresentada uma breve
descrição do surgimento do mesmo.
A MPI (MPI, 1995) e uma interface padrão de troca de mensagens estabelecida pelo
Fórum da MPI , composto pela maioria dos fabricantes de computadores paralelos e
pesquisadores de universidades, laboratórios e da indústria, em especial nos Estados Unidos.
O grupo inicial de construção do MPI era de aproximadamente 60 pessoas,
pertencentes a 40 instituições, principalmente dos Estados Unidos e Europa. A maioria dos
fabricantes de computadores paralelos participou, de alguma forma, da elaboração do MPI,
juntamente com pesquisadores de universidades, laboratórios e autoridades governamentais.
O início do processo de padronização aconteceu no seminário sobre Padronização para Troca
de Mensagens em ambiente de memória distribuída, realizado pelo Center for Research on
Parallel Computing, em abril de 1992. Nesse seminário, as ferramentas básicas para uma
padronização de troca de mensagens foram discutidas e foi estabelecido um grupo de trabalho
para dar continuidade à padronização. O desenho preliminar foi realizado por Dongarra,
Hempel, Hey e Walker em novembro 1992, sendo a versão revisada finalizada em fevereiro
de 1993 ( FILHO, 2002).
Em novembro de 1992 foi decidido colocar o processo de padronização numa base
mais formal, adotando-se o procedimento e a organização do HPF (High Performance
Fortran Forum). O projeto do MPI padrão foi apresentado na conferência Supercomputing
93, realizada em novembro de 1993, do qual se originou a versão oficial do MPI em 5 de
maio de 1994 ( FILHO, 2002).
26
Ao final do encontro do MPI-1 (1994) foi decidido que se deveriam esperar mais
experiências práticas com o MPI. A sessão do Forum-MPIF (CARPENTER , 1998) de
Supercomputing 94 possibilitou a criação do MPI-2, que teve inicio em abril de 1995. No
SuperComputing 96 foi apresentada a versão preliminar do MPI-2. Em abril de 1997 o
documento MPI-2 foi unanimemente votado e aceito (FILHO, 2002).
2.2.1. Conceitos e Funcionamento do MPI Alguns conceitos são importantes na elaboração de processos MPI.
Um conceito importante em processamento MPI é o Rank , associado ao número de
processos.
Todo o processo tem uma única identificação, atribuída pelo sistema quando o
processo é inicializado. Essa identificação é contínua e começa no zero até n - 1 processos a
Figura 6 ilustra a divisão dos processos.
Figura 6 - Demonstração de como são numerados os processos quando utilizado o MPI.
O segundo conceito é associado ao conjunto ordenado de processos e denomina-se:
Group.
Código
Ação
Processo 0
dado
buf
Processo();
Processo 1
dado
Processo()
Processo 2
dado
Processo()
27
Group é um conjunto ordenado de N processos. Todo e qualquer group é associado a
um “communicator”, e inicialmente, todos os processos são membros de um group com um
“communicator” já pré-estabelecido (MPI_COMM_WORLD).
Outro conceito importante que define uma coleção de processos podendo comunicar
entre si é o Communicator.
O “communicator” define uma coleção de processos (group), que poderão se
comunicar entre si (contexto).
O MPI utiliza essa combinação de group e contexto para garantir uma comunicação
segura e evitar problemas no envio de mensagens entre os processos.
É possível que uma aplicação de usuário utilize uma biblioteca de
rotinas, que por sua vez, utilize “message-passing”.
Esta rotina pode usar uma mensagem idêntica à mensagem do
usuário.
A maioria das rotinas de MPI exige que seja especificado um “communicator” como
argumento. MPI_COMM_WORLD é o communicator pré-definido que inclui todos os
processos definidos pelo usuário, numa aplicação MPI (MPI, 1995)(FILHO, 2002).
Outro conceito que representa um elemento de memória é o Aplicatiom Buffer que
pode se dar como exemplo uma variável que armazena um dado que o processo necessita
enviar ou receber.
O System Buffer também é considerado um elemento importante e representa um
endereço de memória reservado pelo sistema para armazenar mensagens.
Dependendo do tipo de operação de send/receive, o dado no “aplication buffer” pode
necessitar ser copiado de/para o “system buffer” (“Send Buffer” e “Receive Buffer”). Neste
caso tem-se comunicação assíncrona (FILHO, 2002).
28
Um outro conceito importante está relacionado a comunicação “Point-to-Point”.
Existem várias opções de programação utilizando-se comunicação “Point-to-Point”, que
determinam como o sistema irá trabalhar a mensagem, existem quatro modos de
comunicação: synchronous, ready, buffered, e standard, e dois modos de processamento:
"blocking" e "non-blocking".
Com isso é observado que existem quatro rotinas “blocking send” e quatro rotinas
“non-blocking send”, correspondentes aos quatro modos de comunicação. A rotina de
“receive” não especifica o modo de comunicação. Simplesmente, ou a rotina é “blocking” ou,
“non- blocking” (FILHO, 2002).
Para entender melhor os modos de comunicação, será apresentado a seguir uma
breve descrição de cada modo.
Primeiro será apresentado os modo de processamento “blocking”.
Existem quatro tipos de "blocking send", uma para cada modo de comunicação, mas
apenas um "blocking receive" para receber os dados de qualquer "blocking send".
Abaixo, seguem exemplos de blocking receive em C e FORTRAN.
C int MPI_ Recv(* buf, count, datatype, source, tag, comm, status)
FORTRAN call MPI_ RECV( buf, count, datatype, source, tag, comm, status, ierror)
O primeiro modo é “blocking synchronous send”, neste modo quando um MPI_
Ssend é executado, o processo que envia avisa ao processo que recebe que uma mensagem
está pronta e esperando por um sinal de OK, para que então, seja transferido o dado.
OBS: “System overhead” ocorre devido a cópia da mensagem do “send buffer” para a
rede e da rede para o “receive buffer”.
29
“Synchronization overhead” ocorre devido ao tempo de espera de um dos processos
pelo sinal de OK de outro processo. Neste modo, o “Synchronization overhead”, pode ser
significante (MPI, 1995)(FILHO, 2002), a Figura 7 ilustra o blocking synchronous send.
Abaixo, seguem exemplos de chamadas de funções em C e FORTRAN
C int MPI_ Ssend(* buf, count, datatype, dest, tag, comm)
FORTRAN call MPI_ SSEND( buf, count, datatype, dest, tag, comm, ierror)
Figura 7 - Blocking Synchronous Send e Blocking Receive (BARBOSA, 2004)
O segundo modo é denominado “Blocking Ready Send”. Quando um MPI_ Rsend é
executado a mensagem é enviada imediatamente para a rede. É exigido que um sinal de OK
do processo que irá receber, já tenha sido feito. Este modo tem como objetivo minimizar o
“System overhead” e o “Synchronization overhead” por parte do processo que envia. A única
espera ocorre durante a cópia do "send buffer" para a rede. Este modo somente deverá ser
utilizado se o programador tiver certeza que uma MPI_ Recv, será executado antes de um
MPI_ Rsend (FILHO, 2002), a Figura 8 ilustra o Blocking Ready Send.
S
R
MPI_SSEND
MPI_RECV
SENDsystem
sync
RECEIVEsystem
synch
OVERHEAD
30
Abaixo, seguem exemplos de chamadas de funções em C e FORTRAN
C int MPI_ Rsend(* buf, count, datatype, dest, tag, comm)
FORTRAN call MPI_ RSEND( buf, count, datatype, dest, tag, comm, ierror)
Figura 8 - Blocking Ready Send e Blocking Receive (BARBOSA, 2004)
O terceiro modo é o “Blocking Buffered Send”. Quando um MPI_ Bsend é executado
a mensagem é copiada do endereço de memória (“Aplication buffer”) para um “buffer”
definido pelo usuário, e então, retorna a execução normal do programa, e aguardado um sinal
de OK do processo que irá receber, para descarregar o “buffer”. Neste modo ocorre “System
overhead” devido a cópia da mensagem do “Aplication buffer” para o “buffer” definido pelo
usuário. Já o “Synchronization overhead”, não existe no processo que envia, mas é
significativo no processo que recebe, caso seja executado o “receive” antes de um “send”.
Neste modo, o usuário é responsável pela definição de um “buffer”, de acordo com o
tamanho dos dados que serão enviados. Utilizando as rotinas: MPI_ Buffer_ attach e MPI_
Buffer_ detach, a Figura 9 ilustra o Blocking Buffered Send .
S
R
MPI_SEND
MPI_RECV
SEND
system
synch
RECIVE
system
synch
OVERHEAD
31
Abaixo, seguem exemplos de chamadas de funções em C e FORTRAN
C int MPI_ Bsend(* buf, count, datatype, dest, tag, comm) FORTRAN call MPI_ BSEND( buf, count, datatype, dest, tag, comm, ierror)
Figura 9 - Blocking Buffered Send e Blocking Receive (BARBOSA, 2004)
O quarto modo para o envio de mensagem é o “Blocking Standard Send” neste
modo, será necessário analisar o tamanho da mensagem que será transmitida, que por sua vez
varia de acordo com o número de processos iniciados. O padrão é um “buffer” de 4Kbytes.
Quando MPI_Send é executado com Message <= 4K, a mensagem é imediatamente
transmitida para rede, e então, para um “System buffer” do processo que irá receber a
mensagem. Neste caso o “Synchronization overhead” é reduzido ao preço de se aumentar o
“System overhead” devido as cópias extras que podem ocorrer, para o buffer (FILHO, 2002),
a Figura 10 ilustra este modo.
S
R
MPI_BSEND
MPI_RECV
SEND
syste
sync
RECIVE
syste
synch
OVERHEAD
32
Figura 10 - Blocking Standard Send e Blocking Receive para Message <= 4K (BARBOSA, 2004)
Quando MPI_ Send é executado com Message > 4K a mensagem é transmitida
essencialmente igual ao modo “Synchronous” (FILHO, 2002), a Figura 11 ilustra este modo.
Figura 11 - Blocking Standard Send e Blocking Receive Message > 4K (BARBOSA, 2004)
Abaixo, seguem exemplos de chamadas de funções em C e FORTRAN:
C int MPI_ Send(* buf, count, datatype, dest, tag, comm) FORTRAN call MPI_ SEND( buf, count, datatype, dest, tag, comm, ierror)
S R
MPI_SEND (blocking standart
MPI_RECVbuffer
Transferência de dados da fonte completo
a tarefa continua quando os dados do buffer do usuários são transferidos
S R
MPI_SEND (blocking standart send)
MPI_RECa transferência não começa até que o MPI_RECV correspondente seja postado
Transferência de dados da fonte completo
a tarefa continua quando os dados do buffer do usuários são transferidos
Tarefas em espera
espera
33
Agora será apresentado o modo de processamento “Non-Blocking”.
Em uma comunicação “blocking”, a execução do programa é suspensa até o “system
buffer” estar pronto para uso. Quando se executa um “blocking send”, significa que o dado
tem que ter sido enviado do “system buffer” para a rede, liberando o “buffer” para ser
novamente utilizado (FILHO, 2002).
Por outro lado em uma comunicação “non-blocking”, a execução do programa
continua imediatamente após ter sido iniciado a comunicação. O programador não tem idéia
se a mensagem já foi enviada ou recebida. Neste tipo de comunicação é necessário bloquear a
continuação da execução do programa, ou averiguar o status do “system buffer”, antes de
reutilizá-lo. Para isso pode-se utilizar os comandos MPI_ Wait e MPI_ Test (FILHO, 2002).
Todas as sub-rotinas “non-blocking”, possuem o prefixo MPI_ Ixxxx, e mais um
parâmetro para identificar o status.
Segue abaixo o exemplo em C e FORTRAN das rotinas de “non-blocking”.
Non- Blocking Synchronous Send
C int MPI_ Issend(* buf, count, datatype, dest, tag, comm, *request) FORTRAN call MPI_ ISSEND( buf, count, datatype, dest, tag, comm, request, ierror)
Non- Blocking Ready Send
C int MPI_ Irsend(* buf, count, datatype, dest, tag, comm,* request) FORTRAN call MPI_ IRSEND( buf, count, datatype, dest, tag, comm, request, ierror)
Non- Blocking Buffered Send
C int MPI_ Ibsend(* buf, count, datatype, dest, tag, comm, *request) FORTRAN call MPI_ IBSEND( buf, count, datatype, dest, tag, comm, request, ierror)
Non- Blocking Standard Send
C int MPI_ Isend(* buf, count, datatype, dest, tag, comm, *request) FORTRAN call MPI_ ISEND( buf, count, datatype, dest, tag, comm, request, ierror)
Non-Blocking Receive
C int MPI_ Irecv(* buf, count, datatype, source, tag, comm, *request)
34
FORTRAN call MPI_ IRECV( buf, count, datatype, source, tag, comm, request, ierror)
Figura 12 - Non-Blocking Standard Send e Non- Blocking Receive com Message <= 4K (BARBOSA, 2004)
Figura 13 - Non-Blocking Standard Send e Non- Blocking Receive Message > 4K (BARBOSA, 2004)
O Standard Receive é um conceito bastante importante neste contexto, pois é uma
operação básica de recebimento de mensagens usado para aceitar os dados enviados por
qualquer outro processo. Pode ser “blocking” e “non-blocking”.
S R
MPI_SEND (nonblocking standart send)
MPI_IRECV
MPI_WAIT
MPI_WAIT a transferência do buffer pode ser proibido se MPI_IRECV for postado muito cedo
SR
MPI_ISEND (nonblocking standart
MPI_IREC
a transferência não começa até que o MPI_RECV correspondente seja postado
Transferência de dados da fonte completo
não há interrupção se o processo
Tarefas em espera
MPI_WAI
MPI_WAI
35
E por fim o conceito de Return Code, que é um valor inteiro retornado pelo sistema
para indicar a finalização da sub-rotina.
No padrão MPI, uma aplicação é constituída por um ou mais processos que se
comunicam, acionando-se funções para o envio e recebimento de mensagens entre os
processos. Inicialmente, na maioria das implementações, um conjunto fixo de processos é
criado. Porém, esses processos podem executar diferentes programas. Por isso, o padrão MPI
é algumas vezes referido como MPMD (multiple program multiple data) (FILHO, 2002).
Elementos importantes em implementações paralelas e a comunicação dos dados
entre processos paralelos e o balanceamento da carga. Dado o fato do número de processos no
MPI ser normalmente fixo, neste texto é enfocado o mecanismo usado para comunicação de
dados entre processos. Os processos podem usar mecanismos de comunicação ponto a ponto
(operações para enviar as mensagens de determinado processo a outros). Um grupo de
processos pode invocar operações coletivas (collective) de comunicação para executar
operações globais. O MPI é capaz de suportar comunicação assíncrona e programação
modular, através de mecanismos de comunicadores (communicator) que permitem ao usuário
MPI definir módulos que encapsulem estruturas de comunicação interna (MPI, 1995).
Os algoritmos que criam um processo para cada processador podem ser
implementados, diretamente, utilizando-se comunicação ponto a ponto ou coletivas. Os
algoritmos que implementam a criação de tarefas dinâmicas ou que garantem a execução
concorrente de muitas tarefas, num único processador, precisam de um refinamento nas
implementações com o MPI.
Embora o MPI seja um sistema complexo, um amplo conjunto de problemas pode ser
resolvido usando-se apenas seis funções, que servem basicamente para: iniciar, terminar,
executar e identificar processos, enviando e recebendo mensagens.
A Tabela 1 mostra as seis funções básicas do MPI.
36
Tabela 1 - Funções básicas do MPI
MPI_INIT
MPI_FINALIZE
MPI_COMM_SIZE
MPI_COMM_RANK
MPI_SEND
MPI_RECV
O MPI_INIT é a primeira rotina do MPI a ser chamada por cada processo, ela
estabelece o ambiente necessário para executar o MPI, sincroniza todos os processos na
inicialização de uma aplicação MPI. Abaixo segue um exemplo desenvolvido na linguagem C
(MPI, 1995).
int MPI_Init (int *argc, char ***argv)
argc = Apontador para um parâmetro da função main; argv = Apontador para um parâmetro da função main;
O MPI_FINALIZE é responsável por finalizar o processo para o MPI, é a última
rotina MPI a ser executada por uma aplicação MPI, sincroniza todos os processos na
finalização de uma aplicação MPI. Abaixo segue um exemplo desenvolvido na linguagem C.
int MPI_Finalize ( )
O MPI_COMM_SIZE é responsável pó retornar o número de processos dentro de
um grupo de processos. Segue abaixo um exemplo desenvolvido na linguagem C.
int MPI_Comm_size (MPI_Comm comm, int *size) comm = MPI communicator.
size = Variável inteira de retorno com o número de processos inicializados durante uma aplicação MPI.
37
O MPI_COMM_RANK tem como finalidade identificar o processo, dentro de um
grupo de processos, é caracterizado por um valor inteiro que varia de 0 a n-1 processos. Segue
abaixo um exemplo deste método desenvolvido na linguagem C.
int MPI_Comm_rank (MPI_Comm, int *rank) rank = Variável inteira de retorno com o número de identificação do processo.
O MPI_SEND é responsável por enviar as mensagens. Segue abaixo um exemplo de
como este método é implementado.
int MPI_Send (void *sndbuf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) sndbuf = Endereço inicial de dados, que será enviado. Endereço do “aplication buffer”. count = Número de elementos a serem enviados. datatype = Tipo do dado. dest = Identificação do processo destino. tag = Rótulo da mensagem. comm = MPI communicator.
O MPI_RECV é responsável por receber as mensagens. Veja abaixo um exemplo da
implementação deste método.
int MPI_Recv (void *recvbuf, int count, MPI_Datatype datatype, int source, int tag, *status, MPI_Comm comm) recvbuf = Variável indicando o endereço do “aplication buffer”. count = Número de elementos a serem recebidos. datatype = Tipo do dado. source = Identificação da fonte. Obs.: MPI_ANY_SOURCE. tag = Rótulo da mensagem. Obs.: MPI_ANY_TAG. comm = MPI communicator. status = Vetor com informações de source e tag
Todos os procedimentos acima, exceto os dois primeiros, possuem um manipulador
de comunicação como argumento. Esse manipulador identifica o grupo de processos e o
38
contexto das operações que serão executadas. Os comunicadores proporcionam o mecanismo
para identificar um subconjunto de processos, durante o desenvolvimento de programas
modulares, assegurando que as mensagens, planejadas para diferentes propósitos, não sejam
confundidas.
2.2.2. Aspectos de implementações do MPI Os ambientes demonstrados neste trabalho são o LAN-MPI e o MPICH, sendo este
último de maior importância para o nosso trabalho por possuir uma implementação do padrão
MPI bem sólida e consistente.
2.2.2.1. LAM-MPI
O LAM-MPI é uma implementação de alta qualidade do padrão MPI. LAM-MPI
provê alto desempenho em uma variedade de plataformas, desde pequenos agrupamentos de
CPU para máquinas SMP interligados com redes de alta velocidade, até ambientes
heterogêneos. Além de alto desempenho, LAM provê várias utilidades para o
desenvolvimento de grandes aplicações em MPI (MPI-LAM).
LAM-MPI provê uma implementação completa do padrão MPI1.2, enquanto
assegurando compatibilidade de código fonte com qualquer outra implementação do MPI.
Uma simples recompilação do código fonte faz com que a compatibilidade seja total.
O LAM-MPI também da suporte a uma grande parte do padrão MPI-2. A lista abaixo
demonstra algumas características suportadas:
Criação e Administração de processos
Comunicação unilateral (implementação parcial)
MPI I/O (usando ROMIO)
Miscelânea de MPI-2:
mpiexec
39
Linhas de apoio (MPI_THREAD_SINGLE -
MPI_THREAD_SERIALIZED)
Usa funções de terminação
Interoperabilidade de linguagem
Ligações de C++
A seguir será mostrado uma breve descrição das principais características do LAM-
MPI.
a) Ponto de chaecagem e restart
É uma das características implementadas pelo LAM-MPI permitindo que aplicações
que operam debaixo do LAM-MPI podem ser salvas em um disco e reiniciadas mais tarde.
LAM requer uma terceira parte em um simples processo de checkpoint/restart, um conjunto
de ferramentas para de fato checar o ponto de parada e reiniciá-lo do lugar certo e a hora
certa, levando em conta as rotinas de processamento em paralelo (MPI-LAM).
b) Rápido Trabalho de iniciação
LAM-MPI utiliza um pequeno daemon em nível de usuário para controle de
processos, entrada e saída, e comunicação de fora de banda. O daemon em nível de usuário é
iniciado no começo de uma sessão que usa lamboot, pode-se usar rsh/ssh, TM (OpenPBS /
PBS Pro), ou BProc para iniciar os daemons remotamente. Embora o tempo para lamboot
executar pode ser longo quando se trata de grandes plataformas quando se usa rsh/ssh, o
tempo gasto para iniciar é compensado com as aplicações mpirun que não utilizam rsh/ssh, e
sim os daemons do LAM. Até mesmo para um número muito grande de nós, aplicações de
iniciação do MPI, leva alguns segundos (MPI-LAM).
40
c) Comunicação de alto Desempenho
LAM-MPI possui opções para comunicação em MPI com um pequeno overhead. O
sistema de comunicação TCP trabalhando na velocidade de Gigabit, possibilita a
comunicação de sistemas de memória compartilhada, sendo assim o meio de transmissão de
alta velocidade para comunicação de nós remotos. LAM-MPI 7.0 e posteriormente com o
apoio de Myrinet transmite em rede usando a interface de GM, isso por que este sistema
possui uma banda significativamente maior e uma menos latência do que o sistema TCP
(MPI-LAM).
d) Run-time Tuning e Seleção do RPI
LAM-MPI valorizou um largo número de parâmetros. Infelizmente, a maioria tinha
que ser fixado no tempo de compilação. Com o LAM-MPI 7.0, quase todo parâmetro em
LAM pode ser alterado ao correr do tempo (variáveis de ambiente ou flags para o mpirun)
fazendo uma aplicação muito mais simples. A inclusão da Interface de Serviços de Sistema de
LAM (SSI) permite que o RPI (usado no transporte de rede para comunicações ponto-a-ponto
usando MPI) seleção pode ser feito em tempo de execução ao invés de recompilar a cada
mudança. Por exemplo, ao invés de recompilar o LAM quatro vezes para decidir qual sistema
de transporte da melhor desempenho para a aplicação, basta colocar uma variável de flag no
mpirun (MPI-LAM).
e) SMP- Aware
O uso de agrupamentos de máquinas de SMP é uma tendência crescente no mundo
agrupando. Com MPI-LAM 7.0, são aperfeiçoadas muitas operações coletivas comuns para
tirar proveito da velocidade de comunicação mais alta entre processos na mesma máquina. Ao
41
usar os SMP-Aware, o aumento de desempenho pode ser visto com uma pequena ou nenhuma
mudança em aplicações de usuário (MPI-LAM).
f) Integração com PBS
PBS (OpenPBS ou PBS Pro) está sendo usando para programação de agrupamentos
de alto desempenho nos dias de hoje. Usando os mecanismos de boot específicos do PBS,
LAM pode propiciar uma contabilidade de processos e limpeza de trabalho nas aplicações de
MPI. Como isso um tempo reduzido na execução do lamboot quando comparado ao rsh/ssh
(MPI-LAM).
g) Integração com BProc
O BProc distribuiu espaço de processo provê um único espaço de processo para um
agrupamento inteiro. Também provê vários mecanismos para não começar aplicações
disponível nos nós de um agrupamento. Mesmo quando LAM não é instalado no nó. O LAM
disponibilizará automaticamente recursos para o nó. Aplicações de MPI ainda devem estar
disponíveis em tudos os nós. (embora o - opção de s para mpirun elimina esta exigência)
(MPI-LAM).
h) Arquitetura de Componente extensível
LAM 7.0 é o primeiro LAM a incluir a tecnologia SSI (Interface de Serviços de
Sistema), viabilizando uma arquitetura de componente extensível para LAM-MPI.
Atualmente, módulos “drop-in” são suportados no booting do ambiente LAM em tempo de
execução, MPI coletivos, ponto de checagem e restart, e MPI transporte (RPI). A seleção de
um componente é decidida em tempo de execução, enquanto o usuário faz a seleção dos
42
módulos que proporcionam um o melhor desempenho para uma aplicação específica (MPI-
LAM).
i) Depuração de Aplicação fácil
Com o apoio de depuradores paralelos como o Distributed Data Debugging Tool e o
Etnus TotalView Parallel Debugger, depurar o LAM-MPI fica fácil. Estes depuradores
permitem que os usuários consigam depurar aplicações em MPI bem complexas, melhorando
assim a qualidade das mesmas (MPI-LAM).
j) Interoperabilidade
LAM implementa muito da Interoperabilidade do padrão MPI (IMPI), com isso
permitindo que uma aplicação de MPI seja executada em várias plataformas de
implementação do padrão MPI. O uso de IMPI permite para os usuários obter o melhor
desempenho possível, até mesmo em um ambiente de heterogêneos (MPI-LAM).
O próximo item se refere a uma outra implementação do padrão MPI e que será
tomado como base para este trabalho.
2.2.2.2. MPICH
MPICH é uma implementação completa e de domínio livre da especificação de MPI.
Foi projetada para ser portável e eficiente. O “CH” referenciado no nome vem de Chameleon
(Camaleão), símbolo de adaptabilidade para o ambiente onde está sendo utilizado e assim de
portabilidade (MPICH).
MPICH é um projeto de pesquisa e também um projeto de desenvolvimento de
software. Como um projeto de pesquisa, sua meta é explorar métodos para estreitar o caminho
entre o programador e uma máquina paralela com o que seu hardware pode proporcionar em
questão de desempenho. Como um projeto de software, a meta de MPICH é adotar o Padrão
43
MPI, proporcionando aos usuários um software livre, implementação de alto-desempenho em
uma diversidade de plataformas (SOUZA, 2003).
Na reunião do Fórum de MPI a conferência de Supercomputing 92 , Gropp e Lusk
se ofereceram para desenvolver uma implementação do MPICH. O propósito era expor
problemas que a especificação poderia causar corrigindo-as antes que fossem fixadas. Este
primeiro MPICH ofereceu um bom desempenho e uma boa portabilidade (SOUZA, 2003).
Esta implementação foi modificada para conseguir um aumento gradativo de
desempenho e portabilidade. Em paralelo foi aplicado um grande sistema onde envolvia todas
as especificações do MPI.
Em maio de 1994 a implementação de MPICH estava completa, portátil, rápida.
Durante algum tempo depois, o MPICH continuou evoluindo em várias direções. Primeiro, a
Abstract Device Interface (ADI). Em segundo, alguns fornecedores de hardware começaram a
tirar proveito desta interface, para desenvolver suas próprias implementações de MPI
resultando em implementações do ADI de grande eficiência para as suas máquinas em
particular. Em terceiro, um conjunto de ferramentas que faz parte do ambiente de
programação paralelo MPICH, foi estendido (MPICH).
a) Abstract Device Interface
Embora MPI seja uma especificação de um padrão relativamente grande, os
dispositivos dependentes são em número menor. Implementando MPI usando ADI, pode-se
construir código que se pode aproveitar em muitas implementações. Uma maior eficiência
poderia ser alcançada se as implementações fossem feitas pelos fabricantes de forma
específica para os seus produtos. Enquanto a ADI foi projetada para proporcionar uma
implementação portátil do padrão MPI, esta definição pode ser usada para implementar
qualquer biblioteca de passagem de mensagem de alto nível (MPICH)(SOUZA, 2003).
44
A passagem de uma mensagem ADI deve proporcionar quatro funções: especificação
de uma mensagem a ser enviada ou recebida, dados comuns entre o API e o hardware que
passa a mensagem, administração das listas de mensagens que estão pendentes (send e
recive), e proporcionando informações básica sobre o ambiente de execução (por exemplo,
quantas tarefas estão lá). O MPICH ADI provê tudo destas funções; porém, muitos sistemas
de hardware de passagem de mensagem podem não prover administração de lista. Estas
funções são emuladas pelo uso de rotinas auxiliares (MPICH).
A abstract device interface é um conjunto de definições de função que possui
protocolos de passagem de mensagem que distinguem o MPICH de outras implementações do
padrão MPI.
Figura 14 - Exemplo das rotinas do MPICH
MPI_Reduce
Interface do canal
Implementações da interface do canal
Interface abstrata do dispositivo
MPI Ponto-a-ponto
MPI
SGI(4)
Melko T3D SGI(3) NX
MPI_Isend
MPID_Post_send
MPID_SendControl
45
Um organograma das camadas superiores do MPICH é mostrado em Figura 14. São
mostradas as funções de cada camada à esquerda sem entrar em detalhes nos algoritmos
presente no ADI.
Ao mais baixo nível, o que realmente é precisado é um modo para transferir dados,
possivelmente em pequenos pacotes, de um processo para outro. Embora muitas
implementações sejam possíveis, a especificação pode ser feita com um número pequeno de
definições. Consiste em cinco funções exigidas. Três rotinas enviam e recebem pacotes (ou
controla) informação: MPID_SendControl,One pode usar MPID_SendControlBlock em vez
de ou junto com MPID_SendControl. Pode ser mais eficiente para usar a versão de bloqueio
para implementar chamadas de bloqueio. MPID_RecvAnyControl, e MPID_ControlMsgAvail;
duas rotinas enviam e recebem dados: MPID_SendChannel e MPID_RecvFromChannel.
Outros que poderiam estar disponíveis em implementações especialmente aperfeiçoadas serão
definidas e usadas, quando certas rotinas estiverem definidas. Estes incluem várias formas de
bloquear e operações de nonblocking para pacotes e dados (MPICH).
Estas operações estão baseadas em uma capacidade simples enviar dados de um
processo para outro. Nenhuma funcionalidade a mais é requerida. O ADI codifica e usa estas
operações simples para realizar as operações, como MPID_Post_recv que é usado pela
implementação de MPI.
2.2.3. Problemas de desempenho em MPI A vontade de escrever programas paralelos é igual a de querer resolver grandes
problemas em menos tempo. Se os programas seriais forem suficientemente rápidos para
solucionar todos os problemas que interessa, e se eles puderem armazenar todos os dados,
programação paralela seria somente um exercício intelectual. Logo, para escrever um
46
programa deve-se levar em conta a sua performance. Neste capítulo serão discutidos métodos
para mensurar a performance de programas paralelos.
Note que não foi usado o mais convencional dos métodos para analisar a
performance de programas. E estes métodos são muito bons, especialmente na análise de
performance de programas seriais, porém eles não fornecem detalhes suficientes na análise de
programas paralelos. Ainda assim, considera-se que um estudo empírico é o melhor a ser
feito. É bom considerar que o foco é a velocidade, uma vez que há muitos aspectos a serem
considerados em programação paralela. Em particular, você deverá sempre ter em mente o
custo do desenvolvimento de programas paralelos. Muitos programas paralelos são
desenvolvidos na maioria das vezes para obter o máximo possível de performance e, como
conseqüência, levam anos para serem desenvolvidos. Claramente, você deverá sempre se
questionar sobre qual terá maior custo de desenvolvimento, o mais rápido e mais complexo
programa ou um programa simples e mais lento. Para melhor entender o assunto será
abordado uma breve discussão sobre performance em programas seriais até chegar em
programas paralelos.
2.2.4. Medidas de Desempenho e Métricas Medidas de desempenho, que permitam a análise do ganho obtido com o aumento do
total de processadores utilizados, são necessárias. Algumas medidas utilizadas são: Tempo de
Execução, “speedup” (ganho de desempenho) e eficiência.
a) Tempo de Execução
O Tempo de execução (Texec) de um programa paralelo é o tempo decorrido desde o
primeiro processador iniciar a execução do problema (Ti) até o último terminá-la (Tf)
(PACHECO, 1997).
47
Texec = Tf -Ti = f (Tcomp , Tcomu , Tocio). (1)
Em um instante da execução, as expressões Tcomp, Tcomu e Tocio representam que o
processador está - respectivamente - em fase de computação, comunicação, ou ociosidade.
(PACHECO, 1997).
b) Speedup
O Speedup (S) obtido por um algoritmo paralelo executando sobre p processadores é
a razão entre o tempo levado por aquele computador executando o algoritmo serial mais
rápido (Ts) e o tempo levado pelo mesmo computador executando o algoritmo paralelo
usando p processadores (Tp) (PACHECO, 1997).
S= Ts / Tp (2)
c) Eficiência
Eficiência (E) é a fração do tempo em que os processadores estão ativos. Ela é usada
para medir a qualidade de um algoritmo paralelo.
Seu resultado é a razão entre o speedup e a quantidade P de processadores. Esta
medida mostra quanto o paralelismo foi explorado no algoritmo. Quanto maior a fração,
menor será a eficiência (PACHECO, 1997).
E = S / P = Ts/PTp (3)
48
d) Lei de Amdahl
A lei de Amdahl visa ao aumento de desempenho do processamento possível,
introduzindo melhorias numa determinada característica. Ele é limitado pela percentagem em
que essa característica é utilizada.
Considerando-se um programa com Texec =100 seg, sendo 20% operações de ponto
flutuante e 80% de inteiros (MANDEL, 1997), a equação abaixo demonstra como o ganho da
unidade de ponto flutuante foi quatro vezes mais rápido.
sTexec 8580420
=+= 18.185
100==ganho
Esse mesmo exemplo de ganho obtido pode ser observado na equação abaixo, que
considera a unidade de inteiros como sendo duas vezes mais rápida.
sTexec 602
8020 =+= 67.160
100==ganho
2.2.5. Análise de Performance de Programas Seriais
É necessário ver a analise de performance de programas como uma continuação,
integrante do processo de desenvolvimento. Visto assim, há diferentes graus de precisão
implícitos na expressão “análise de performance”. Antes nenhum código era escrito, eram
fornecidas somente estimativas de performance que envolve arbitrariamente constantes
simbólicas obtidas por se estimar o número de comandos executados. Assim como
procedimentos de desenvolvimento, também se substituem as constantes simbólicas por
constantes numéricas que são válidas para um sistema compilado e particular. Sendo assim,
(4)
(5)
49
em algumas análises prioritárias especifica-se a contagem de comandos em tempo de
execução; como detalhes da performance obtida, podendo especificar em milissegundos ou
microssegundos o tempo de execução (PACHECO, 1997).
Seria bom se a frase fosse verdadeira: “O tempo de execução deste programa é T(n)
unidades se o input tem o tamanho n”. O tempo atual que um programa leva para resolver um
problema, o tempo para começar uma execução até completar a execução, dependerá de
outros fatores como o tamanho do input. Por exemplo, dependerá de:
O hardware que está sendo usado;
A linguagem de programação e do compilador;
Os detalhes de outros input e seus tamanhos.
Para evitar a relação com o primeiro fator, como já mencionado, serão contados
como “comandos executados” nas análises iniciais. Isto levará ao segundo fator: se contar
comandos executados, são estes comandos executados? Se sim, que tipo de execução, RISC
ou SISC? Ou são os comando feitos em linguagens de alto nível, e se sim, qual linguagem de
alto nível? Também, serão contatos comandos diferentes, desde que em geral, comandos
diferentes requeiram diferentes tempos de execução?
Finalmente, fica fácil tomar como exemplo de programas que tenham
comportamentos muito diferentes com diferentes inputs, mesmo se os inputs tenham o mesmo
tamanho. Por exemplo, uma inserção aleatória que fornece números inteiros para incrementar
uma ordem e que use busca linear rodará muito mais rápido se o input é 1, 2, ...., n que se o
input é n, n-1, , ..., 2, 1 (PACHECO, 1997). Pode-se evitar este problema discutindo o pior
caso de execução ou um possível caso médio de tempo de execução; em geral, quando se
refere a tempo de execução, sempre será discutido o pior caso de tempo de tempo de
execução.
50
Mesmo assim, quando ao se contar comandos, pode-se evitar completamente o
primeiro dos dois problemas. Muitos autores são imprecisos de usarem analises asymptotic.
Em análises assyntotic, especificam-se limites na performance dos programas. Como
exemplo, pessoas muitas vezes dizem que o comum algoritmo aleatório conhecido como
“bubble sort” é um algoritmo n2. O que significa que se você aplicar o algoritmo Bubble sort
em uma lista formada de n itens, o número de comandos executados será menor que algumas
constantes múltiplas de n2, provavelmente se n for suficientemente grande. Se você ainda não
se acostumou a ver este tipo de análise, esta sentença pode parecer muito vaga. Ainda assim,
estimando a performance de programas seriais, eles se mostrarão muito bons (PACHECO,
1997).
Mesmo que eles não sejam tão bons para performance de programas paralelos. Ainda
assim, se contado comandos executados e incluir todas as constantes múltiplas explicitamente
nas formulas, a menos que se tenha determinado que não seja necessário.
2.2.6. Análise de Performance de Programas Paralelos Uma diferença clara entre estimativa de performance de programas paralelos e seriais
é que o tempo de execução de um programa paralelo dependerá de duas variáveis: tamanho de
input e número de processos. Mesmo assim, ao invés de usar T(n) para denotar performance,
será usado a função de duas variáveis, T(n,p). É o tempo gasto do momento quando o
primeiro processo começa a execução do programa até o momento que o último programa
completo a execução e executa a última sentença. Em muitos dos programas, T(n,p) será o
número de processos executados pelo processo 0 (zero) (ou o processo que é responsável pelo
I/O), tipicamente a execução começará com o processo 0 reunindo e distribuindo dados de
input, e terminando com o processo 0 imprimindo o resultado (PACHECO, 1997).
51
Note que esta definição implica em que se múltiplos processos estão em execução
em um simples processador físico, o tempo de execução em todas as possibilidades será
substancialmente maior se os processos estiverem rodando em processadores fisicamente
separados.
Geralmente, quando é discutida a performance de programas paralelos, a
subjetividade da comparação de um programa paralelo com um serial ocorre. O mais
comumente usado em mensuração é rapidez e eficiência. Vagamente, rapidez é a quantidade
de tempo de execução de uma solução serial para um problema para tempo de execução
paralelo. Ocorre que, se To(n) denota o tempo de execução de uma solução serial e Tr(n,p)
denota o tempo de execução de uma solução paralela com p processos, então a velocidade do
programa paralelo é
),()(),(pnT
nTpnSπσ
=
Muitos autores definem a velocidade como sendo a porção de tempo de execução do
programa serial conhecidamente rápido em um processador de um sistema paralelo.
Por um valor fixo de p, ele usualmente será o caso que 0 < S(n,p) <= p. se S(n,p) = p,
um programa é dito que tem velocidade linear. Isto, é claro, uma rara ocorrência desde que
muitas soluções paralelas adicionarão muito overhead por causa da grande quantidade de
processos de comunicação. Infelizmente, a maior freqüência e a mais comum ocorrência é de
lentidão. Na verdade, o programa paralelo rodando em mais de um processo é atualmente
mais lento que um programa serial. Isto infelizmente resulta em uma excessiva quantidade de
overhead, e este overhead é geralmente causado pela grande quantidade de processos de
comunicação (PACHECO, 1997).
Uma alternativa para a velocidade é a eficiência. Eficiência é a medida de utilização
de processos em um programa paralelo, relativo ao programa serial. Está definido como:
(6)
52
),()(),(),(pnpT
nTp
pnSpnEπσ
==
Então 0 < S(n,p) <= p, 0 < E(n,p)<=1. Se E(n,p)=1, o programa está mostrando
velocidade linear, enquanto se E(n,p)<1/p, o programa está executando mais devagar.
2.2.7. O custo da comunicação Como já citado anteriormente no neste trabalho, nota-se que comunicação, como I/O,
é significativamente maior que cálculos locais. Então, para fazer uma estimativa razoável de
performance de programas paralelos, conta-se o custo da comunicação separadamente do
custo dos cálculos de I/O. Isto é,
),(),(),(),( / pnTpnTpnTpnT commoicalc ++=
Em vista disto, é preciso ter o melhor entendimento do que ocorre em dois processos
de comunicação. Supondo-se que está rodando um programa paralelo e o processo q está
enviando uma mensagem para o processo r. Quando o processo q executa a sentença
MPI_SEND(message, count, datatype, r, tag, comm) e o processo r
executa a sentença MPI_RECV(message, count, datatype, q, tag, comm,
&status) os detalhes do que ocorre no nível de hardware irão variar. Sistemas diferentes
usam diferentes protocolos de comunicação, e um sistema simples executará duas
configurações diferentes de comandos no nível de máquina dependendo se o processo reside
no mesmo ou em processador fisicamente distinto (PACHECO, 1997).
(7)
(8)
53
O interesse maior é nos casos onde os processos residem em processadores distintos.
Nesta situação, a execução do par send/receive pode ser dividida em duas fases: a fase inicial
e a fase de comunicação. Tipicamente, durante a fase inicial, a mensagem pode ser copiada na
área de armazenamento de mensagens do sistema de controle, e o envelope de dados,
consistindo na posição do recurso (q), a posição de destino (r), a tag, o comunicador, e
possivelmente outras informações, podem ser adicionadas a mensagem. Durante a fase de
comunicação, os dados atuais são transmitidos entre os processadores físicos. Pode haver
também outra fase, analogamente a fase inicial, até o processo de recebimento, durante o qual
a mensagem pode ser copiada da área de buffering do controlador do sistema para a memória
do controlador de usuário (PACHECO, 1997).
Os custos embutidos nestas fases irão variar de sistema para sistema, usa-se
constantes simbólicas para representar os custos. Será usado Ts para representar o tempo de
execução da fase inicial, incluindo qualquer tempo gasto no processo de recebimento e cópia
da mensagem para o controlador de usuário, e Tc representará o tempo que levará para
transmitir uma unidade simples de dados, de um processador para outro. A unidade de dados
pode ser um byte, uma palavra ou algumas unidades grandes de dados. Usando esta notação, o
custo de enviar uma simples mensagem contendo K unidades de dados é dado por:
Ts + KTc
O tempo Ts é também chamado de tempo latente, e a recíproca de Tc é também
chamada de largura de banda.
É surpreendentemente difícil obter estimativas reais de Ts e Tc. Para um sistema
particular, a melhor coisa a fazer atualmente é escrever programas que enviam e recebem
mensagens. Mesmo assim, algumas vezes acontece algumas generalizações. Em muitos
(9)
54
sistemas, Tc está em ordem de magnitude de custos de uma operação aritmética, Ta e Ts estão
de um para 3 ordens de magnitude maior que Tc. Por exemplo, em um Ncube2, Tc é
aproximadamente 2.5 microssegundos/float, e Ts é 170 microssegundos (PACHECO, 1997).
Alguns valores não devem ser considerados precisamente por uma série de razões:
Estimativas de Ts, Tc e Ta em alguns sistemas (todos os tempos estão em
microssegundos; SM representa o uso das funções de memória compartilhada, Tc é o tempo
por dupla).
2.3. Aspectos de Implementação do MPI em Java Com o grande crescimento do uso da linguagem Java no meio científico, sentiu-se a
necessidade da implementação do padrão MPI utilizando esta linguagem.
Existem varias implementações do padrão MPI utilizando a linguagem Java como
por exemplo: JavaMPI, JMPI, MPIJ e mpiJava (CARPENTER, 1999). Este último será
apresentado com maiores detalhes logo abaixo.
2.3.1. mpiJava O padrão de MPI é baseado em objetos. A especificação de C++ que se liga no
padrão MPI 2 possui estes objetos em uma hierarquia de classe. O mpiJava API segue este
modelo, erguendo a estrutura de sua hierarquia de classe diretamente das ligações de C++.
São ilustradas as classes principais de mpiJava na Figura 15.
A classe MPI só possui membros estáticos. Age como se fosse um módulo que
contém serviços globais, como por exemplo, a inicialização do MPI, e muitas outras
constantes globais inclusive o comunicador padrão COMM_WORLD.
A classe mais importante existente no pacote é a classe de comunicador Comm. Toda
a comunicação em mpiJava é realizada por objetos de Comm ou suas subclasses. Como
sempre em MPI, um comunicador está esperando por um “objeto coletivo”, logicamente
55
compartilhado por um grupo de processadores. Os processos se comunicam enviando
mensagens aos seus semelhantes pelo comunicador comum (CARPENTER, 2000).
Figura 15 - Principais classes do mpiJava
Outra classe de grande importância para estudo é a classe de Datatype. Isto descreve
os tipos dos objetos para o buffers de passagem de mensagem, para enviar, receber, e qualquer
outra função que envolva comunicação. Vário datatypes básico são predeterminados no
pacote. Estes correspondem principalmente aos tipos primitivos do Java, mostrados na Tebela
2.
Tabela 2 – datatypes básicos do mpiJava
MPI datatype Java datatype MPI.BYTE byte MPI.CHAR char MPI.SHORT short MPI.BOOLEAN boolean MPI.INT int MPI.LONG long MPI.FLOAT float MPI.DOUBLE double MPI.OBJECT Object
56
As operações básicas utilizadas para enviar e receber são objetos da classe Comm
com Interfaces. As duas linhas abaixo são mencionadas como um exemplo:
public void Send(Object buf, int desl, int cont, Datatype datat, int dest, int tag)
public Status Recv(Object buf,int desl,int cont, Datatype datat, int font, int tag)
Em ambos os casos o argumento que corresponde a buff deve ser um array em Java.
Na implementação eles são elementos de array de tipo primitivo da linguagem.
Implicitamente eles devem ser array une-dimensionais, pois em Java arrays
multidimensionais são arrays de arrays.
2.3.2. Implementações avançadas com o mpiJava A implementarão do mpiJava se dá por meio de uma JNI wrapper para
implementação nativa do MPI.
A ligação do Java com o MPI nativo não é tão simples assim por estes motivos ainda
existem conflitos de baixo nível entre as rotinas em Java e os mecanismos de interrupção do
MPI.
Segundo CARPENTER, 2002 esta comunicação está melhorando de acordo com que
a máquina virtual Java vai se aprimorando.
Para aprimorar a troca de mensagens o mpiJava da uma considerável importância
para o uso de marshalling e unmarshalling de dados.
Segundo CARPENTER, 2002, para uma melhor desenvolvimento e performance do
mpiJava foram realizadas algumas alterações na estrutura do MPI para assim atender melhor a
estrutura da linguagem Java.
Algumas destas mudanças foram:
• O tipo básico MPI.OBJECT foi adicionado;
57
• As classes de exeções do MPI não são específicas como subclasses de
IOException;
• Os construtores de tipos derivados como: vetor, hvector, indexed, hindexed
se tornaram métodos não-estáticos.
Foi realizada também um mudança nos métodos “destruidores” de MPI, deixando
assim a utilização do sistema mais simples para o usuário, pois o mesmo não teria que se
preocupar com a limpeza dos métodos que estão em memória (CARPENTER, 2002).
Todas as classe de MPI estão dentro do pacote mpi, os nomes nas classe e membros
geralmente seguem as recomendações das convenções para os códigos Java da Sun’s. Isso por
que normalmente estas convenções estão de acordo com as convenções de nomes para o
padrão MPI2, caso algum nome tenha ser alterado o mesmo leva uma letra minúscula no
inicio do nome da classe ou método.
Algumas restrições de dados foram apresentadas pois a Máquina Virtual Java não
implementa o conceito de um endereço físico ser passado para um dado, com isso o uso do
MPI_TYPE_STRUCT datatype constructor foi reduzido tornando impossível enviar datatype
básicos misturados em uma única mensagem (CARPENTER, 2002).
Uma outra característica do mpiJava é com relação ao inicio do buffer de dados, onde
C e Fortran possuem dispositivos para tratar este tipo de problema, mas o Java não possui este
recurso, mas provê a mesma flexibilidade associando o parâmetro a um elemento do buffer
isso define a posição do elemento no buffer (CARPENTER, 2002).
O MPI não devolve mensagem de erro de forma explicita desta forma será utilizado
as exceções do Java para apresentar os referidos erros (CARPENTER, 2002).
Agora é ilustrada um pouco da comunicação pont-to-pont realizada pelo mpiJava.
58
Os métodos básicos de comunicação pont-to-pont são o send e o receive e estão
dentro da classe Comm. Um simples uso dos mesmo está no conjunto de código abaixo, que é
um simples programa que manda uma mensagem “Hello, there”.
import mpi.* ; class Hello { static public void main(String[] args) { MPI.init(args) ; int myrank = MPI.COMM_WORLD.rank() ; if(myrank == 0) { char [] message = "Hello, there".toCharArray() ; MPI.COMM_WORLD.send(message, 0, message.length, MPI.CHAR, 1, 99) ; } else { char [] message = new char [20] ; MPI.COMM_WORLD.recv(message, 0, 20, MPI.CHAR, 0, 99) ; System.out.println("received:" + new String(message) + ":") ; } MPI.finish(); } }
A comunicação pont-to-pont possui algumas características importantes como; por
exemplo, nas operações de Blocking, a rotina de envio de mensagem seria assim:
void Comm.send(Object buf, int offset, int count, Datatype datatype, int dest, int tag)
buf - envia o array do buffer offset - Offset inicial no envio do buffer count - número de itens para ser enviado datatype - datatype para ser enviado no buffer dest - Destino tag - etiqueta da mensagem
Um aspecto importante no tratamento de dados usando a linguagem Java define bem
os seus tipos de dados primitivos, tento que estar de acordo com IEEE 754 para dados float
e double
Outro aspecto importante é o da comunicação envolvendo o envio de dados usando o
modo buffer, envio no modo síncrono e no modo pronto.
59
Quanto ao modo buffer, deve-se observar o contador que controla o tamanho do
envio que é o parâmetro count, conforme exemplo do código abaixo:
void Comm.bsend(Object buf, int offset, int count, Datatype datatype, int dest, int tag) Buf - envia o array do buffer Offset - Offset inicial no envio do buffer Count - número de itens para ser enviado Datatype - datatype para ser enviado no buffer dest - Destino tag - etiqueta da mensagem
Quanto ao modo síncronomo, deve-se observar o aguardo de uma mensagem para
sincronizar . Exemplo:
void Comm.ssend(Object buf, int offset, int count, Datatype datatype, int dest, int tag) buf - envia o array do buffer offset - Offset inicial no envio do buffer count - número de itens para ser enviado datatype - datatype para ser enviado no buffer dest - Destino tag - etiqueta da mensagem
Uma outra característica importante de se observar no mpiJava é a alocação buffer, e
a utilização do mesmo.
void MPI.bufferAttach(byte [] buffer) byte [] MPI.bufferDetach()
Outro modo de comunicação pont-to-pont é através das operações Non-blocking.
As comunicações non-blocking usam métodos da classe Request para identificar os
métodos de comunicação para envio.
Request Comm.isend(Object buf, int offset, int count, Datatype datatype, int dest, int tag) buf - envia o array do buffer offset - Offset inicial no envio do buffer count - número de itens para ser enviado datatype - datatype para ser enviado no buffer dest - Destino tag - etiqueta da mensagem
60
2.3.3. Outros Ambientes em Java Um importante recurso utilizado neste projeto é a Serialização de Objetos que é o
processo de codificação de um objeto Java como um array de bytes, assim como a
desserialização dos objetos é o processo de instanciação de um objeto a partir de um array de
bytes, podendo suportar estruturas de dados dos objetos, por exemplo. Uma simples chamada
de uma estrutura pode serializar todo o conteúdo de uma lista ligada. Objetos podem ser
salvos em um arquivo e reabertos tempos após, ou transmitidos via rede a uma outra aplicação
Java, por exemplo, via protocolo TCP/IP. Campos específicos podem ser marcados como
transientes de modo que o serializador e desserializador percebam isto.
A biblioteca de Serialização pode ainda ser customizada pela sobreposição dos
métodos readObject() e writeObject() de modo a atender determinada aplicação.
Um outro ambiente Java do padrão MPI é o MPIJ que é uma implementação
completamente baseada no padrão MPI. A comunicação entre os processos no MPIJ usa o
Data marshalling nativo para dados primitivos do Java descrito acima, está técnica permite
alcançar velocidades na comunicação comprável a das implementações nativas de MPI. O
MPIJ faz parte do Distributed Object Group Metacomputing Architecture
(DOGMA).(CARPENTER, 2000).
Este ambiente de execução de aplicações paralelas em clusters de workstations e
supercomputadores, é uma plataforma de pesquisa desenvolvida a partir do trabalho de Glenn
Judd na Brigham Young University.
O ambiente DOGMA escrito em Java, é uma plataforma independente e dinâmica
que suporta reconfiguração e gerenciamento remoto, carga decentralizada de classes
dinâmicas, detecção e isolamento de falhas, e participação por nós de browsers e clientes.
Uma Máquina Virtual Distribuída (DJM- Distributed Java Machine) forma um
segundo layer do ambiente runtime DOGMA. A responsabilidade dela é conectar várias
61
Máquinas Virtual Java como uma máquina distribuída simples. Os nós são organizados em
duas categorias: famílias e clusters. Clusters de nós, são nós que são localizados físicamente
ou próximos do ponto de vista de conexão, enquanto famílias são os nós que tem arquitetura e
configuração similares.
Há um gerenciador chamado DJMManager que é um daemon centralizado
responsável pela máquina distribuída inteira, requerendo processamento de reconfiguração ou
falhas, enquanto uma aplicação NodeManager executa em cada nó na máquina distribuída,
aplicações de comunicação solicitam a máquina distribuída através do NodeManager local.
Grupos de famílias e clusters de nós podem ser armazenados numa configuração,
estas podem ser dinamicamente adicionadas ou removidos no run-time pelo gerenciamento de
configuração.
Browsers-web pode participar na máquina distribuída carregando um applet
NodeManager. Aplicações utilizam um disco local ou distribuído de um servidor WWW.
Um outro ambiente que pode ser mencionado é o JMPI é um projeto de propósito
comercial da MPI Software Technology, Inc., feito a partir do trabalho de mestrado de Steven
(MORIN, 2000) com o intuito de desenvolver um sistema de passagem de mensagem em
ambientes paralelos utilizando a linguagem Java. O JMPI combina as vantagens da linguagem
Java com as técnicas de passagem de mensagem entre processos paralelos em ambientes
distribuídos (DINCER, 1998).
62
3. PERFORMANCE EM AMBIENTE MPI USANDO JAVA
Neste trabalho foi desenvolvido o JMPI-PLUS, que se trata de uma implementação
do padrão MPI baseada em Java que teve como base as classes do mpiJava, desenvolvido por
Carpenter. O JMPI-PLUS tem como objetivo o aprimoramento no que diz respeito aos
métodos de envio de mensagens da implementação mpiJava, que é uma implementação do
padrão MPI utilizando a linguagem Java. Em colaboração com outro trabalho realizou o
tratamento do transporte de mensagens usando serialização de objetos (CORREIA, 2005).
Para a implementação do JMPI-PLUS foi construído um cluster utilizando Beowulf.
Segundo (WALKER, 2001), a classe Beowulf é uma categoria especial de cluster
construído a partir de máquinas e dispositivos de baixo custo facilmente encontrados no
mercado. A idéia principal é que seja feita uma configuração de hardware e software para que
se tenha um aumento de performance na execução paralela de aplicações. A categoria
Beowulf está dentro do grupo dos sistemas SSI, Single System Image, o usuário deve ver o
sistema como uma máquina única.
Uma característica importante que se tem em máquinas Beowulf é a centralização dos
pacotes de programas e contas de usuários em uma máquina servidora que é a responsável
pelas tarefas administrativas do cluster. Quando uma tarefa é enviada ao cluster, a máquina
servidora recebe e divide a tarefa entre os nós processadores, também chamados de escravos.
Sendo assim um cluster Beowulf de 2 nós na verdade possui um total de três máquinas, uma
máquina servidora e duas máquinas escravas (STERLING, 1999).
Em nível de sistema operacional um dos aspectos mais importante da configuração
Beowulf é a configuração do NFS (Network File System) e do NIS (Network Information
Service). O objetivo da configuração NFS é permitir que a máquina master compartilhe a sua
63
partição que contém os softwares usados no cluster, normalmente instalados em /usr/local,
fazendo uma exportação para os nodos escravos.
Normalmente a pasta /home também é exportada. Os nós escravos por sua vez fazem
à montagem desses file systems em suas áreas locais com os mesmos nomes, /usr/local e
/home. Com isso a atualização e administração dos softwares do cluster se torna bem mais
fácil e elimina um problema sério, que é o de controle das versões dos pacotes instalados.
Dessa maneira o pacote JDK (Java Development Kit), por exemplo, seria instalado na pasta
/usr/local/j2sdk1.4. Os nós escravos tendo o file system montado precisam acrescentar apenas
as informações do JDK na variável PATH e CLASSPATH e podem usar os pacote da SUN
para desenvolver e rodar suas aplicações, sem a necessidade de instalar localmente o pacote
(STERLING, 1999) (SOUZA, 2003).
A utilização do NIS se deve por que ele é o responsável por fazer a autenticação
centralizada dos usuários. O usuário criado na máquina servidora, também tem que estar
criado em todos os nós, pois o usuário utilizado na máquina servidora, automaticamente tem
que ter acesso a todas as máquinas do cluster, pois suas informações são repassadas para
todos os nós da rede via servidor NIS.
O cluster utilizado neste projeto está localizado na Fundação Educacional de
Fernandópolis - FEF e possui três nós processadores, os escravos foram chamados de
“escravoN”, (onde “N” varia de acordo com o número de escravos) e o servidor de “master”.
Todas as máquinas do cluster da FEF possuem CPU de 2.4GHz, memória RAM de
256MB, HD de 40GB, placa de rede Intel(R) PRO 10/100 VE Network Connection, todas as
máquinas dispunham de floppy Disk 1.4, unidade de CDROM. A versão do linux instalado é
o Red Hat 9.0.
Os softwares instalados são o pacote mpiJava (CARPENTER, 2000) e o MPICH
(MPICH).
64
O pacote mpiJava, como já visto no itens 2.3, é uma implementação do MPI
utilizando a linguagem Java e foi desenvolvida por Bryan Carpenter e Geoffrey Fox da
NPAC, Syracuse University, Syracuse, USA; Vladimir Getov da School of Computer Science,
University of Westminster, London, UK; Glenn Judd da Computer Science Department,
Brigham Young University, Provo, USA; Tony Skjellum da MPI Software Technology, Inc.,
Starkville, USA.
Esta implementação do MPI utiliza a serialização de objetos e o marshalling de
dados.
Para tentar-se melhorar o envio de mensagens trabalhou-se diretamente na alteração
das rotinas dos métodos do pacote mpiJava.
• public byte[] Object_Serialize
• public void Object_Deserialize
• public void Rsend
• public void Object_Ibsend
• public Request Irecv
65
O método public byte[] Object_Serialize, pertence a classe Comm, é responsável por
serializar o objeto que será transmitido para os nós processadores.
Neste método foi implementado uma forma para a serializaçao de objetos utilizando
recursos da própria linguagem Java. A Figura 16 ilustra a implementação do método.
Figura 16 – Implemetação método public byte[] Object_Serialize
public byte[] Object_Serialize(Object buf, int offset, int count, Datatype type) throws MPIException { if(type.Size() != 0) { byte[] byte_buf ; Object buf_els [] = (Object[])buf; try { ByteArrayOutputStream o = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(o); int base; for (int i = 0; i < count; i++) { base = type.Extent() * i; for (int j = 0 ; j < type.displacements.length ; j++) out.writeObject(buf_els[base + offset + type.displacements[j]]); } out.flush(); out.close(); byte_buf = o.toByteArray(); } catch(Exception ex) { ex.printStackTrace(); byte_buf = null ; } return byte_buf ; } //fecha o if else return new byte[0]; }
66
O método public void Object_Deserialize, pertence a classe Comm, é responsável por
desserializar o objeto que foi transmitido para os nós processadores.
Neste método foi implementado uma forma para a deserializaçao de objetos
utilizando recursos da própria linguagem Java. A Figura 17 ilustra a implementação do
método.
Figura 17 – Implemetação do método public void Object_Deserialize
O método public void Rsend, pertence a classe Comm, é responsável por enviar um
objeto em modo pronto distribuindo-os mesmos entre os nós processadores do cluster. A
Figura 18 ilustra a implementação do método.
Figura 18 – Implemetação do método public void Rsend
public void Rsend(Object buf, int offset, int count, Datatype type, int dest, int tag) throws MPIException { if (type.isObject()){ byte[] byte_buf = Object_Serialize(buf,offset,count,type); int[] length_buf = {byte_buf.length, count} ; rsend(length_buf, 0, 2, MPI.INT, dest, tag); rsend(byte_buf , 0, byte_buf.length, MPI.BYTE, dest, tag); } else rsend(buf, offset, count, type, dest, tag); }
public void Object_Deserialize(Object buf, byte[] byte_buf, int offset, int count, Datatype type) throws MPIException { if(type.Size() != 0) { Object buf_els [] = (Object[])buf; try { ByteArrayInputStream in = new ByteArrayInputStream(byte_buf); ObjectInputStream s = new ObjectInputStream(in); int base; for (int i = 0; i < count; i++) { base = type.Extent() * i; for (int j = 0 ; j < type.displacements.length ; j++) buf_els[base + type.displacements[j]]=s.readObject(); } s.close(); } catch(Exception ex) { ex.printStackTrace(); } } }
67
O método public void Object_Ibsend, pertence a classe Comm, é responsável por
enviar os objetos serializados utilizando o modo buffer distribuindo-os entre os nós
processadores do cluster. A Figura 19 ilustra a implementação do método.
Figura 19 – Implemetação do método public void Object_Ibsend
O método public void Object_Irecv, pertence a classe Comm, é responsável por
receber os objetos. A Figura 20 ilustra a implementação do método.
Figura 20 – Implemetação do método public void Object_Irecv
public Request Irecv(Object buf, int offset, int count, Datatype type, int source, int tag) throws MPIException { if (type.isObject()){ int[] length_buf= new int[2]; Request req = new Request(buf, offset, count, type, tag, this, length_buf) ; Irecv(length_buf, 0, 2, MPI.INT, source, tag, req); return req; } else return Irecv(buf, offset, count, type, source, tag, new Request()); }
public Request Ibsend(Object buf, int offset, int count, Datatype type, int dest, int tag) throws MPIException { if (type.isObject()){ byte[] byte_buf = Object_Serialize(buf,offset,count,type); int[] length_buf = {byte_buf.length, count} ; Request hdrReq = Ibsend(length_buf, 0, 2, MPI.INT, dest, tag, new Request()); Request req = new Request(hdrReq) ; Ibsend(byte_buf, 0, byte_buf.length, MPI.BYTE, dest, tag, req); return req; } else return Ibsend(buf, offset, count, type, dest, tag, new Request()); }
68
A figura abaixo ilustra o Sender e Receiver entre nós do cluster em que o JMPI-
PLUS irá trabalhar.
Figura 21 – Cenário de funcionamento do projeto.
Para testar a eficiência dos ambientes instalados foram realizados testes com matrizes
de números inteiros de 512 X 512, 1024 X 1024 e 2048 X 2048 em um cluster com dois nós
processadores.
O gráfico representado da figura 22 demonstra os resultados obtidos com os testes
realizados.
Array- Output- Stream
Array- Input- Stream
Dados (imagens para teste)
MPI_TYPE_STRUCT
MPI_SEND
MPI_RECV
MPI_TYPE_STRUCT
Dados (imagens para teste)
Sender Objeto Receiver Objeto
serialização do buff
desserialização do buff
receive do buff
Write array elements
send buffer
Element data
Transporte (outro trabalho)
Tratamento dos pacotes
reconstruct objects
69
0
100
200
300
400
500
600
700
Matriz 512 X 512 Matriz 1024 X1024
Matriz 2048 X2048
MPICHmpiJavaJMPI-PLUS
Figura 22 - Gráfico dos resultados obtidos com os testes entre os ambientes.
Pode-se observar no gráfico acima que a implementação do MPI baseada em Java
apresentou melhor resultado que a implementação MPI baseado em C. Normalmente uma
aplicação feita utilizando a linguagem C, que é a linguagem que o MPICH foi implementado,
é mais rápida que uma mesma implementação desenvolvida em Java. Talvez isso possa ser
explicado pelo fato do código C ser compilado e o código Java ser interpretado (SOUZA,
2003). Um código compilado normalmente é mais rápido que um código interpretado.
Mas levando em consideração que o mpiJava implementa a serialização de objetos e
o marshalling de dados, conseguiu-se com isso um ganho considerável na troca de mensagem
entre os nós processadores chegando a aproximadamente 29% de ganho na troca de
mensagem.
Pode-se observar também que a distribuição e trarefas de pequeno porte no sistema
não houve um grande ganho na performance, isso ocorreu devido ao tempo perdido com o
overhed entre a troca de mensagens.
Uma outra medida de performance foi realizada utilizado MPICH e o JMPI-PLUS,
utilizando imagens médicas, pois atualmente percebe-se o uso cada vez mais constante e
intenso da computação na área da saúde. Esta inovação tecnológica, que a princípio começou
Tempo (segundos)
70
de maneira “gradativa”, é hoje considerada por muitos, uma das ferramentas mais importantes
e indispensáveis nesse seguimento.
O desenvolvimento da atividade clínica é marcada pela procura contínua de
diagnósticos precisos e de ação terapêuticas adequadas. Para dar suporte a essa tarefa da
melhor maneira possível, o clínico faz uso de uma imensa variedade de informações, dentre as
quais destacam-se as imagens, que proporcionam uma interpretação direta e cada vez mais
precisa dos objetos (NETO, 2003).
Ao longo do tempo, vários fatores têm contribuído para o desenvolvimento e
aplicação que se fazem referência à imagens e sua atuação na medicina como a evolução de
forma acelerada da tecnologia dos computadores e sua capacidade cada vez maior de
processamento; o desenvolvimento de novas técnicas de aquisição, o aperfeiçoamento de
algoritmos capazes de executar de maneira automática tarefas complexas (JUNIOR, 1999).
Com a inovação da tecnologia da informação na área da medicina, visando a
melhorar a qualidade dos serviços e o atendimento dos pacientes. Hospitais e clínicas de
pequeno e grande porte estão realizando a integração de seus sistemas de informações para
tecnologias utilizadas mundialmente. Essa integração possibilita o gerenciamento e
armazenamento de imagens, possibilitando que as informações dos pacientes e suas
respectivas imagens sejam compartilhadas. Sua recuperação e visualização passam a ser
realizadas no próprio local ou remotamente (TAMAE, 2005).
Para que esses sistemas pudessem obter êxito em seus objetivos foi criado, em 1993,
um padrão de imagens e informações, chamado DICOM (Digital Imaging and
Communication in Medicine), que entre outras finalidades define a forma de efetuar o
armazenamento e transmissão de imagens médicas de maneira padronizada. A obtenção das
imagens digitais pode ser realizada através de scanner, câmeras digitais ou simplesmente
utilizando o padrão “DICOM”, visto que esse padrão é gerado pelos equipamentos atuais que
71
utilizam imagens médicas, tais como, tomografia computadorizada, ressonância magnética,
ultra-sonografia, mamografia, dentre outros (NETO, 2003).
O DICOM é um padrão desenvolvido por um comitê de trabalho formado por
membros do American College of Radiology (ACR) e do National Electrical Manufactures
Association (NEMA) que iniciou os trabalhos em 1983. Este comitê foi constituído com a
finalidade de desenvolver um padrão digital de informações e imagens. Ele publicou a
primeira versão em 1985, que foi chamada de ACR-NEMA 300-1985 ou (ACR-NEMA
Version 1.0) e a segunda versão em 1988, chamada de ACR-NEMA 300-1988 ou (ACR-
NEMA Version 2.0) (JUNIOR, 1999).
A terceira versão do padrão, nomeada de DICOM 3.0 foi apresentada em 1993, que
tinha como objetivos principais promover a comunicação de informações de imagens digitais,
sem levar em consideração os fabricantes dos aparelhos, facilitar o desenvolvimento e
expansão dos sistemas PACS e permitir a criação de uma base de dados de informações de
diagnósticos que possam ser examinadas por uma grande variedade de aparelhos distribuídos
fisicamente em entidades de saúde (NETO, 2003).
A aquisição das imagens na área da medicina pode ser realizada utilizando inúmeros
recursos, como, por exemplo, câmeras digitais (colposcopia, peniscopia, vulvoscopia,
patologia, dermatologia, etc), scanners (através da “varredura” por linhas, obtém a imagem
digital) e simplesmente utilizando o padrão DICOM, visto que esse padrão atualmente é
encapsulado nos equipamentos de produção de imagens médicas de tecnologia atual.
A aquisição das imagens médicas é de extrema importância para a fase de
processamento das imagens, portanto as imagens dos exames médicos devem estar com uma
“qualidade regular” no que se refere à visualização com uma boa resolução. Tal fato em
questão pode interferir indiretamente na interpretação da imagem, podendo ocasionar um erro
72
de interpretação, e conseqüentemente um diagnóstico não preciso, o que significa prejuízo
para o paciente.
Este teste foi realizado com objetos DICOM fornecidos por uma base de dados de
um trabalho correlato desenvolvido pelo aluno Rodrigo Tamae. Este trabalho correlato é
responsável por pegar a imagem em diversos formatos convertendo-as para o padrão DICOM,
armazenando-as assim em um diretório específico e gravando path do caminho do objeto no
banco de dados. Está sendo escrito um artigo para ser entregue até o dia da defesa explicando
de maneira bem detalhada a relação dos dois projetos.
Para esta simulação foi desenvolvida uma aplicação em Java utilizando os conceitos
de JMPI-PLUS, para consultar no banco de dados do projeto acima citado uma imagem no
padrão DICOM e submete-la ao processamento no cluster, obtendo assim um tempo
resultante deste processo.
O mesmo foi feito para a simulação utilizando os conceitos do MPICH, a mesma
imagem só que no formato JPEG, a aplicação utilizada para trabalhar com esta imagem não
foi desenvolvida neste trabalho.
Os testes foram realizados com três tamanhos diferentes de imagens.
0
0,5
1
1,5
2
2,5
3
3,5
4
Imagem 512Kb Imagem 1300Kb Imagem 2000Kb
Tem
po (s
egun
dos)
MPICHJMPI-PLUS
Figura 23 - Gráfico da simulação de processamento entre figura JPG e no padrão DICOM.
73
O gráfico representado na Figura 23 ilustra o resultado obtido com a simulação
realizada entre o processamento de uma imagem no padrão DICOM que foi processada
utilizado a implementação do JMPI-PLUS e uma imagem no formato JPEG utilizando o
implementação do MPICH.
Notou-se que a implementação do JMPI-PLUS obteve um melhor desempenho, pois
utiliza a serialização de objetos.
74
CONCLUSÕES E TRABALHOS FUTUROS
Características como facilidade de uso, multiplataforma, gerência de memória,
escalabilidade, uso de programação orientada a objetos, estão fazendo com que a linguagem
Java seja largamente adotada como padrão para o desenvolvimento de sistemas.
O grande aumento de dados tanto em tamanho quanto em volume faz-se necessário a
utilização de processamento paralelo e distribuído. Por este motivo optou-se por trabalhar
neste ambiente distribuído utilizando o modelo de cluster Beowulf.
O projeto Beowulf é utilizado, por ter se popularizado na construção de máquinas
para processamento distribuído a um baixo custo a partir de PC´s e dispositivos facilmente
encontrados no mercado.
Uma implementação de MPI é desenvolvida neste trabalho com base em um sistema
de processamento distribuído, utilizado o cluster Beowulf e a linguagem Java.
Usando os recursos avançados da linguagem que por sua vez garante a portabilidade,
interoperabilidade, reuso de código e componentes, facilidade de programação, serialização e
transporte de objetos. O ambiente mpiJava també foi utilizado, pois possui uma interface
simples, exigiu-se pouco esforço com relação a implementação para adaptação do código
existente. Assim poucas linhas de código foram escrita, entretanto um grande esforço de
conhecimento conceitual e teórico foi necessário.
Neste trabalho foram realizados testes utilizando a linguagem Java para o
processamento distribuído trabalhando com matrizes e imagens médicas no padrão DICOM,
pois os mesmo requerem um grande poder de processamento.
Em todos os testes realizados a implementação utilizando a linguagem Java obteve
um melhor resultado em relação a ambientes convencionais (MPICH).
75
Observou-se também que com um numero pequeno de dados a serem precessados
não houve um ganho considerável no processamento, pois o overhed gasto na comunicação
não compenssou o volume de dados.
Como trabalhos futuros propomos uma aplicação para trabalhar com o
processamento distribuído e transporte de imagens médicas através da Web utilizando um
Data Center. Trabalhos este que já estão em fase de desenvolvimento.
Propomos também a implentação completa do projeto, fazendo-se necessário links
com outros projetos, criando-se assim serviços que poderão ser disponibilizados para
terceiros. Bem como também será realizado testes com imagens de tamanho maiores e
variados.
Uma outra proposta é a implentação do projeto em anbientes heterogêneos, fazendo
assim necessário o controle no balenceamento de carda para os nós da rede.
76
REFERÊNCIA BIBLIOGRÁFICA BARBOSA, Jorge: Introdução ao MPI, UCPEL, 2004, disponível em: http://atlas.ucpel.tche.br/~barbosa/sist_dist/mpi/mpi.html#introducao#introducao. BRUNO, Odemir Martinez: Supercomputadores Atuais, Universidade de São Paulo, Instituto De Ciências Matemáticas E De Computação, Departamento De Ciências De Computação E Estatística, 2003. CARPENTER Bryan, V.Getov, G. Judd, T. Skjellum, G. Fox. MPI for Java, Java Grande Forum, 1998. CARPENTER, Bryan, Geoffrey Fox, Sung Hoon Ko and Sang Lim. Object Serialization for Marshalling Data in a Java Interface to MPI. august 1999. CARPENTER, Bryan, Mark Baker, Geoffrey Fox, Sung Hoon Ko and Sang Lim. mpiJava: An Object-Oriented Java interface to MPI. june 2000. CORREIA, Vasco Martins: Serialização dos dados de Imagens Médicas usando troca de mensagens, Laboratório de Engenharia de Software - Centro Universitario Euripides Soares da Rocha – UNIVEM, 2005. DIETZ, Hank Linux Parallel Processing How To. 5 January 1998, disponível em: http://www.ldp.org.2000, acessado em 13 de Janeiro e 2004 DINCER, Kivanc. jmpi and a Performance Instrumentation Analysis and Visualization Tool for jmpi. First UK Workshop on Java for High Performance Network Computing, EUROPAR-98, Southampton, UK, September 2-3, 1998. FIGUEIREDO, Orlando Andrade: Implementação de espaços de tuplas do tipo JavaSpaces , USP-São Carlos, outubro de 2002, acessado em 11 de março de 2004, disponível em: http://www.teses.usp.br/teses/disponiveis/55/55134/tde-08032003-012015. FILHO, Virgílio J. M. Ferreira: MPI-Implmentação Paralela, Universidade Federal do Rio de Janeiro, Departamento de Engenharia Industrial, Rio de Janeiro – Rio de Janeiro, 2002. GUALEVE, José Adalberto F.: Arquitetura de Computadores III – Aulas 1 e 2, 2003. IGNÁCIO, Aníbal Alberto Vilcapona: MPI: Uma Ferramenta Para Implementação Paralela, Universidade Federal do Rio de Janeiro, Programa de Engenharia de Produção/COPPE, 2002. JUNIOR, Pedro Paulo Magalhães Oliveira, Exames Virtuais Utilizando um Algoritmo de Ray Casting Acelerado, Depto. de Informática, Orient.: Marcelo Gattass, dissertação de mestrado, PUC-Rio, 1999. LAINE, JEAN MARCOS: Desenvolvimento de Modelos para Predição de Desempenho de Programas Paralelos MPI, Escola Politécnica da Universidade de São Paulo, 2003.
77
LEITE, Julius: Computação Distribuída e Paralela, Universidade Federal Fluminense, Instituto de Computação – Programa de Pós-Graduação em Computação, Niterói – Rio de Janeiro, disponível em: http://www.ic.uff.br/PosGrad/comppar.html, acessado em 13 de janeiro de 2004. MANDEL, Arnaldo; Simon, Imre; Lyra, Jorge L. : Computação e Comunicação, Universidade de São Paulo, 05508-900 São Paulo, SP, Brasil, 16 de Julho de 1997, disponível em: http://www.ime.usp.br/~is/abc/abc/node12.html, acessado em 13 de janeiro de 2004. MIYAKE, Kuriko: Intel, NTT e Sillicon Graphics farão super-rede com 1 mi de PCs, 30/12/2001, disponível em http://idgnow.terra.com.br/idgnow/pcnews/2001/11/0086, 2001, acessado em 13 de janeiro de 2004. MORENO, Edward David Moreno Ordonez: Sinergia entre Algoritmos, Arquitetura de Computadores e Sistema Operacional, Energia y Computación, junho de 2002, disponível em http://energiaycomputacion.univalle.edu.co/edicion19/revista19_8a.phtml, acessado em 11 de março de 2004. MORIN, Steven Raymond - JMPI: Implementing the Message Passing Interface Standard in Java, University of Massachusetts, graduate degree in Master of Sience in Electrical and Computer Enginnering, september 2000, disponível em: http://www.ecs.umass.edu/ece/realtime/ publications/steve-thesis.pdf. MPICH, Informações disponíveis no site oficial do MPICH, disponível em http://www-unix.mcs.anl.gov/mpi/mpich/indexold.html acessado em acessado em 5 de junho de 2004. MPI, Informações no site oficial do Message Passing Interface Fórum, disponível em http://www.mpi-forum.org/, acessado em 5 de junho de 2004. MPI-LAN, Informações no site oficial do LAM/MPI Parallel Computing, disponível em http://www.lam-mpi.org/, acessado em 5 de junho de 2004. NETO, Geraldo H. , Wdson O., FABIO V. V.: Armazenamento de Imagens Médicas com InterBase, Centro Universitário Moura Lacerda, 2003. PACHECO, Peter S.: Parallel programming with MPI. San Francisco – California: Morgan Kaufmann Publishers, Inc. 1997. SOUZA, Marcelo; SOUZA, Josemar; MICHELI, Milena: Influência da comunicação no rendimento de uma máquina paralela virtual baseada em Redes ATM, Universidade Católica do Salvador, Curso de Informática, Salvador – Bahia, 2001. SOUZA, G.P.; PFITSCHER, H.; MELO, A.C.M.A. Computação Distribuída Baseada em Java Rodando em Arquiteturas Beowulf e Arquiteturas Heterogêneas. Departamento de Computação – Universidade de Brasília (UNB). 2003. STERLING, Thomas L. Salmon, John. Becker, Donald J. e Savaresse, Daniel F. How to build a Beowulf: a guide to the implementation and aplication of PC clusters. Massachusetts Institute of Technology. 1999.
78
TAMAE, Rodrigo Yoshio. SISPRODIMEX Sistema de Processamento Distribuído de Imagens Médicas com XML. Programa de Pós-Graduação em Ciência da Computação, Fundação de Ensino Eurípides Soares da Rocha 2005.
TANENBAUM, A.S.: Structured Computer Organization. Prentice Hall, 1999. WALKER, B. J. Introdution to Single System Image Clustering (2001). Disponivel em http://www.souceforge.net acessado em 15 de julho de 2005.
79
ANEXO
Código do JMPI-PLUS Classe MPI.java
package mpi; import java.util.LinkedList ; public class MPI { static int MAX_PROCESSOR_NAME = 256; static public Intracomm COMM_WORLD; static public Comm COMM_SELF; static public int GRAPH, CART; static public int ANY_SOURCE, ANY_TAG; static public Op MAX, MIN, SUM, PROD, LAND, BAND, LOR, BOR, LXOR, BXOR, MINLOC, MAXLOC; static public Datatype BYTE, CHAR, SHORT, BOOLEAN, INT, LONG, FLOAT, DOUBLE, PACKED, LB, UB, OBJECT; static public Datatype SHORT2, INT2, LONG2, FLOAT2, DOUBLE2; static public Request REQUEST_NULL; static public Group GROUP_EMPTY; static public int PROC_NULL; static public int BSEND_OVERHEAD; static public int UNDEFINED; static public int IDENT, CONGRUENT, SIMILAR, UNEQUAL; static public int TAG_UB, HOST, IO; static Errhandler ERRORS_ARE_FATAL, ERRORS_RETURN; static { System.loadLibrary("savesignals"); saveSignalHandlers(); System.loadLibrary("mpijava"); restoreSignalHandlers(); try { BYTE = new Datatype(); CHAR = new Datatype(); SHORT = new Datatype(); BOOLEAN = new Datatype(); INT = new Datatype(); LONG = new Datatype(); FLOAT = new Datatype(); DOUBLE = new Datatype(); PACKED = new Datatype(); LB = new Datatype(); UB = new Datatype(); OBJECT = new Datatype();
80
SHORT2 = new Datatype() ; INT2 = new Datatype() ; LONG2 = new Datatype() ; FLOAT2 = new Datatype() ; DOUBLE2 = new Datatype() ; MAX = new Op(1); MIN = new Op(2); SUM = new Op(3); PROD = new Op(4); LAND = new Op(5); BAND = new Op(6); LOR = new Op(7); BOR = new Op(8); LXOR = new Op(9); BXOR = new Op(10); MINLOC = new Op(new Minloc(), true); MAXLOC = new Op(new Maxloc(), true); GROUP_EMPTY = new Group(Group.EMPTY); REQUEST_NULL = new Request(Request.NULL); SetConstant(); ERRORS_ARE_FATAL = new Errhandler(Errhandler.FATAL); ERRORS_RETURN = new Errhandler(Errhandler.RETURN); COMM_WORLD = new Intracomm() ; } catch (MPIException e) { System.out.println(e.getMessage()) ; System.exit(1) ; } } static private native void saveSignalHandlers(); static private native void restoreSignalHandlers(); static public String [] Init(String[] args) throws MPIException { String [] newArgs = InitNative(args); restoreSignalHandlers(); BYTE.setBasic(1); CHAR.setBasic(2); SHORT.setBasic(3); BOOLEAN.setBasic(4); INT.setBasic(5); LONG.setBasic(6); FLOAT.setBasic(7); DOUBLE.setBasic(8); PACKED.setBasic(9); LB.setBasic(10); UB.setBasic(11); OBJECT.setBasic(12); SHORT2.setContiguous(2, MPI.SHORT); INT2.setContiguous(2, MPI.INT); LONG2.setContiguous(2, MPI.LONG); FLOAT2.setContiguous(2, MPI.FLOAT); DOUBLE2.setContiguous(2, MPI.DOUBLE); SHORT2.Commit(); INT2.Commit(); LONG2.Commit(); FLOAT2.Commit();
81
DOUBLE2.Commit(); COMM_WORLD.setType(Intracomm.WORLD); return newArgs ; } static private native String [] InitNative(String[] args); static private native void SetConstant(); static public native void Finalize() throws MPIException ; static public native double Wtime(); static public native double Wtick(); static public String Get_processor_name() throws MPIException { byte[] buf = new byte[MAX_PROCESSOR_NAME] ; int lengh = Get_processor_name(buf) ; return new String(buf,0,lengh) ; } static private native int Get_processor_name(byte[] buf) ; static public native boolean Initialized() throws MPIException ; private static byte [] buffer = null ; static public void Buffer_attach(byte[] buffer) throws MPIException { MPI.buffer = buffer ; Buffer_attach_native(buffer); } static private native void Buffer_attach_native(byte[] buffer); static public byte[] Buffer_detach() throws MPIException { Buffer_detach_native(buffer); byte [] result = MPI.buffer ; MPI.buffer = null ; return result ; } static private native void Buffer_detach_native(byte[] buffer); static LinkedList freeList = new LinkedList() ; synchronized static void clearFreeList() { while(!freeList.isEmpty()) ((Freeable) freeList.removeFirst()).free() ; } } class Maxloc extends User_function{ public void Call(Object invec, int inoffset, Object outvec, int outoffset, int count, Datatype datatype){ if(datatype == MPI.SHORT2) { short [] in_array = (short[])invec; short [] out_array = (short[])outvec; int indisp = inoffset ; int outdisp = outoffset ; for (int i = 0; i < count; i++, indisp += 2, outdisp += 2) { short inval = in_array [indisp] ; short outval = out_array [outdisp] ; if(inval > outval) {
82
out_array [outdisp ] = inval ; out_array [outdisp + 1] = in_array [outdisp + 1] ; } else if(inval == outval) { short inloc = in_array [indisp + 1] ; if(inloc < out_array [outdisp + 1]) out_array [outdisp + 1] = inloc ; } } } else if(datatype == MPI.INT2) { int [] in_array = (int[])invec; int [] out_array = (int[])outvec; int indisp = inoffset ; int outdisp = outoffset ; for (int i = 0; i < count; i++, indisp += 2, outdisp += 2){ int inval = in_array [indisp] ; int outval = out_array [outdisp] ; if(inval > outval) { out_array [outdisp ] = inval ; out_array [outdisp + 1] = in_array [outdisp + 1] ; } else if(inval == outval) { int inloc = in_array [indisp + 1] ; if(inloc < out_array [outdisp + 1]) out_array [outdisp + 1] = inloc ; } } } else if(datatype == MPI.LONG2) { long [] in_array = (long[])invec; long [] out_array = (long[])outvec; int indisp = inoffset ; int outdisp = outoffset ; for (int i = 0; i < count; i++, indisp += 2, outdisp += 2){ long inval = in_array [indisp] ; long outval = out_array [outdisp] ; if(inval > outval) { out_array [outdisp ] = inval ; out_array [outdisp + 1] = in_array [outdisp + 1] ; } else if(inval == outval) { long inloc = in_array [indisp + 1] ; if(inloc < out_array [outdisp + 1]) out_array [outdisp + 1] = inloc ; } } } else if(datatype == MPI.FLOAT2) { float [] in_array = (float[])invec; float [] out_array = (float[])outvec; int indisp = inoffset ; int outdisp = outoffset ; for (int i = 0; i < count; i++, indisp += 2, outdisp += 2){ float inval = in_array [indisp] ; float outval = out_array [outdisp] ;
83
if(inval > outval) { out_array [outdisp ] = inval ; out_array [outdisp + 1] = in_array [outdisp + 1] ; } else if(inval == outval) { float inloc = in_array [indisp + 1] ; if(inloc < out_array [outdisp + 1]) out_array [outdisp + 1] = inloc ; } } } else if(datatype == MPI.DOUBLE2) { double [] in_array = (double[])invec; double [] out_array = (double[])outvec; int indisp = inoffset ; int outdisp = outoffset ; for (int i = 0; i < count; i++, indisp += 2, outdisp += 2){ double inval = in_array [indisp] ; double outval = out_array [outdisp] ; if(inval > outval) { out_array [outdisp ] = inval ; out_array [outdisp + 1] = in_array [outdisp + 1] ; } else if(inval == outval) { double inloc = in_array [indisp + 1] ; if(inloc < out_array [outdisp + 1]) out_array [outdisp + 1] = inloc ; } } } else { System.out.println("MPI.MAXLOC: invalid datatype") ; try { MPI.COMM_WORLD.Abort(1); } catch(MPIException e) {} } } } class Minloc extends User_function{ public void Call(Object invec, int inoffset, Object outvec, int outoffset, int count, Datatype datatype){ if(datatype == MPI.SHORT2) { short [] in_array = (short[])invec; short [] out_array = (short[])outvec; int indisp = inoffset ; int outdisp = outoffset ; for (int i = 0; i < count; i++, indisp += 2, outdisp += 2){ short inval = in_array [indisp] ; short outval = out_array [outdisp] ; if(inval < outval) { out_array [outdisp ] = inval ; out_array [outdisp + 1] = in_array [outdisp + 1] ; } else if(inval == outval) { short inloc = in_array [indisp + 1] ; if(inloc < out_array [outdisp + 1]) out_array [outdisp + 1] = inloc ;
84
} } } else if(datatype == MPI.INT2) { int [] in_array = (int[])invec; int [] out_array = (int[])outvec; int indisp = inoffset ; int outdisp = outoffset ; for (int i = 0; i < count; i++, indisp += 2, outdisp += 2){ int inval = in_array [indisp] ; int outval = out_array [outdisp] ; if(inval < outval) { out_array [outdisp ] = inval ; out_array [outdisp + 1] = in_array [outdisp + 1] ; } else if(inval == outval) { int inloc = in_array [indisp + 1] ; if(inloc < out_array [outdisp + 1]) out_array [outdisp + 1] = inloc ; } } } else if(datatype == MPI.LONG2) { long [] in_array = (long[])invec; long [] out_array = (long[])outvec; int indisp = inoffset ; int outdisp = outoffset ; for (int i = 0; i < count; i++, indisp += 2, outdisp += 2){ long inval = in_array [indisp] ; long outval = out_array [outdisp] ; if(inval < outval) { out_array [outdisp ] = inval ; out_array [outdisp + 1] = in_array [outdisp + 1] ; } else if(inval == outval) { long inloc = in_array [indisp + 1] ; if(inloc < out_array [outdisp + 1]) out_array [outdisp + 1] = inloc ; } } } else if(datatype == MPI.FLOAT2) { float [] in_array = (float[])invec; float [] out_array = (float[])outvec; int indisp = inoffset ; int outdisp = outoffset ; for (int i = 0; i < count; i++, indisp += 2, outdisp += 2){ float inval = in_array [indisp] ; float outval = out_array [outdisp] ; if(inval < outval) { out_array [outdisp ] = inval ; out_array [outdisp + 1] = in_array [outdisp + 1] ; } else if(inval == outval) { float inloc = in_array [indisp + 1] ; if(inloc < out_array [outdisp + 1])
85
out_array [outdisp + 1] = inloc ; } } } else if(datatype == MPI.DOUBLE2) { double [] in_array = (double[])invec; double [] out_array = (double[])outvec; int indisp = inoffset ; int outdisp = outoffset ; for (int i = 0; i < count; i++, indisp += 2, outdisp += 2){ double inval = in_array [indisp] ; double outval = out_array [outdisp] ; if(inval < outval) { out_array [outdisp ] = inval ; out_array [outdisp + 1] = in_array [outdisp + 1] ; } else if(inval == outval) { double inloc = in_array [indisp + 1] ; if(inloc < out_array [outdisp + 1]) out_array [outdisp + 1] = inloc ; } } } else { System.out.println("MPI.MINLOC: invalid datatype") ; try { MPI.COMM_WORLD.Abort(1); } catch(MPIException e) {} } } }
Classe MPIException.java
package mpi; public class MPIException extends Exception { public MPIException() {super() ;} public MPIException(String message) {super(message) ;} }
Classe Comm.java
package mpi; import java.io.*; import java.lang.*; public class Comm { protected final static int SELF = 1; protected final static int WORLD = 2; protected static long nullHandle ; Comm() { }
86
void setType(int Type) { GetComm(Type); } private native void GetComm(int Type); protected Comm(long handle) { this.handle = handle; } public Object clone() { return new Comm(dup()); } protected native long dup(); public native int Size() throws MPIException ; public native int Rank() throws MPIException ; public static native int Compare(Comm comm1, Comm comm2) throws MPIException ; public native void Free() throws MPIException ; public native boolean Is_null(); public Group Group() throws MPIException { return new Group(group()); } private native long group(); public native boolean Test_inter() throws MPIException ; public Intercomm Create_intercomm(Comm local_comm, int local_leader, int remote_leader, int tag) throws MPIException { return new Intercomm(GetIntercomm(local_comm, local_leader, remote_leader, tag)) ; } public native long GetIntercomm(Comm local_comm, int local_leader, int remote_leader, int tag) ; public byte[] Object_Serialize(Object buf, int offset, int count, Datatype type) throws MPIException { if(type.Size() != 0) { byte[] byte_buf ; Object buf_els [] = (Object[])buf; try { ByteArrayOutputStream o = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(o); int base; for (int i = 0; i < count; i++) { base = type.Extent() * i; for (int j = 0 ; j < type.displacements.length ; j++) out.writeObject(buf_els[base + type.displacements[j]]); }
87
out.flush(); out.close(); byte_buf = o.toByteArray(); } catch(Exception ex) { ex.printStackTrace(); byte_buf = null ; } return byte_buf ; } //fecha o if else return new byte[0]; } public void Object_Deserialize(Object buf, byte[] byte_buf, int offset, int count, Datatype type) throws MPIException { if(type.Size() != 0) { Object buf_els [] = (Object[])buf; try { ByteArrayInputStream in = new ByteArrayInputStream(byte_buf); ObjectInputStream s = new ObjectInputStream(in); int base; for (int i = 0; i < count; i++) { base = type.Extent() * i; for (int j = 0 ; j < type.displacements.length ; j++) buf_els[base + offset + type.displacements[j]]=s.readObject(); } s.close(); } catch(Exception ex) { ex.printStackTrace(); } } } public void Send(Object buf, int offset, int count, Datatype type, int dest, int tag) throws MPIException { if (type.isObject()){ byte[] byte_buf = Object_Serialize(buf,offset,count,type); int[] length_buf = {byte_buf.length, count} ; send(length_buf, 0, 2, MPI.INT, dest, tag); // header send(byte_buf, 0, byte_buf.length, MPI.BYTE,dest, tag) ; } else { send(buf, offset, count, type, dest, tag); } } private native void send(Object buf, int offset, int count, Datatype type, int dest, int tag); // Executa uma operação de send e receive blocking
88
public Status Sendrecv(Object sendbuf, int sendoffset, int sendcount, Datatype sendtype, int dest, int sendtag, Object recvbuf, int recvoffset, int recvcount, Datatype recvtype, int source, int recvtag) throws MPIException { if(sendtype.isObject() || recvtype.isObject()) { Request reqs [] = {Isend(sendbuf, sendoffset, sendcount, sendtype, dest, sendtag), Irecv(recvbuf, recvoffset, recvcount, recvtype, source, recvtag)} ; Status stas [] = Request.Waitall(reqs) ; return stas [1] ; } else return Sendrecv(sendbuf, sendoffset, sendcount, sendtype, dest, sendtag, recvbuf, recvoffset, recvcount, recvtype, source, recvtag, new Status()); } private native Status Sendrecv(Object sbuf, int soffset, int scount, Datatype stype, int dest, int stag, Object rbuf, int roffset, int rcount, Datatype rtype, int source, int rtag, Status stat); // Executa uma operação de send e receive recebendo mensagens de um buffer public Status Sendrecv_replace(Object buf, int offset, int count, Datatype type, int dest, int sendtag, int source, int recvtag) throws MPIException { if(type.isObject()) { Status status = new Status() ; byte[] sendbytes = Object_Serialize(buf,offset,count,type); int[] length_buf = {sendbytes.length, count} ; Sendrecv_replace(length_buf, 0, 2, MPI.INT, dest, sendtag, source, recvtag, status) ;
89
byte [] recvbytes = new byte [length_buf[0]] ; Sendrecv(sendbytes, 0, sendbytes.length, MPI.BYTE, dest, sendtag, recvbytes, 0, recvbytes.length, MPI.BYTE, status.source, recvtag, status) ; Object_Deserialize(buf,recvbytes,offset,length_buf[1],type); status.object_count = length_buf[1] ; return status; } else return Sendrecv_replace(buf, offset, count, type, dest, sendtag, source, recvtag, new Status()); } private native Status Sendrecv_replace(Object buf, int offset, int count, Datatype type, int dest, int stag, int source, int rtag, Status stat); // Send utilizando o modo buffer public void Bsend(Object buf, int offset, int count, Datatype type, int dest, int tag) throws MPIException { if (type.isObject()){ byte[] byte_buf = Object_Serialize(buf,offset,count,type); int[] length_buf = {byte_buf.length, count} ; bsend(length_buf, 0, 2, MPI.INT, dest, tag); bsend(byte_buf, 0, length_buf[0], MPI.BYTE, dest, tag); } else bsend(buf, offset, count, type, dest, tag); } private native void bsend(Object buf, int offset, int count, Datatype type, int dest, int tag) ; // Send utilizando o modo sincrono public void Ssend(Object buf, int offset, int count, Datatype type, int dest, int tag) throws MPIException { if (type.isObject()){ byte[] byte_buf = Object_Serialize(buf,offset,count,type); int[] length_buf = {byte_buf.length, count} ;
90
send(length_buf, 0, 2, MPI.INT, dest, tag); ssend(byte_buf , 0, byte_buf.length, MPI.BYTE, dest, tag); } else ssend(buf, offset, count, type, dest, tag); } private native void ssend(Object buf, int offset, int count, Datatype type, int dest, int tag); // Send utilizando o modo pronto public void Rsend(Object buf, int offset, int count, Datatype type, int dest, int tag) throws MPIException { if (type.isObject()){ byte[] byte_buf = Object_Serialize(buf,offset,count,type); int[] length_buf = {byte_buf.length, count} ; rsend(length_buf, 0, 2, MPI.INT, dest, tag); rsend(byte_buf , 0, byte_buf.length, MPI.BYTE, dest, tag); } else rsend(buf, offset, count, type, dest, tag); } private native void rsend(Object buf, int offset, int count, Datatype type, int dest, int tag) ; // Comnicação Nonblocking // Modo standard public Request Isend(Object buf, int offset, int count, Datatype type, int dest, int tag) throws MPIException { if (type.isObject()) { byte[] byte_buf = Object_Serialize(buf,offset,count,type); int[] length_buf = {byte_buf.length, count} ; Request hdrReq = Isend(length_buf, 0, 2, MPI.INT, dest, tag, new Request()); Request req = new Request(hdrReq) ; Isend(byte_buf, 0, byte_buf.length, MPI.BYTE, dest, tag, req); return req; } else return Isend(buf, offset, count, type, dest, tag, new Request()); } protected native Request Isend(Object buf, int offset,
91
int count, Datatype type, int dest, int tag, Request req); // Modo buffer public Request Ibsend(Object buf, int offset, int count, Datatype type, int dest, int tag) throws MPIException { if (type.isObject()){ byte[] byte_buf = Object_Serialize(buf,offset,count,type); int[] length_buf = {byte_buf.length, count} ; Request hdrReq = Ibsend(length_buf, 0, 2, MPI.INT, dest, tag, new Request()); Request req = new Request(hdrReq) ; Ibsend(byte_buf, 0, byte_buf.length, MPI.BYTE, dest, tag, req); return req; } else return Ibsend(buf, offset, count, type, dest, tag, new Request()); } protected native Request Ibsend(Object buf, int offset, int count, Datatype type, int dest, int tag, Request req); // Modo sincrono public Request Issend(Object buf, int offset, int count, Datatype type, int dest, int tag) throws MPIException { if (type.isObject()){ byte[] byte_buf = Object_Serialize(buf,offset,count,type); int[] length_buf = {byte_buf.length, count} ; Request hdrReq = Issend(length_buf, 0, 2, MPI.INT, dest, tag, new Request()); Request req = new Request(hdrReq) ; Isend(byte_buf, 0, byte_buf.length, MPI.BYTE, dest, tag, req); return req; } else return Issend(buf, offset, count, type, dest, tag, new Request()); } protected native Request Issend(Object buf, int offset, int count,
92
Datatype type, int dest, int tag, Request req); // Modo pronto public Request Irsend(Object buf, int offset, int count, Datatype type, int dest, int tag) throws MPIException { if (type.isObject()){ byte[] byte_buf = Object_Serialize(buf,offset,count,type); int[] length_buf = {byte_buf.length, count} ; Request hdrReq = Irsend(length_buf, 0, 2, MPI.INT, dest, tag, new Request()); Request req = new Request(hdrReq) ; Isend(byte_buf, 0, byte_buf.length, MPI.BYTE, dest, tag, req); return req; } else return Irsend(buf, offset, count, type, dest, tag, new Request()); } protected native Request Irsend(Object buf, int offset, int count, Datatype type, int dest, int tag, Request req); // Receive utilizando o nonblocking public Request Irecv(Object buf, int offset, int count, Datatype type, int source, int tag) throws MPIException { if (type.isObject()){ int[] length_buf= new int[2]; Request req = new Request(buf, offset, count, type, tag, this, length_buf) ; Irecv(length_buf, 0, 2, MPI.INT, source, tag, req); return req; } else return Irecv(buf, offset, count, type, source, tag, new Request()); } protected native Request Irecv(Object buf, int offset, int count, Datatype type, int source, int tag,
93
Request req); public Prequest Send_init(Object buf, int offset, int count, Datatype type, int dest, int tag) throws MPIException { return new Prequest(Prequest.MODE_STANDARD, buf, offset, count, type, dest, tag, this) ; } public Prequest Bsend_init(Object buf, int offset, int count, Datatype type, int dest, int tag) throws MPIException { return new Prequest(Prequest.MODE_BUFFERED, buf, offset, count, type, dest, tag, this) ; } public Prequest Ssend_init(Object buf, int offset, int count, Datatype type, int dest, int tag) throws MPIException { return new Prequest(Prequest.MODE_SYNCHRONOUS, buf, offset, count, type, dest, tag, this) ; } public Prequest Rsend_init(Object buf, int offset, int count, Datatype type, int dest, int tag) throws MPIException { return new Prequest(Prequest.MODE_READY, buf, offset, count, type, dest, tag, this) ; } public Prequest Recv_init(Object buf, int offset, int count, Datatype type, int source, int tag) throws MPIException { return new Prequest(buf, offset, count, type, source, tag, this) ; } public int Pack(Object inbuf, int offset, int incount, Datatype datatype, byte[] outbuf, int position) throws MPIException { if (datatype.isObject()){ byte[] byte_buf = Object_Serialize(inbuf,offset,incount,datatype);
94
System.arraycopy(byte_buf,0,outbuf,position,byte_buf.length); return (position + byte_buf.length); } else return pack(inbuf, offset, incount, datatype, outbuf, position); } private native int pack(Object inbuf, int offset, int incount, Datatype data, byte[] outbuf, int position); public int Unpack(byte[] inbuf, int position, Object outbuf, int offset, int outcount, Datatype datatype) throws MPIException { if (datatype.isObject()){ Object buf_els [] = (Object[])outbuf; int ava=0; try { ByteArrayInputStream in = new ByteArrayInputStream(inbuf,position, inbuf.length-position); ObjectInputStream s = new ObjectInputStream(in); int base; for (int i = 0; i < outcount; i++){ base = datatype.Extent() * i; for (int j = 0 ; j < datatype.displacements.length ; j++) buf_els[base + offset + datatype.displacements[j]]=s.readObject(); } ava= in.available(); s.close(); }catch(Exception ex){ex.printStackTrace();} return inbuf.length- ava; } else return unpack(inbuf, position, outbuf, offset, outcount, datatype); } private native int unpack(byte[] inbuf, int position, Object outbuf, int offset, int outcount, Datatype type); public native int Pack_size(int incount, Datatype datatype) throws MPIException ; public Status Iprobe(int source, int tag) throws MPIException { return Iprobe(source,tag,new Status()); } private native Status Iprobe(int source, int tag,Status stat)
95
throws MPIException ; public Status Probe(int source, int tag) throws MPIException { return Probe(source,tag,new Status()); } private native Status Probe(int source, int tag,Status stat) throws MPIException ; public native int Attr_get(int keyval) throws MPIException ; public native int Topo_test() throws MPIException ; public native void Abort(int errorcode) throws MPIException ; public native void Errhandler_set(Errhandler errhandler) throws MPIException ; public Errhandler Errorhandler_get() throws MPIException { return new Errhandler(errorhandler_get()) ; } private native long errorhandler_get(); protected long handle; static { init(); } private static native void init(); }
Classe Group.java
package mpi; public class Group extends Freeable { protected final static int EMPTY = 0; private static native void init(); protected long handle; protected Group(int Type) { GetGroup(Type); } protected Group(long _handle) { handle = _handle;} private native void GetGroup(int Type); public native int Size() throws MPIException ; public native int Rank() throws MPIException ; public void finalize() throws MPIException { synchronized(MPI.class) { MPI.freeList.addFirst(this) ; } } native void free() ; public static native int [] Translate_ranks(Group group1,int [] ranks1, Group group2) throws MPIException ; public static native int Compare(Group group1, Group group2) throws MPIException ;
96
public static Group Union(Group group1, Group group2) throws MPIException { return new Group(union(group1, group2)) ; } private static native long union(Group group1, Group group2); public static Group Intersection(Group group1,Group group2) throws MPIException { return new Group(intersection(group1, group2)) ; } private static native long intersection(Group group1, Group group2); public static Group Difference(Group group1, Group group2) throws MPIException { return new Group(difference(group1, group2)) ; } private static native long difference(Group group1, Group group2) ; public Group Incl(int [] ranks) throws MPIException { return new Group(incl(ranks)) ; } private native long incl(int [] ranks); public Group Excl(int [] ranks) throws MPIException { return new Group(excl(ranks)) ; } private native long excl(int [] ranks) ; public Group Range_incl(int [][] ranges) throws MPIException { return new Group(range_incl(ranges)) ; } private native long range_incl(int [][] ranges) ; public Group Range_excl(int [][] ranges) throws MPIException { return new Group(range_excl(ranges)) ; } private native long range_excl(int [][] ranges) ; static { init(); } }
Datatype.java package mpi; public class Datatype extends Freeable { private final static int UNDEFINED = -1 ; private final static int NULL = 0 ; private final static int BYTE = 1 ; private final static int CHAR = 2 ; private final static int SHORT = 3 ; private final static int BOOLEAN = 4 ; private final static int INT = 5 ; private final static int LONG = 6 ; private final static int FLOAT = 7 ; private final static int DOUBLE = 8 ;
97
private final static int PACKED = 9 ; private final static int LB = 10 ; private final static int UB = 11 ; private final static int OBJECT = 12 ; private static native void init(); Datatype() {} Datatype(int Type) { setBasic(Type) ; } void setBasic (int Type) { switch(Type) { case OBJECT : baseType = OBJECT ; displacements = new int [1] ; lb = 0 ; ub = 1 ; lbSet = false ; ubSet = false ; break ; case LB : baseType = UNDEFINED ; displacements = new int [0] ; lb = 0 ; ub = 0 ; lbSet = true ; ubSet = false ; break ; case UB : baseType = UNDEFINED ; displacements = new int [0] ; lb = 0 ; ub = 0 ; lbSet = false ; ubSet = true ; break ; default : baseType = Type ; GetDatatype(Type); baseSize = size() ; } } private native void GetDatatype(int Type); private Datatype(int count, Datatype oldtype) throws MPIException { setContiguous(count, oldtype) ; } void setContiguous(int count, Datatype oldtype) throws MPIException { baseType = oldtype.baseType ; if(baseType == OBJECT || baseType == UNDEFINED) { int oldSize = oldtype.Size() ; boolean oldUbSet = oldtype.ubSet ;
98
boolean oldLbSet = oldtype.lbSet ; displacements = new int [count * oldSize] ; ubSet = count > 0 && oldUbSet ; lbSet = count > 0 && oldLbSet ; lb = Integer.MAX_VALUE ; ub = Integer.MIN_VALUE ; if(oldSize != 0 || oldLbSet || oldUbSet) { int oldExtent = oldtype.Extent() ; if(count > 0) { int ptr = 0 ; for (int i = 0 ; i < count ; i++) { int startElement = i * oldExtent ; for (int l = 0; l < oldSize; l++, ptr++) displacements [ptr] = startElement + oldtype.displacements[l] ; } int maxStartElement = oldExtent > 0 ? (count - 1) * oldExtent : 0 ; int max_ub = maxStartElement + oldtype.ub ; if (max_ub > ub) ub = max_ub ; int minStartElement = oldExtent > 0 ? 0 : (count - 1) * oldExtent ; int min_lb = minStartElement + oldtype.lb ; if (min_lb < lb) lb = min_lb ; } } else { if(count > 1) { System.out.println("Datatype.Contiguous: repeat-count specified " + "for component with undefined extent"); MPI.COMM_WORLD.Abort(1); } } } else { baseSize = oldtype.baseSize ; GetContiguous(count, oldtype) ; } } private native void GetContiguous(int count, Datatype oldtype); private Datatype(int count, int blocklength, int stride, Datatype oldtype, boolean unitsOfOldExtent) throws MPIException { baseType = oldtype.baseType ; if(baseType == OBJECT || baseType == UNDEFINED) { int oldSize = oldtype.Size() ; boolean oldUbSet = oldtype.ubSet ; boolean oldLbSet = oldtype.lbSet ; int repetitions = count * blocklength ; displacements = new int [repetitions * oldSize] ;
99
ubSet = repetitions > 0 && oldUbSet ; lbSet = repetitions > 0 && oldLbSet ; lb = Integer.MAX_VALUE ; ub = Integer.MIN_VALUE ; if(repetitions > 0) { if(oldSize != 0 || oldLbSet || oldUbSet) { int oldExtent = oldtype.Extent() ; int ptr = 0 ; for (int i = 0 ; i < count ; i++) { int startBlock = stride * i ; if(unitsOfOldExtent) startBlock *= oldExtent ; for (int j = 0; j < blocklength ; j++) { int startElement = startBlock + j * oldExtent ; for (int l = 0; l < oldSize; l++, ptr++) displacements [ptr] = startElement + oldtype.displacements[l] ; } int maxStartElement = oldExtent > 0 ? startBlock + (blocklength - 1) * oldExtent : startBlock ; int max_ub = maxStartElement + oldtype.ub ; if (max_ub > ub) ub = max_ub ; int minStartElement = oldExtent > 0 ? startBlock : startBlock + (blocklength - 1) * oldExtent ; int min_lb = minStartElement + oldtype.lb ; if (min_lb < lb) lb = min_lb ; } } else { if(unitsOfOldExtent) { System.out.println("Datatype.Vector: " + "old type has undefined extent"); MPI.COMM_WORLD.Abort(1); } else { if(blocklength > 1) { System.out.println("Datatype.Hvector: repeat-count specified " + "for component with undefined extent"); MPI.COMM_WORLD.Abort(1); } } } } } else { baseSize = oldtype.baseSize ; if(unitsOfOldExtent) GetVector(count, blocklength, stride, oldtype) ; else GetHvector(count, blocklength, stride, oldtype) ; } } private native void GetVector(int count, int blocklength, int stride,
100
Datatype oldtype); private native void GetHvector(int count, int blocklength, int stride, Datatype oldtype) ; private Datatype(int[] array_of_blocklengths, int[] array_of_displacements, Datatype oldtype, boolean unitsOfOldExtent) throws MPIException { baseType = oldtype.baseType ; if(baseType == OBJECT || baseType == UNDEFINED) { int oldSize = oldtype.Size() ; boolean oldUbSet = oldtype.ubSet ; boolean oldLbSet = oldtype.lbSet ; int count = 0 ; for (int i = 0; i < array_of_blocklengths.length; i++) count += array_of_blocklengths[i] ; displacements = new int [count * oldSize] ; ubSet = count > 0 && oldUbSet ; lbSet = count > 0 && oldLbSet ; lb = Integer.MAX_VALUE ; ub = Integer.MIN_VALUE ; if(oldSize != 0 || oldLbSet || oldUbSet) { int oldExtent = oldtype.Extent() ; int ptr = 0 ; for (int i = 0; i < array_of_blocklengths.length; i++) { int blockLen = array_of_blocklengths [i] ; if(blockLen > 0) { int startBlock = array_of_displacements [i] ; if(unitsOfOldExtent) startBlock *= oldExtent ; for (int j = 0; j < blockLen ; j++) { int startElement = startBlock + j * oldExtent ; for (int l = 0; l < oldSize; l++, ptr++) displacements [ptr] = startElement + oldtype.displacements[l] ; } int maxStartElement = oldExtent > 0 ? startBlock + (blockLen - 1) * oldExtent : startBlock ; int max_ub = maxStartElement + oldtype.ub ; if (max_ub > ub) ub = max_ub ; int minStartElement = oldExtent > 0 ? startBlock : startBlock + (blockLen - 1) * oldExtent ; int min_lb = minStartElement + oldtype.lb ; if (min_lb < lb) lb = min_lb ; } } } else { if(unitsOfOldExtent) { System.out.println("Datatype.Indexed: old type has undefined extent");
101
MPI.COMM_WORLD.Abort(1); } else { for (int i = 0; i < array_of_blocklengths.length; i++) if(array_of_blocklengths [i] > 1) { System.out.println("Datatype.Hindexed: repeat-count specified " + "for component with undefined extent"); MPI.COMM_WORLD.Abort(1); } } } } else { baseSize = oldtype.baseSize ; if(unitsOfOldExtent) GetIndexed(array_of_blocklengths, array_of_displacements, oldtype) ; else GetHindexed(array_of_blocklengths, array_of_displacements, oldtype) ; } } private native void GetIndexed(int[] array_of_blocklengths, int[] array_of_displacements, Datatype oldtype) ; private native void GetHindexed(int[] array_of_blocklengths, int[] array_of_displacements, Datatype oldtype) ; private Datatype(int[] array_of_blocklengths, int[] array_of_displacements, Datatype[] array_of_types) throws MPIException { baseType = UNDEFINED; for (int i = 0; i < array_of_types.length; i++) { int oldBaseType = array_of_types[i].baseType ; if(oldBaseType != baseType) { if(baseType == UNDEFINED) { baseType = oldBaseType ; if(baseType != OBJECT) baseSize = array_of_types[i].baseSize ; } else if(oldBaseType != UNDEFINED) { System.out.println("Datatype.Struct: All base types must agree..."); MPI.COMM_WORLD.Abort(1); } } } if(baseType == OBJECT || baseType == UNDEFINED) { int size = 0 ; for (int i = 0; i < array_of_blocklengths.length; i++) size += array_of_blocklengths[i] * array_of_types[i].Size(); displacements = new int [size] ; } ubSet = false ; lbSet = false ; lb = Integer.MAX_VALUE ; ub = Integer.MIN_VALUE ; int ptr = 0 ; for (int i = 0; i < array_of_blocklengths.length; i++) { int blockLen = array_of_blocklengths [i] ;
102
if(blockLen > 0) { Datatype oldtype = array_of_types [i] ; int oldBaseType = oldtype.baseType ; if(oldBaseType == OBJECT || oldBaseType == UNDEFINED) { int oldSize = oldtype.Size() ; boolean oldUbSet = oldtype.ubSet ; boolean oldLbSet = oldtype.lbSet ; if(oldSize != 0 || oldLbSet || oldUbSet) { int oldExtent = oldtype.Extent() ; int startBlock = array_of_displacements [i] ; for (int j = 0; j < blockLen ; j++) { int startElement = startBlock + j * oldExtent ; for (int l = 0; l < oldSize; l++, ptr++) displacements [ptr] = startElement + oldtype.displacements[l] ; } if (oldUbSet == ubSet) { int maxStartElement = oldExtent > 0 ? startBlock + (blockLen - 1) * oldExtent : startBlock ; int max_ub = maxStartElement + oldtype.ub ; if (max_ub > ub) ub = max_ub ; } else if(oldUbSet) { int maxStartElement = oldExtent > 0 ? startBlock + (blockLen - 1) * oldExtent : startBlock ; ub = maxStartElement + oldtype.ub ; ubSet = true ; } if (oldLbSet == lbSet) { int minStartElement = oldExtent > 0 ? startBlock : startBlock + (blockLen - 1) * oldExtent ; int min_lb = minStartElement + oldtype.lb ; if (min_lb < lb) lb = min_lb ; } else if(oldLbSet) { int minStartElement = oldExtent > 0 ? startBlock : startBlock + (blockLen - 1) * oldExtent ; lb = minStartElement + oldtype.lb ; lbSet = true ; } } else { if(blockLen > 1) { System.out.println("Datatype.Struct: repeat-count specified " + "for component with undefined extent"); MPI.COMM_WORLD.Abort(1); } } } } } if(baseType != OBJECT && baseType != UNDEFINED)
103
GetStruct(array_of_blocklengths, array_of_displacements, array_of_types, lbSet, lb, ubSet, ub) ; } private native void GetStruct(int[] array_of_blocklengths, int[] array_of_displacements, Datatype[] array_of_types, boolean lbSet, int lb, boolean ubSet, int ub) ; protected boolean isObject() { return baseType == OBJECT || baseType == UNDEFINED ; } public int Extent() throws MPIException { if(baseType == OBJECT || baseType == UNDEFINED) return ub - lb ; else return extent() / baseSize ; } private native int extent(); public int Size() throws MPIException { if(baseType == OBJECT || baseType == UNDEFINED) return displacements.length; else return size() / baseSize ; } private native int size(); public int Lb() throws MPIException { if(baseType == OBJECT || baseType == UNDEFINED) return lb; else return lB() / baseSize ; } private native int lB(); public int Ub() throws MPIException { if(baseType == OBJECT || baseType == UNDEFINED) return ub; else return uB() / baseSize ; } private native int uB(); public void Commit() throws MPIException { if (baseType != OBJECT && baseType != UNDEFINED) commit() ; } private native void commit(); public void finalize() throws MPIException { synchronized(MPI.class) { MPI.freeList.addFirst(this) ; } } native void free() ; public static Datatype Contiguous(int count, Datatype oldtype) throws MPIException {
104
return new Datatype(count, oldtype) ; } public static Datatype Vector(int count, int blocklength, int stride, Datatype oldtype) throws MPIException { return new Datatype(count, blocklength, stride, oldtype, true) ; } public static Datatype Hvector(int count, int blocklength, int stride, Datatype oldtype) throws MPIException { return new Datatype(count, blocklength, stride, oldtype, false) ; } public static Datatype Indexed(int[] array_of_blocklengths, int[] array_of_displacements, Datatype oldtype) throws MPIException { return new Datatype(array_of_blocklengths, array_of_displacements, oldtype, true) ; } public static Datatype Hindexed(int[] array_of_blocklengths, int[] array_of_displacements, Datatype oldtype) throws MPIException { return new Datatype(array_of_blocklengths, array_of_displacements, oldtype, false) ; } public static Datatype Struct(int[] array_of_blocklengths, int[] array_of_displacements, Datatype[] array_of_types) throws MPIException { return new Datatype(array_of_blocklengths, array_of_displacements, array_of_types) ; } protected long handle; protected int baseType ; protected int baseSize ; // or private protected int displacements[] ; protected int lb, ub ; protected boolean ubSet, lbSet ; static { init(); } }
Intracomm.java package mpi; public class Intracomm extends Comm { Intracomm() {} void setType(int type) {
105
super.setType(type) ; shadow = new Comm(dup()) ; } protected Intracomm(long handle) throws MPIException { super(handle) ; shadow = new Comm(dup()) ; } public Object clone() { try { return new Intracomm(dup()) ; } catch (MPIException e) { throw new RuntimeException(e.getMessage()) ; } } public Intracomm Split(int colour, int key) throws MPIException { long splitHandle = split(colour,key) ; if(splitHandle == nullHandle) return null ; else return new Intracomm(splitHandle) ; } private native long split(int colour, int key); public Intracomm Creat(Group group) throws MPIException { long creatHandle = creat(group) ; if(creatHandle == nullHandle) return null ; else return new Intracomm(creatHandle) ; } private native long creat(Group group); public native void Barrier() throws MPIException ; private void copyBuffer(Object inbuf, int inoffset, int incount, Datatype intype, Object outbuf, int outoffset, int outcount, Datatype outtype) throws MPIException { if(intype.isObject()) { Object [] inbufArray = (Object[])inbuf; Object [] outbufArray = (Object[])outbuf; int outbase = outoffset, inbase = inoffset ; int kout = 0 ; for (int j = 0 ; j < incount ; j++) { for (int k = 0 ; k < intype.displacements.length ; k++) outbufArray [outbase + outtype.displacements [kout]] = inbufArray [inbase + intype.displacements [k]] ; inbase += intype.Extent() ; kout++; if (kout == outtype.displacements.length){ kout = 0; outbase += outtype.Extent() ; } } } else {
106
byte [] tmpbuf = new byte [Pack_size(incount, intype)] ; Pack(inbuf, inoffset, incount, intype, tmpbuf, 0) ; Unpack(tmpbuf, 0, outbuf, outoffset, outcount, outtype) ; } } private Object newBuffer(Object template) { if(template instanceof Object[]) return new Object [((Object[]) template).length] ; if(template instanceof byte[]) return new byte [((byte[]) template).length] ; if(template instanceof char[]) return new char [((char[]) template).length] ; if(template instanceof short[]) return new short [((short[]) template).length] ; if(template instanceof boolean[]) return new boolean [((boolean[]) template).length] ; if(template instanceof int[]) return new int [((int[]) template).length] ; if(template instanceof long[]) return new long [((long[]) template).length] ; if(template instanceof float[]) return new float [((float[]) template).length] ; if(template instanceof double[]) return new double [((double[]) template).length] ; return null ; } public void Bcast(Object buf, int offset, int count, Datatype type, int root) throws MPIException { if (type.isObject()){ if (Rank() == root){ for (int dst = 0; dst < Size(); dst++) if (dst != root) shadow.Send(buf, offset, count, type, dst, 0); } else shadow.Recv(buf, offset, count, type, root, 0); } else bcast(buf, offset*type.Size(), count, type, root); } private native void bcast(Object buf, int offset, int count, Datatype type, int root); public void Gather(Object sendbuf, int sendoffset, int sendcount, Datatype sendtype, Object recvbuf, int recvoffset,
107
int recvcount, Datatype recvtype, int root) throws MPIException { if (sendtype.isObject()) { if (Rank() == root) { for (int src = 0; src < Size(); src++) { int dstOffset = recvoffset + recvcount * recvtype.Extent() * src ; if (src == root) copyBuffer(sendbuf, sendoffset, sendcount, sendtype, recvbuf, dstOffset, recvcount, recvtype) ; else shadow.Recv(recvbuf, dstOffset, recvcount, recvtype, src, 0); } } else shadow.Send(sendbuf, sendoffset, sendcount, sendtype, root, 0); } else gather(sendbuf, sendoffset*sendtype.Size(), sendcount,sendtype, recvbuf, recvoffset*recvtype.Size(), recvcount,recvtype, root); } private native void gather(Object sendbuf, int sendoffset, int sendcount, Datatype sendtype, Object recvbuf, int recvoffset, int recvcount, Datatype recvtype, int root); public void Gatherv(Object sendbuf, int sendoffset, int sendcount, Datatype sendtype, Object recvbuf, int recvoffset, int [] recvcount, int [] displs, Datatype recvtype, int root) throws MPIException { if (sendtype.isObject()){ if (Rank() == root){ for (int src = 0; src < Size(); src++){ int dstOffset = recvoffset + sendtype.Extent() * displs[src] ; if (src == root) copyBuffer(sendbuf, sendoffset, sendcount,sendtype, recvbuf, dstOffset, recvcount[src], recvtype); else shadow.Recv(recvbuf, dstOffset, recvcount[src], recvtype, src, 0); } } else shadow.Send(sendbuf, sendoffset, sendcount, sendtype, root, 0); } else gatherv(sendbuf , sendoffset*sendtype.Size(), sendcount, sendtype, recvbuf , recvoffset*recvtype.Size(), recvcount, displs, recvtype , root); } private native void gatherv(Object sendbuf,
108
int sendoffset, int sendcount, Datatype sendtype, Object recvbuf, int recvoffset, int [] recvcount, int [] displs, Datatype recvtype, int root); public void Scatter(Object sendbuf, int sendoffset, int sendcount, Datatype sendtype, Object recvbuf, int recvoffset, int recvcount, Datatype recvtype, int root) throws MPIException { if (sendtype.isObject()){ if (Rank() == root){ for (int dst = 0; dst < Size() ; dst++){ int srcOffset = sendoffset + sendcount * sendtype.Extent() * dst ; if (dst == root) copyBuffer(sendbuf, srcOffset, sendcount, sendtype, recvbuf, recvoffset, recvcount, recvtype); else shadow.Send(sendbuf, srcOffset, sendcount, sendtype, dst, 0); } } else shadow.Recv(recvbuf, recvoffset, recvcount, recvtype, root, 0); } else scatter(sendbuf, sendoffset*sendtype.Size(), sendcount, sendtype, recvbuf, recvoffset*recvtype.Size(), recvcount, recvtype, root); } private native void scatter(Object sendbuf, int sendoffset, int sendcount, Datatype sendtype, Object recvbuf, int recvoffset, int recvcount, Datatype recvtype, int root); public void Scatterv(Object sendbuf, int sendoffset, int [] sendcount, int [] displs, Datatype sendtype, Object recvbuf, int recvoffset, int recvcount, Datatype recvtype, int root) throws MPIException { if (sendtype.isObject()){ if (Rank() == root){ for (int dst = 0 ; dst < Size() ; dst++){ int srcOffset = sendoffset + sendtype.Extent() * displs[dst] ; if (dst == root) copyBuffer(sendbuf, srcOffset, sendcount[dst], sendtype, recvbuf, recvoffset, recvcount, recvtype);
109
else shadow.Send(sendbuf, srcOffset, sendcount[dst], sendtype, dst, 0); } } else shadow.Recv(recvbuf, recvoffset, recvcount, recvtype, root, 0); } else scatterv(sendbuf, sendoffset * sendtype.Size(), sendcount, displs, sendtype, recvbuf, recvoffset * recvtype.Size(), recvcount, recvtype, root); } private native void scatterv(Object sendbuf, int sendoffset, int [] sendcount, int [] displs, Datatype sendtype, Object recvbuf, int recvoffset, int recvcount, Datatype recvtype, int root); public void Allgather(Object sendbuf, int sendoffset, int sendcount, Datatype sendtype, Object recvbuf, int recvoffset, int recvcount, Datatype recvtype) throws MPIException { if (sendtype.isObject()){ Gather(sendbuf, sendoffset, sendcount, sendtype, recvbuf, recvoffset, recvcount, recvtype, 0); Bcast(recvbuf, recvoffset, Size() * recvcount, recvtype, 0); } else allgather(sendbuf, sendoffset*sendtype.Size(), sendcount, sendtype, recvbuf, recvoffset*recvtype.Size(), recvcount, recvtype); } private native void allgather(Object sendbuf, int sendoffset, int sendcount, Datatype sendtype, Object recvbuf, int recvoffset, int recvcount, Datatype recvtype); public void Allgatherv(Object sendbuf, int sendoffset, int sendcount, Datatype sendtype, Object recvbuf, int recvoffset, int [] recvcount, int [] displs, Datatype recvtype) throws MPIException { if (sendtype.isObject()){ Gatherv(sendbuf, sendoffset, sendcount, sendtype,
110
recvbuf, recvoffset, recvcount, displs, recvtype, 0); for (int src = 0; src < Size(); src++){ int dstOffset = recvoffset + sendtype.Extent() * displs[src] ; Bcast(recvbuf, dstOffset, recvcount[src], recvtype, 0); } } else allgatherv(sendbuf , sendoffset*sendtype.Size(), sendcount, sendtype, recvbuf , recvoffset*recvtype.Size(), recvcount, displs, recvtype); } private native void allgatherv(Object sendbuf, int sendoffset, int sendcount, Datatype sendtype, Object recvbuf, int recvoffset, int [] recvcount, int [] displs, Datatype recvtype); public void Alltoall(Object sendbuf, int sendoffset, int sendcount, Datatype sendtype, Object recvbuf, int recvoffset, int recvcount, Datatype recvtype) throws MPIException { if (sendtype.isObject()) for (int dst = 0; dst < Size(); dst++) { int srcOffset = sendoffset + sendcount * sendtype.Extent() * dst ; Gather(sendbuf, srcOffset, sendcount, sendtype, recvbuf, recvoffset, recvcount, recvtype, dst); } else alltoall(sendbuf, sendoffset*sendtype.Size(), sendcount, sendtype, recvbuf, recvoffset*recvtype.Size(), recvcount, recvtype); } private native void alltoall(Object sendbuf, int sendoffset, int sendcount, Datatype sendtype, Object recvbuf, int recvoffset, int recvcount, Datatype recvtype); public void Alltoallv(Object sendbuf, int sendoffset, int [] sendcount, int [] sdispls, Datatype sendtype, Object recvbuf, int recvoffset, int [] recvcount, int [] rdispls, Datatype recvtype) throws MPIException { if (sendtype.isObject())
111
for (int dst = 0; dst < Size(); dst++) { int srcOffset = sendoffset + sendtype.Extent() * sdispls[dst] ; Gatherv(sendbuf, srcOffset, sendcount[dst], sendtype, recvbuf, recvoffset, recvcount, rdispls, recvtype, dst); } else alltoallv(sendbuf, sendoffset*sendtype.Size(), sendcount, sdispls, sendtype, recvbuf, recvoffset*recvtype.Size(), recvcount, rdispls, recvtype); } private native void alltoallv(Object sendbuf, int sendoffset, int [] sendcount, int [] sdispls, Datatype sendtype, Object recvbuf, int recvoffset, int [] recvcount, int [] displs, Datatype recvtype); public void Reduce(Object sendbuf, int sendoffset, Object recvbuf, int recvoffset, int count, Datatype datatype, Op op, int root) throws MPIException { if (op.isUser()) { if (Rank() == root) { copyBuffer(sendbuf,sendoffset,count,datatype, recvbuf,recvoffset,count,datatype); Object tempbuf = newBuffer(recvbuf) ; for (int src = 0; src < Size(); src++) if(src != root) { shadow.Recv(tempbuf, 0, count, datatype, src, 0); op.Call(tempbuf, 0, recvbuf, recvoffset, count, datatype); } } else shadow.Send(sendbuf, sendoffset, count, datatype, root, 0); } else reduce(sendbuf, sendoffset, recvbuf, recvoffset, count, datatype, op, root) ; } private native void reduce(Object sendbuf, int sendoffset, Object recvbuf, int recvoffset, int count, Datatype datatype, Op op, int root); public void Allreduce(Object sendbuf, int sendoffset, Object recvbuf, int recvoffset, int count, Datatype datatype, Op op) throws MPIException { if (op.isUser()){ Reduce(sendbuf, sendoffset, recvbuf, recvoffset, count, datatype, op, 0); Bcast(recvbuf, recvoffset, count, datatype, 0); } else { allreduce(sendbuf, sendoffset, recvbuf, recvoffset, count, datatype, op) ; } } private native void allreduce(Object sendbuf, int sendoffset,
112
Object recvbuf, int recvoffset, int count, Datatype datatype, Op op) ; public void Reduce_scatter(Object sendbuf, int sendoffset, Object recvbuf, int recvoffset, int [] recvcounts, Datatype datatype, Op op) throws MPIException { if (op.isUser()) { int [] displs = new int [recvcounts.length] ; int count = 0 ; for (int i = 0; i < recvcounts.length; i++) { displs [i] = count ; count += recvcounts [i] ; } Object tempbuf = newBuffer(sendbuf) ; copyBuffer(sendbuf,sendoffset,count,datatype, tempbuf,sendoffset,count,datatype); Reduce(tempbuf, sendoffset, sendbuf, sendoffset, count, datatype, op, 0); Scatterv(tempbuf, sendoffset, recvcounts, displs, datatype, recvbuf, recvoffset, recvcounts[Rank()], datatype, 0); } else reduce_scatter(sendbuf, sendoffset, recvbuf, recvoffset, recvcounts, datatype, op) ; } private native void reduce_scatter(Object sendbuf, int sendoffset, Object recvbuf, int recvoffset, int [] recvcounts, Datatype datatype, Op op) ; public void Scan(Object sendbuf, int sendoffset, Object recvbuf, int recvoffset, int count, Datatype datatype, Op op) throws MPIException { if (op.isUser()){ if (Rank() == 0) copyBuffer(sendbuf,sendoffset,count,datatype, recvbuf,recvoffset,count,datatype); else{ shadow.Recv(recvbuf, recvoffset, count, datatype, Rank() - 1, 0); op.Call(sendbuf, sendoffset, recvbuf, recvoffset, count, datatype); } if (Rank() < Size() - 1) shadow.Send(recvbuf, recvoffset, count, datatype, Rank() + 1, 0); } else scan(sendbuf, sendoffset, recvbuf, recvoffset, count, datatype, op); } private native void scan(Object sendbuf, int sendoffset, Object recvbuf, int recvoffset, int count, Datatype datatype, Op op) ; public Cartcomm Create_cart(int [] dims, boolean [] periods, boolean reorder) throws MPIException { long cartHandle = GetCart(dims, periods, reorder) ; if(cartHandle == nullHandle) return null ; else return new Cartcomm(cartHandle) ; }
113
private native long GetCart(int [] dims, boolean [] periods, boolean reorder) ; public Graphcomm Create_graph(int [] index, int [] edges, boolean reorder) throws MPIException { long graphHandle = GetGraph(index,edges,reorder) ; if(graphHandle == nullHandle) return null ; else return new Graphcomm(graphHandle) ; } private native long GetGraph(int [] index,int [] edges, boolean reorder); private Comm shadow ; }
Prequest.java package mpi; public class Prequest extends Request { protected final static int MODE_STANDARD = 0 ; protected final static int MODE_BUFFERED = 1 ; protected final static int MODE_SYNCHRONOUS = 2 ; protected final static int MODE_READY = 3 ; private int src ; protected Prequest(int mode, Object buf, int offset, int count, Datatype type, int dest, int tag, Comm comm) { opTag = Request.OP_SEND ; this.mode = mode ; this.buf = buf; this.offset = offset; this.count = count; this.type = type; this.dest = dest; this.tag = tag; this.comm = comm ; if(type.isObject()) { typeTag = Request.TYPE_OBJECT ; length_buf = new int [2] ; hdrReq = new Request() ; } else typeTag = Request.TYPE_NORMAL ; } protected Prequest(Object buf, int offset, int count, Datatype type, int source, int tag, Comm comm) { opTag = Request.OP_RECV ; this.buf = buf; this.offset = offset; this.count = count; this.type = type; this.src = source; this.tag = tag;
114
this.comm = comm; if(type.isObject()) { typeTag = Request.TYPE_OBJECT ; length_buf = new int [2] ; } else typeTag = Request.TYPE_NORMAL ; } public void Start() throws MPIException { switch(typeTag) { case TYPE_NORMAL : switch(opTag) { case OP_SEND : switch(mode) { case MODE_STANDARD : comm.Isend(buf, offset, count, type, dest, tag, this); break; case MODE_BUFFERED : comm.Ibsend(buf, offset, count, type, dest, tag, this); break; case MODE_SYNCHRONOUS : comm.Issend(buf, offset, count, type, dest, tag, this); break; case MODE_READY : comm.Irsend(buf, offset, count, type, dest, tag, this); break; } break ; case OP_RECV : comm.Irecv(buf, offset, count, type, src, tag, this) ; break ; } break ; case TYPE_OBJECT : switch(opTag) { case OP_SEND : byte [] byte_buf = comm.Object_Serialize(buf,offset,count,type); length_buf[0] = byte_buf.length; length_buf[1] = count ; switch(mode) { case MODE_STANDARD : comm.Isend(length_buf, 0, 2, MPI.INT, dest, tag, hdrReq) ; comm.Isend(byte_buf, 0, byte_buf.length, MPI.BYTE, dest, tag, this); break; case MODE_BUFFERED : comm.Ibsend(length_buf, 0, 2, MPI.INT, dest, tag, hdrReq) ; comm.Ibsend(byte_buf, 0, byte_buf.length, MPI.BYTE, dest, tag, this); break; case MODE_SYNCHRONOUS : comm.Issend(length_buf, 0, 2, MPI.INT, dest, tag, hdrReq) ;
115
comm.Isend(byte_buf, 0, byte_buf.length, MPI.BYTE, dest, tag, this); break; case MODE_READY : comm.Irsend(length_buf, 0, 2, MPI.INT, dest, tag, hdrReq) ; comm.Isend(byte_buf, 0, byte_buf.length, MPI.BYTE, dest, tag, this); break; } break ; case OP_RECV : comm.Irecv(length_buf, 0, 2, MPI.INT, src, tag, this) ; break ; } break ; } } public static void Startall(Prequest [] array_of_request) throws MPIException { int req_length = array_of_request.length ; for (int i = 0; i<req_length; i++) array_of_request[i].Start() ; } }
Request.java package mpi; public class Request { protected final static int NULL = 0; protected final static int TYPE_NORMAL = 0; protected final static int TYPE_OBJECT = 1; protected final static int OP_SEND = 0; protected final static int OP_RECV = 1; protected Request hdrReq ; protected int typeTag = TYPE_NORMAL ; protected int opTag ; protected int mode ; protected Object buf; protected int offset; protected int count; protected Datatype type; protected int dest; protected int tag; protected Comm comm; protected int[] length_buf; private static native void init(); private native void GetReq(int Type); protected Request() {}
116
protected Request(int Type) { GetReq(Type); } protected Request(Request hdrReq) { typeTag = Request.TYPE_OBJECT ; opTag = Request.OP_SEND ; this.hdrReq = hdrReq ; } protected Request(Object buf, int offset, int count, Datatype type, int tag, Comm comm, int [] length_buf) { typeTag = Request.TYPE_OBJECT ; opTag = Request.OP_RECV ; this.buf = buf; this.offset = offset; this.count = count; this.type = type; this.tag = tag; this.comm = comm; this.length_buf = length_buf; } public native void Free() throws MPIException ; public native void Cancel() throws MPIException ; public native boolean Is_null(); private Status complete(Status status) throws MPIException { switch(typeTag) { case TYPE_NORMAL : break; case TYPE_OBJECT : switch(opTag) { case OP_SEND : hdrReq.Wait(new Status()) ; break; case OP_RECV : int index = status.index ; byte[] byte_buf = new byte[length_buf[0]]; status = comm.Recv(byte_buf, 0, length_buf[0], MPI.BYTE, status.source, tag) ; comm.Object_Deserialize(buf, byte_buf, offset, length_buf[1], type); status.object_count = length_buf[1]; status.index = index ; break; } break ; } return status ; } public Status Wait() throws MPIException { Status result = new Status(); Wait(result);
117
return complete(result) ; } private native Status Wait(Status stat); public Status Test() throws MPIException { Status result = new Status(); if (Test(result) == null) return null; else return complete(result) ; } private native Status Test(Status stat); public static Status Waitany(Request [] array_of_request) throws MPIException { Status result = new Status(); Waitany(array_of_request, result); if(result == null) return null; else return array_of_request[result.index].complete(result) ; } private static native Status Waitany(Request [] array_of_request, Status stat); public static Status Testany(Request [] array_of_request) throws MPIException { Status result = new Status(); result = Testany(array_of_request, result); if(result == null) return null; else return array_of_request[result.index].complete(result) ; } private static native Status Testany(Request [] array_of_request, Status stat); public static Status[] Waitall (Request [] array_of_request) throws MPIException { Status result[] = waitall(array_of_request); for (int i = 0 ; i < array_of_request.length ; i++) result [i] = array_of_request [i].complete(result [i]) ; return result; } private static native Status[] waitall(Request [] array_of_request); public static Status[] Testall(Request [] array_of_request) throws MPIException { Status result[] = testall(array_of_request); if (result == null) return null; else { for (int i = 0 ; i < array_of_request.length ; i++) result [i] = array_of_request [i].complete(result [i]) ; return result;
118
} } private static native Status[] testall(Request [] array_of_request); public static Status[] Waitsome(Request [] array_of_request) throws MPIException { Status result[] = waitsome(array_of_request); for (int i = 0 ; i < result.length ; i++) result [i] = array_of_request [result [i].index].complete(result [i]) ; return result; } private static native Status[] waitsome(Request [] array_of_request); public static Status[] Testsome(Request [] array_of_request) throws MPIException { Status result[] = testsome(array_of_request); if (result == null) return null; else { for (int i = 0 ; i < result.length ; i++) result [i] = array_of_request [result [i].index].complete(result [i]) ; return result; } } private static native Status[] testsome(Request [] array_of_request); protected long handle; protected Object bufSave ; protected int countSave, offsetSave ; protected long bufbaseSave, bufptrSave ; protected int baseTypeSave ; protected long commSave, typeSave ; static { init(); } }
Livros Grátis( http://www.livrosgratis.com.br )
Milhares de Livros para Download: Baixar livros de AdministraçãoBaixar livros de AgronomiaBaixar livros de ArquiteturaBaixar livros de ArtesBaixar livros de AstronomiaBaixar livros de Biologia GeralBaixar livros de Ciência da ComputaçãoBaixar livros de Ciência da InformaçãoBaixar livros de Ciência PolíticaBaixar livros de Ciências da SaúdeBaixar livros de ComunicaçãoBaixar livros do Conselho Nacional de Educação - CNEBaixar livros de Defesa civilBaixar livros de DireitoBaixar livros de Direitos humanosBaixar livros de EconomiaBaixar livros de Economia DomésticaBaixar livros de EducaçãoBaixar livros de Educação - TrânsitoBaixar livros de Educação FísicaBaixar livros de Engenharia AeroespacialBaixar livros de FarmáciaBaixar livros de FilosofiaBaixar livros de FísicaBaixar livros de GeociênciasBaixar livros de GeografiaBaixar livros de HistóriaBaixar livros de Línguas
Baixar livros de LiteraturaBaixar livros de Literatura de CordelBaixar livros de Literatura InfantilBaixar livros de MatemáticaBaixar livros de MedicinaBaixar livros de Medicina VeterináriaBaixar livros de Meio AmbienteBaixar livros de MeteorologiaBaixar Monografias e TCCBaixar livros MultidisciplinarBaixar livros de MúsicaBaixar livros de PsicologiaBaixar livros de QuímicaBaixar livros de Saúde ColetivaBaixar livros de Serviço SocialBaixar livros de SociologiaBaixar livros de TeologiaBaixar livros de TrabalhoBaixar livros de Turismo