Este é um post disponível para assinantes MVPArtigo Java Magazine 30 - Mais Performance com Java
Artigo Publicado pela Java Magazine 30.

Mais Performance com Java
Explorando Novas Técnicas de Otimização do HotSpot
Saiba em detalhes como os compiladores JIT aceleram seu código, e fique por dentro das últimas novidades de desempenho nas novas JVMs
Osvaldo Pinali Doederlein
O Java evoluiu muito desde a sua introdução, crescendo continuamente tanto em funcionalidades quanto em desempenho. As antigas percepções sobre mau desempenho do Java já são algo do passado. Hoje, somente desenvolvedores muito desinformados – ou de má-fé – afirmariam que o Java é “lento por ser interpretado”, ou que compiladores JIT não podem competir com compiladores estáticos (o que se tem visto ultimamente é com freqüência o oposto).
Por outro lado, a tecnologia das JVMs não está "completa", no sentido que não reste nada a melhorar. Também não faz mágica – toda plataforma e toda linguagem faz algumas opções, e certos aspectos do Java ainda dificultam a obtenção de um desempenho máximo em casos específicos.
Este artigo possui dois objetivos gerais. O primeiro é discutir, de maneira franca, as questões de desempenho do Java, os princípios da otimização de código, e as capacidades e limitações das JVMs atuais. O segundo é atualizar o leitor sobre as últimas novidades de desempenho do Java – especificamente as novas otimizações que estão vindo com o Mustang e o IBM JDK 5.0.
Desempenho e a evolução das linguagens
No estudo de linguagens de programação e compiladores, vemos que o trabalho dos otimizadores vai no sentido contrário do realizado pelos projetistas de linguagens. Tudo o que é construído por uma linguagem de programação tem que ser “desconstruído” pelos otimizadores.
Linguagens de mais alto nível têm um desempenho intrínseco pior[1], pois as primitivas de programação utilizadas no código-fonte vão se distanciando cada vez mais das operações fundamentais das CPUs. Essa distância exige um mapeamento a ser feito pelo compilador. Quanto mais complexo o mapeamento, mais inteligência será necessária para fazê-lo da forma mais eficiente possível.
Por exemplo, para fazer um loop, um programador poderia escrever o seguinte código em Assembly x86:
LOOP:
MOV EDI, 1000 # executar 1000 iterações
... corpo do loop ...
DEC EDI
JNZ EDI, LOOP # decrementa, e repete até chegar a 0
Este código é "de baixo nível", não só por utilizar as instruções nativas de uma CPU, mas também por explorar suas idiossincrasias. Por exemplo, usamos o registrador EDI como variável de controle do loop, porque este registrador é muito útil para indexar arrays de dados. Também programamos o loop ao contrário, contando de 1000 a 0 (ao invés de contar de 0 a 1000, como seria mais intuitivo). Isso é feito para explorar o comportamento de instruções como a DEC, a qual além de diminuir um valor em 1, também compara o resultado com zero – o que economiza uma instrução extra que teríamos que usar (a CMP) caso o valor de parada fosse qualquer outro.
Em suma, uma linguagem de baixo nível não só aumenta a quantidade de código que tem de ser escrito, mas também induz o programador a contorcer seus algoritmos para adaptar-se ao comportamento da linguagem[2]. E o Assembly, se por um lado facilita obter o melhor desempenho possível, tem desvantagens sérias e conhecidas. É extremamente difícil, pouco produtivo e perigoso (sendo difícil evitar bugs catastróficos) – e a portabilidade é zero.
Na medida em que as linguagens sobem de nível, resolvem-se essas desvantagens, mas sempre com algum custo em desempenho. Por exemplo, em Java 5.0 podemos escrever um loop assim:
for (Cliente c: clientes)
// utiliza c
As melhorias são evidentes. Facilidade de programação e manutenção; portabilidade, inclusive binária; segurança – qualquer erro gera no máximo uma exceção, por exemplo se clientes==null teremos uma NullPointerException, mas não uma pane ou corrupção do heap.
Os custos, por outro lado, também são grandes. Código nativo deve ser gerado em tempo de execução, pois inclui otimizações nem sempre triviais – como alocar variáveis para registradores ou selecionar as instruções de CPU ideais para cada operação. A JVM é obrigada a verificar indexações de arrays e usos de referências nulas. Se clientes for uma coleção, sua iteração exigirá objetos Iterator, que exigem alocação no heap, invocação a métodos polimórficos como "
ATENÇÃO! A exibição deste artigo foi interrompida.
Este é um post disponível para assinantes MVP
Space do autor


0
0
