|
The Java Specialists' Newsletter
Issue 090 2004-06-22
Category:
Performance
Java version: Sun JDK 1.5.0-beta2 Autoboxing: facilidades e performanceby Dr. Heinz M. Kabutz
Special Thank You!
I would like to thank Rafael Steil from the Grupo de Usuarios
Java - GUJ, from Brazil for translating our newsletters into Portuguese. In addition, I would like to
thank Vanessa Sabino for assisting Rafael in the translation work.
Disclaimer: Since I do not know any Portuguese, I make no warranty for the
accuracy of the translation. Heinz
Autoboxing: facilidades e performance
Em meu último curso de Java em Pretoria ( África do Sul ), demonstrei aos alunos como funcionava o recurso de autoboxing. Nossos testes mostraram que autoboxing pode ser ineficiente. Por mais legal que o recurso seja, ele é também especialmente perigoso. Estudantes de Java podem usar autoboxing por engano, afetando a performance de forma negativa.
Contudo, antes que eu me aprofunde no assunto, eu gostaria de lhes mostrar um pouco sobre o novo loop for
O novo construtor for
Por volta de dois anos e meio atrás, na newsletter TJSN
040, reclamei amargamente da síntaxe do Iterator. Eu sentia que usando um loop
for ou while
juntamente com conversões de tipo tendia a tornar o código-fonte feio. A nova forma de utilização do for no JDK 1.5 finalmente confirma a minha reclamação. Eis como você pode usá-lo em seu código:
import java.util.*;
public class NewFor {
public static void main(String[] args) {
// we can use type-safe collections...
Collection<String> names = new ArrayList<String>();
names.add("Maxi");
names.add("Connie");
names.add("Helene");
names.add("Heinz");
//names.add(new Integer(42)); -- does not compile!
// look at the new for construct:
for (String name : names) {
System.out.println("name = " + name);
}
}
}
Podemos combinar autoboxing com Generics. Autoboxing é o processo de converter
tipos primitivos para objetos e vice-versa, automaticamente. Meu código de
exemplo na newsletter 40 poderia ser escrito mais elegantemente como:
public void showAging(Collection<Integer> ages) {
for(int age : ages) {
System.out.println("Now you're " + age +
", in 3 years time, you'll be " + (age + 3));
}
}
Descobri por acidente que é possível também usar o novo loop for com arrays:
public class NewForArrays {
public static void main(String[] args) {
String[] names = {"Maxi", "Connie", "Helene", "Heinz"};
for (String name : names) {
System.out.println("name = " + name);
}
}
}
Não é maravilhoso? Após muito tempo, uma forma consistente de iterar por coleções e arrays. Isso funciona mesmo para arrays de tipos primitivos:
public class NewForPrimitiveArrays {
public static void main(String[] args) {
int[] daysPerMonth = {31,28,31,30,31,30,31,31,30,31,30,31};
int totalDays = 0;
for (int days : daysPerMonth) {
totalDays += days;
}
System.out.println("totalDays = " + totalDays);
}
}
Quando descompilamos a classe, vemos o seguinte ( não tão ruim ):
public class NewForPrimitiveArrays {
public static void main(String args[]) {
int daysPerMonth[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
int totalDays = 0;
int arr$[] = daysPerMonth;
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; i$++) {
int days = arr$[i$];
totalDays += days;
}
System.out.println("totalDays = " + totalDays);
}
}
Um grande medo dos programadores é que você pode usar algumas formas de construção que por sua vez tornam o código inaceitavelmente lento. Eis aqui um código que compara a performance entre a forma "antiga" e a "nova":
import java.util.*;
public class NewForPerformance {
public static void main(String[] args) {
// let's look at the performance difference of for construct
Collection<Integer> numbers = new ArrayList<Integer>(10000);
for(int i=0;i<10000; i++) {
// I can add an "int" to a collection of Integer, thanks
// to the autoboxing construct shamelessly copied from C#
numbers.add((int)Math.random());
}
oldFor(numbers);
newFor(numbers);
}
private static void oldFor(final Collection<Integer> numbers) {
measureIterations("oldFor", new Runnable() {
public void run() {
for(Iterator<Integer> it = numbers.iterator(); it.hasNext();) {
Integer i = it.next();
}
}
});
}
private static void newFor(final Collection<Integer> numbers) {
measureIterations("newFor", new Runnable() {
public void run() {
for(Integer i : numbers);
}
});
}
private static void measureIterations(String method, Runnable r) {
long start = System.currentTimeMillis();
int iterations = 0;
while(System.currentTimeMillis() - start <= 2000) {
r.run();
iterations++;
}
System.out.println(method + ": " + iterations + " in " +
(System.currentTimeMillis()-start) + "ms");
}
}
Quando rodo este programa, tenho a seguinte saída:
oldFor: 3532 in 2003ms
newFor: 3561 in 2003ms
Ambos métodos são similares o suficiente que podemos declarar que não existe diferença entre eles. Então, você deixaria de usar o novo loop
for para ficar lutando com Iterators?
Perigo espreitando logo abaixo
Vamos assumir que exércitos de programadores Java irão passar a usar Generics e o novo
for. sso irá tornar arrays redundantes, uma vez que autoboxing nos permite usar
ints com Collections (repare que estamos adicionando e pegando valures da Collection como um tipo de dado primitivo int, mas o tipo de objeto na collection é um Integer):
import java.util.*;
public class AutoBoxing {
public static void main(String[] args) {
Collection<Integer> values = new ArrayList<Integer>();
for (int i=0; i<100; i++) {
values.add(i);
}
for(int val : values) {
System.out.println(val);
}
}
}
Vamos ver o que acontece quando temos uma collection de números, e desejamos incrementar todos os valores:
import java.util.*;
public class AutoBoxingIncrement {
public static void main(String[] args) {
// we set up a Collection containing Integers and an int[]
List<Integer> values = new ArrayList<Integer>();
int[] valuesArray = new int[1000];
for (int i = 0; i < 1000; i++) {
values.add(i);
valuesArray[i] = i;
}
// let's time how quickly we can increment the 1000 values
long time = System.currentTimeMillis();
// we must do it a few times to see the difference
for (int j = 0; j < 100000; j++) {
for (int i = 0; i < values.size(); i++) {
values.set(i, values.get(i) + 1);
}
}
System.out.println("autoboxing with generics took " +
(System.currentTimeMillis() - time) + "ms");
// now we try with an array
time = System.currentTimeMillis();
for (int j = 0; j < 100000; j++) {
for (int i = 0; i < valuesArray.length; i++) {
valuesArray[i]++;
}
}
System.out.println("Using a plain array took " +
(System.currentTimeMillis() - time) + "ms");
}
}
Quando rodo este programe em meu notebook, vejo uma enorme diferença de performance. O acesso direto ao array de ints é por volta de 20 vezes mais rápida.
autoboxing com Generics levou 9954ms
Usando um array normal levou 551ms
Generics são extremamente fáceis de aprender, e após usando-os por algumas horas, eu não quero voltar para Collections sem tipagem. Contudo, temos que estar conscientes quando fazemos algumas coisas estúpdas que podem impactar na performance, como usar autoboxing quando não devemos.
Saudações,
Heinz
Performance Articles
Related Java Course
Discuss at The Java Specialist Club
|