4
Foz do Iguaçu, 27 a 29 de Outubro de 2004 Comparação entre Java e C++ na Computação Numérica Claudio Schepke, Andrea Schwertner Charão Universidade Federal de Santa Maria Laboratório de Sistemas de Computação Informática/CT - UFSM Campus - 97105-900, Santa Maria, RS {schepke, andrea}@inf.ufsm.br Resumo A computação numérica é uma importante ferra- menta para a solu ção de problemas nas áreas cientfjicas e da engenharia. Muitas aplicações nestas áreas re- querem a resolução de grandes sistemas lineares de forma rápida e eficiente, geralmente através de métodos numéricos iterati vos. A programação ori entada a ob- jetos torna a implementação desses métodos mais sim- ples, uma vez que permite codificar um algoritmo em um n(vel de abstração bastante pr óximo à formulação ma- temá tica do método. O presente artigo apresenta uma compa ração entre as linguagens Java e C++ para a resolução da equação de Laplace através do método itera- ti vo do Gradiente Conjugado. Tal comparação contribui, principalmente, para ressaltar as vantagens e desvanta- gens destas linguagens no contexto da computação ci- entfjica de alto desempenho. 1. Introdução A computação científica está gera lmente associada à resolução de diversos problemas nas áreas das ciências na- turais e exatas, bem como nas engenharias. Exemplos deste tipo de problemas incluem o cálculo do deslocamento es- telar, a simulação de determin adas condições climáticas da terra, a descoberta de novos medicamentos, o teste da ae- rodinâmica de carros e aviões e a determinação de locais viáveis para uma possível extração de petróleo [1). Todas estas aplicações requerem processamento de alto desempe- nho, o que depende fortemente da combinação de hardware e software utilizados. A programação orientada a objetos é uma opção para o desenvolvimento de aplicações que resolvem proble- mas modelados matematicamente. Em particular, este pa- radi gma favorece a co nstrução de programas em um nível de abstração próximo às formulações matemáticas. No en- tanto, as linguagens que seguem este paradigma possuem diferenças entre s i, tanto em relação a seus recursos para o desenvolvimento de programas, como também ao desem- penho na execução do código resultante. Este artigo tem por objetivo apresentar uma co mparação entre as linguagens Java e C++ no contexto da computação c.ientífica de alto desempenho. Para isso, foi esco lhido um problema recorrente em muitas aplicações científicas: a resolução da equação de Laplace através do método numéri co do Gradiente Conjugado. As próximas seções tratam do uso da orientação a objet os aplicada a métodos numéricos, do uso de Java para o alto desempenho e, fi- nalmente, apresentam a descrição da implementação e os resultados obtidos. 2. Uso da orientação a objetos para o cálculo numérico Uma vasta gama de fenômenos físicos pode ser mode- lada através de eq uações diferenciais parciais, sendo re- solvíveis numericamente com o auxílio de técnicas de discretização. A utilização destas técnicas resulta geral- mente em um sistema linear de equações algébricas, cuja so lução pode ser determinada através de métodos di- retos ou iterativos [7). A programação eficiente destes métodos de reso lução de sistemas não é uma tarefa tri- vial, mas atualmente pode-se contar co m uma variedade de bibliotecas que facilitam sua utilização [12) [3]. No de- senvolvimento de stas bibliotecas, um dos paradigmas que se revelam adequados é a programação orientada a obje- tos [8). A orientação a objetos traz consigo algumas ca- racterísticas interessantes para o desenvolvimento de ap li cações de cálculo numérico. Uma das vantagens está no fato de que os problemas podem ser co nsiderados ob- je tos. Desta forma, os dados representam os atributos e os métodos numéricos ap li cados representam as fu ncio- nalidades. Já o uso de classes facilita a organização do código-fonte. Assim os métodos podem compartilhar ca- racterísticas para a solução de um problema, sem a ne- 208

Comparação entre Java e C++ na Computação Numérica · Uma das vantagens está no fato de que os problemas podem ser considerados ob jetos. Desta forma, os dados representam os

  • Upload
    vulien

  • View
    217

  • Download
    0

Embed Size (px)

Citation preview

Foz do Iguaçu, 27 a 29 de Outubro de 2004

Comparação entre Java e C++ na Computação Numérica

Claudio Schepke, Andrea Schwertner Charão Universidade Federal de Santa Maria

Laboratório de Sistemas de Computação Informática/CT - UFSM Campus - 97105-900, Santa Maria, RS

{schepke, andrea}@inf.ufsm.br

Resumo

A computação numérica é uma importante ferra­menta para a solução de problemas nas áreas cientfjicas e da engenharia. Muitas aplicações nestas áreas re­querem a resolução de grandes sistemas lineares de forma rápida e eficiente, geralmente através de métodos numéricos iterativos. A programação orientada a ob­jetos torna a implementação desses métodos mais sim­ples, uma vez que permite codificar um algoritmo em um n(vel de abstração bastante próximo à formulação ma­temática do método. O presente artigo apresenta uma comparação entre as linguagens Java e C++ para a resolução da equação de Laplace através do método itera­tivo do Gradiente Conjugado. Tal comparação contribui, principalmente, para ressaltar as vantagens e desvanta­gens destas linguagens no contexto da computação ci­entfjica de alto desempenho.

1. Introdução

A computação científica está geralmente associada à resolução de diversos problemas nas áreas das ciências na­turais e exatas, bem como nas engenharias. Exemplos deste tipo de problemas incluem o cálculo do deslocamento es­telar, a simulação de determinadas condições climáticas da terra, a descoberta de novos medicamentos, o teste da ae­rodinâmica de carros e aviões e a determinação de locais viáveis para uma possível extração de petróleo [1). Todas estas aplicações requerem processamento de alto desempe­nho, o que depende fortemente da combinação de hardware e software utilizados.

A programação orientada a objetos é uma opção para o desenvolvimento de aplicações que resolvem proble­mas modelados matematicamente. Em particular, este pa­radigma favorece a construção de programas em um nível de abstração próximo às formulações matemáticas. No en­tanto, as linguagens que seguem este paradigma possuem

diferenças entre si, tanto em relação a seus recursos para o desenvolvimento de programas, como também ao desem­penho na execução do código resultante.

Este artigo tem por objetivo apresentar uma comparação entre as linguagens Java e C++ no contexto da computação c.ientífica de alto desempenho. Para isso, foi escolhido um problema recorrente em muitas aplicações científicas: a resolução da equação de Laplace através do método numérico do Gradiente Conjugado. As próximas seções tratam do uso da orientação a objetos aplicada a métodos numéricos, do uso de Java para o alto desempenho e, fi­nalmente, apresentam a descrição da implementação e os resultados obtidos.

2. Uso da orientação a objetos para o cálculo numérico

Uma vasta gama de fenômenos físicos pode ser mode­lada através de equações diferenciais parciais, sendo re­solvíveis numericamente com o auxílio de técnicas de discretização. A utilização destas técnicas resulta geral­mente em um sistema linear de equações algébricas, cuja solução pode ser determinada através de métodos di­retos ou iterativos [7). A programação eficiente destes métodos de resolução de sistemas não é uma tarefa tri­vial, mas atualmente pode-se contar com uma variedade de bibliotecas que facilitam sua utilização [12) [3]. No de­senvolvimento destas bibliotecas, um dos paradigmas que se revelam adequados é a programação orientada a obje­tos [8).

A orientação a objetos traz consigo algumas ca­racterísticas interessantes para o desenvolvimento de aplicações de cálculo numérico. Uma das vantagens está no fato de que os problemas podem ser considerados ob­jetos. Desta forma, os dados representam os atributos e os métodos numéricos aplicados representam as funcio­nalidades. Já o uso de classes facilita a organização do código-fonte. Assim os métodos podem compartilhar ca­racterísticas para a solução de um problema, sem a ne-

208

Anais do 5° Workshop de Computação de Alto Desempenho, WSCAD 2004

cessidade de replicação de trechos de código. Também o encapsulamento ajuda na minimização da interde­pendência entre as partes do código, ocultando detalhes de implementação.

3. Java e Alto Desempenho

O desempenho não foi originalmente considerado um fator relevante no desenvolvimento da linguagem Java. De fato, geralmente aponta-se como principais carac­terísticas desta linguagem a orientação a objetos, o suporte à distribuição, a segurança e a portabilidade. Mesmo as­sim, mudanças vêm ocorrendo para poder-se usufruir dos pontos fortes da linguagem e, ao mesmo tempo, ob­ter uma melhora da eficiência do código gerado [13]. Em geral, a opção pelo uso de Java ocorre principal­mente para programas em rede e em especial à Web.

O uso de Java no processamento científico possui al­guns limitantes [5], sendo que um deles é a falta de arrays de várias dimensões com um formato regular. No caso da implementação de uma matriz numérica, é necessário uti­lizar uma estrutura de vetor de vetores. É importante no­tar que operações com matrizes de· grande porte aplicadas à álgebra linear são algumas das operações mais comuns e com maior custo computacional em aplicações científicas.

Outra característica que tem impacto no desempenho de Java é a verificação de exceções, que garante robustez aos programas escritos nesta linguagem. Devido ao tempo destinado à realização dessa tarefa, geralmente ocorre uma perda de desempenho na execução geral de um programa. Java faz a verificação de ponteiros que estejam apontando para regiões de memória não alocadas ou ultrapassando os limites pré-definidos de um vetor. Também o coletor de lixo consome um tempo significativo do período de execução, mas oferece uma vantagem ao programador, pelo fato dele não precisar liberar explicitamente a memória.

Uma terceira característica a ser levada em conta no con­texto da computação científica está na não existência de es­truturas simples que possibilitam o trabalho com números complexos e operadores matemáticos específicos. A solução adotada é a utilização de classes especiais para a representação de objetos cujos tipos não estejam defi­nidos como padrão da linguagem e a implementação de métodos específicos, como nas operações utilizadas so­bre matrizes e vetores. Para atender as necessidades de de­terminados grupos de programadores, já foram criados pacotes especializados, como os que possibilitam o traba­lho com álgebra linear e funções matemáticas com um alto nível de abstração sobre os dados manipulados [4]. Re­centemente, foram feitas algumas melhorias em Java para a geração de código eficiente. Inicialmente, os byteco­des gerados na compilação se tomaram mais específicos, para, em seguida, poderem ser compilados para a arquite-

209

tura nativa na primeira execução do código. Atualmente, os compiladores permitem gerar código de máquina, direta­mente do código-fonte Java, para uma determinada arquite­tura, sendo possível encontrar vários compiladores para este fim. Uma solução alternativa está na possibilidade de se­rem feitas chamadas de métodos nativos, implementados em outra linguagem, dentro do código Java (JNI- Java Na­tive Interface).

Outra melhoria está na tentativa de diminuição do tempo de comunicação entre processos. Uma das possíveis for­mas está na utilização de um código RMI (Remate Method lnvocation) mais eficiente [8]. Soluções como o RMI assíncrono não bloqueiam os processos nas chama­das de funções remotas, algo que permite "mascarar"o tempo de comunicação no caso de sistemas distribuídos. Existem também implementações para uma Máquina Vir­tual Distribuída, que oculta do usuário a existência de vários nós-processadores. Seu uso permite um grande pa­ralelismo, já que os diversos fluxos de código podem ser executados sobre diversos processadores.

Ainda na área de paralelismo, uma das idéias mais recen­tes é a computação em grids [9]. O objetivo, neste caso, é permitir o processamento remoto em máquinas ociosas dis­poníveis de diversos lugares, enviando e recebendo tarefas e respostas por rede, para que grandes quantias de dados se­jam processadas. O uso de Java na implementação de pro­gramas de gerenciamento de grids é um campo de pesquisa e desenvolvimento bastante ativo atualmente.

4. Comparação entre Java e C++

Esta seção visa apresentar uma comparação entre os tem­pos de execução de duas versões (Java e C++) de uma mesma aplicação. O objetivo desta comparação é apresen­tar uma diferença prática existente mi simulação de um código e, em especial, do método do Gradiente Conju­gado, não buscando fazer uma análise teórica do custo das operações, já que estes foram amplamente discutidos [2]. No passado foram feitas diversas comparações com base no tempo de execução de cada uma das operações [ 11 ]. É pre­ciso, entretanto, contextualizar as mudanças feitas, tanto na implementação, com o uso de bibliotecas específicas, como na geração de código por parte dos compiladores, anali­sando as suas principais partes.

A comparação feita neste trabalho tem como base uma aplicação cuja finalidade é determinar a solução de um problema modelado pela Equação de Laplace, utilizando o método do Gradiente Conjugado (GC) [7]. Esse método explora as propriedades das matrizes simétricas, positi­vas, definidas e, em especial, as matrizes esparsas, sendo um dos métodos numéricos iterativos mais utilizados. Suas operações se concentram em tomo de operações entre ve­tores, matrizes e vetores, e escalares com vetores. A cada

iteração são feitas duas operações entre matrizes e vetores, três produtos internos e três atualizações de vetor. O método tem como característica a rápida convergência.

Dentre as bibliotecas existentes para operações com álgebra linear para Java, foi escolhida a biblioteca JMP [10], versão 0.7.1, totalmente implementada nesta lin­guagem. As principais características de JMP se referem ao seu uso de matrizes esparsas, o oferecimento de soluções paralelas para as operações com matrizes e vetores, espe­cialmente o BLAS (Basic Linear Algebra Subprograms), a disponibilidade de diversos pré-condicionadores, a fa­cilidade para leitura de vetores e matrizes, bem como a possibilidade de decomposição, além de outros recur­sos para a criação de programas. Já para a implementação em C++, foi escolhida a biblioteca SparseLib++ [6], versão 1.5. O foco principal de SparseLib++ são as es­truturas de dados e o suporte computacional a métodos iterativos. A fim de propiciar uma execução eficiente des­tes métodos, esta biblioteca possui uma vasta gama de formatos de armazenamento, como estruturas para matri­zes e vetores compactados, o que pode diminuir o número de operações a serem aplicadas, bem como possibili­tam ao programador optar por um formato que melhor se adapta ao problema. Também existem métodos de lei­tura para diversos formatos de compressão.

Para fins de comparação, foram gerados três códigos exe­cutáveis: C++, Java bytecode e Java compilado para código nativo. O código escrito nas duas linguagens é bastante se­melhante, mudando apenas a forma como as bibliotecas re­solvem o sistema. Para o código C++ foi usado o compila­dor GNU G++IGCC, versão 3.3.2. Para o código em Java foi utilizado o compilador Javac, com o JDK 1.4.02, e o compilador GNU GCJ/GCC, versão 3.3.2, para a geração de código nativo.

Os testes foram realizados em um computador com pro­cessador Intel Celeron de 1 GHz e memória de 128MB. As matrizes utilizadas foram de 2.000 por 2.000, 20.000 por 20.000 e 200.000 por 200.000 elementos. Após as execuções, obteve-se as médias das 1 O execuções realiza­das para cada caso, sendo que o desvio padrão se apresen­tou baixo.

Em termos de medição dos tempos de execução, o pro­grama foi dividido em três partes. Na primeira parte, é medido o tempo de inicialização das variáveis e o preen­chimento dos vetores e matrizes. Em seguida, é calculado o tempo para a fase do pré-condicionamento. O pré­condicionamento consiste em simplificar a matriz, para que as operações feitas pelos métodos numéricos possam con­vergir de modo mais rápido. Sem o pré-condicionamento, os métodos seriam mais custosos para a aquisição da solução. Para esta aplicação, o pré-condicionador es­colhido foi a fatorização LU incompleta [7]. Por fim, o módulo em que se tem o maior interesse é a parte

Foz do Iguaçu, 27 a 29 de Outubro de 2004

em que há a aplicação do método do Gradiente Conju­gado. Além destas três regiões foi medido o tempo total de execução. Todos esses resultados estão apresentados nas ta­belas e no gráfico a seguir.

210

Tabela 1 - Tempos de execução com C++

C++ 2000 20000 200000 Inicio 0.05012 0.63963 6.44687 Precond 0.00442 0.04990 0.49968 MétodoGC 0.00521 0.07280 0.72616 Total 0.06196 0.76465 8. 14305

Tabela 2- Tempos de execução com Java compilado

Java Compilado 2000 20000 200000 Inicio 0.0105 0.1402 1.0903 Precond 0.0552 12.1857 1823.1154 MétodoGC 0.0243 0.2198 1.9792 Total 0.0908 12.5467 1826.1864

Tabela 3- Tempos de execução com Java bytecode

Java bytecode 2000 20000 200000 Inicio 0.0953 0.7638 7.1611 Precond 0.2807 16.0573 1893.0888 MétodoGC 0.069 0.7331 6.8919 Total 0.4898 17.6046 1907.2011

8

7

6

~5 8.4 E Ql 3 ....

2

1

o 2000 20000 200000

Dimensão da Matriz

Figura 1. Comparação entre os tempos de execução do método GC

Anais do 5° Workshop de Computação de Alto Desempenho, WSCAD 2004

5. Discussão dos Resultados

O desempenho apresentado na comparação entre o método do Gradiente Conjugado, nas três execuções, apre­senta bons resultados no uso de Java compilado para código nativo. À medida que aumenta o número de elemen­tos da matriz, aumenta também a relação de eficiência de Java compilado sobre Java bytecode e C++ para o método em questão, ainda que a execução de C++ seja a me­lhor. Já os tempos de inicialização são semelhantes para Java bytecode e C++, melhorando para o código Java com­pilado, o que mostra um maior esforço por parte da implementação C++, para a criação das estruturas com as informações.

O pré-condicionamento, entretanto, é uma etapa que se mostra bastante penosa para a implementação em Java. O uso de pré-condicionadores é fundamental para uma rápida convergência dos métodos numéricos, já que os mesmos servem para se obter um bom valor inicial para a resposta. Bons pré-condicionadores conseguem diminuir bastante o tempo a ser gasto pelo método iterativo. Todavia, um grande número de operações precisa ocorrer para tanto. Devido às estruturas especiais de armazenamento disponíveis na bi­blioteca SparseLib++, no que diz respeito a matrizes espar­sas, como também o fator natural do desempenho de C++ ser melhor de modo geral, o tempo do pré-condicionamento apresentou-se bem menor para este caso.

6. Conclusão

A utilização da orientação a objetos para operações re­lacionadas à computação numérica mostra-se como um re­curso de programação viável através do uso de algumas bi­bliotecas. Atualmente existem muitas bibliotecas em C++ voltadas para o uso científico. Muitas delas são amplamente utilizadas e já foram alvo de otimizações. Este é o caso da SparseLib++, uma biblioteca que possui uma excelente implementação para as operações com matrizes esparsas.

O uso de Java como uma linguagem de programação para aplicações científicas tem-se mostrado como uma opção, apresentando a portabilidade e a abstração ori­entada a objetos como ponto forte. Mesmo assim, as bibliotecas em Java podem ainda ser consideradas es­cassas e as existentes são geralmente implementadas para aplicações específicas. No caso da biblioteca JMP, a mesma ainda está em desenvolvimento, sofrendo cons­tantes revisões e incremento de funções. No entanto, seu desempenho ainda se apresenta inferior ao que é exi­gido pela computação científica. Cabe destacar que, no momento, existem muitas bibliotecas em desenvolvi­mento, tentando abranger mais funcionalidades [3].

A otimização de código na compilação, as melhorias na implementação nas funções de baixo nível , bem como as

n1

características.a serem melhoradas nas linguagens, poderão apresentar resultados significativos para os próximos anos. Todavia, uma solução mais usual pode ser o processamento paralelo para a obtenção de resultados em um tempo com­putacional menor.

Referências

[I) G. R. Andrews. Foundations of Multithreaded, Parai/e/, and Distributed Programming. Addison-Wesley, US, 200 I.

[2) M. Bergman and P. Sloot. Basic Linear Algebra Subsys­tems. 1993. Dept. ofComp. Sys., Univ. of Amsterdam, num­ber CAMAS-TR-2.1.2.1, Department of Computer Systems, University of Amsterdam, The Netherlands.

[3) R. Boisvert and R. Pozo. JavaNumerics. 2004. http://math.nist.govljavanumerics.

[4] R. F. Boisvert, J. J. Dongarra, R. Pozo, K. A. Remington. and G. W. Stewart. Developing numericallibraries in Java. 199M. http://www.cs.ucsb.edulconferences/java981papersljnt.pdf.

[S] G. G. H. Cavalheiro. Princ(pios da Programaçcio Concor­rente. ANAIS, Segunda Escola Regional de Alto Desempe­nho- Sociedade Brasileira de Computação - Instituto de In­formática da UFRGS I UNISINOS I ULBRA. 2002.

[6) J. Dongarra, R. Pozo, K. Remington, X. Niu, and A. Lumsdaine. A sparse matrix library in C++ for high performance architectures. 1994. ftp://gams.nist.govlpublpozolpaperslsparse.ps.Z.

[7) J. J . Dongarra, L. S. Duff, D. C. Sorensen, and H. A. Van der Yorst. Numerical Linear Algebra for High-Performcmce Compllters. SIAM, 1998.

[8) J. Farley. Java Distributed Computing. O'Rei lly, 1998.

[9] I. Foster and C. Kesselman. The Grid: 8/ueprillfs for a New Computing lnfrastructure. Morgan Kaufmann, 1999.

[ lO] B. O. Heimsund. JMP - A sparse matrix library for Java, 2004. http://www.math.uib.nol bjomoh/jmplindex2.html.

[ li) A. G. Hoekstra, P. M. A. Sloot, M. J . De Haan, and L. Hertz­berger. Time complexity analysis for distributed mcmory computers - Implementation of a parallel Conjugate Gradi­ent method. 1991. In J. van. Leeuwen, editor, Computing Science in the Netherlands, pages 249-266. Elsevier Science.

[12) T. Yeldhuizen. The Object-Oriented Numerics Page, 2004. http://www.oonumerics.orgloon.

[ 13) P. Wu, S. Midkiff, J. Moreira, and M. Gupta. Effi-cient Support for Complex Numbers in Java. 1999. http://www.cs.ucsb.edulconferences/java991papersl53-wu.pdf.