Performance de ArrayList e outras Collections

24 respostas
C

Olá pessoal, recentemente fiz um mini-aplicativo muito interessante que realiza N loops de escrita (utilizando geralmente o método add() ou put()) de objetos utilizando quase todas as variações polimórficas de instânciar uma mesma classe da API Collection do Java, medindo assim o tempo em Millisegundos dos loops de execução de cada tipo de estrutura List, ArrayList, Set, HashSet, Map, HashMap e etc.

Por exemplo abaixo segue alguns dos testes que realizo:

// Testando Instância pura de um ArrayList
private void testingArrayList(int max) {

		ArrayList<Object> array = new ArrayList<Object>();
		long start = System.currentTimeMillis();
		for (int i = 0; i < max; i++) {
			array.add(i);
		}
		long end = System.currentTimeMillis();
		long time = (end - start);
		System.out.println("Tempo: "+time);
	}

// Testando Instância polimórfica de um ArrayList com List

private void testingList2ArrayList(int max) {

		List<Object> array = new ArrayList<Object>();
		long start = System.currentTimeMillis();
		for (int i = 0; i < max; i++) {
			array.add(i);
		}
		long end = System.currentTimeMillis();
		long time = (end - start);
		System.out.println("Tempo: "+time);
	}

// Testando Instância polimórfica de um ArrayList com uma Collection

private void testingCollection2ArrayList(int max) {

		Collection<Object> array = new ArrayList<Object>();
		long start = System.currentTimeMillis();
		for (int i = 0; i < max; i++) {
			array.add(i);
		}
		long end = System.currentTimeMillis();
		long time = (end - start);
		System.out.println("Tempo: "+time);
	}

Resultado de alguns dos testes:

ArrayList -> ArrayList | 5 milliseconds.
List -> ArrayList | 6 milliseconds.
Collection -> ArrayList | 7 milliseconds.

Em muito desses testes a diferença de tempo é perceptível, por mais que esses tempos NÃO signifiquem muita diferença em questão de performance.

É óbvio que exista diferença de tempo entre dois ou mais tipos de estrutura distintos, exemplo ArrayList, LinkedList, Set, Maps, pois cada um possuem suas características peculiares

Porém a minha dúvida é se alguém sabe me explicar, porque dessas diferenças em casos de utilização de uma mesma estrutura que é o caso citado acima, pois está utilizando apenas a estrutura ArrayList.

O código fonte esta nesse link para conferir os inúmeros testes de benchmark que fiz:

espero que gostem e se alguém descobrir me avisem!

24 Respostas

M

caio.ribeiro.pereira

Estou com uma duvida,
em qual versão Java você realizou os teste?

Há mudanças na Java 6 para 7?

D

Cara aqui tem um link bem interessante sobre isso:

http://blog.caelum.com.br/performance-hashset-em-vez-de-arraylist/

C

Utilizei Java 6, talvez eu tenha me expressado mal, mas creio que se utilizar o mini-aplicativo e ver meu código-fonte você entenderá melhor a minha dúvida.

C

Acho (acho) que pelo fato da variável de referência definir quais são os métodos que poderão ser invocados, a JVM custa mais a descobrir quais são esses métodos quando usado uma variável de referência no topo da arvore.

Realmente não faz sentido pra mim, pois se for pensar dessa maneira que eu falei, deveria ser ao contrário. Quando mais em baixo da arvore fosse a referencia, mais métodos poderiam ser chamados, e construtores acionados.

edit:
Desculpe, sobre o primeiro post, eu entendi a pergunta chave errado, mas estou pesquisando. Caso descobrir eu aviso.

M

Então caio,

acho que seja na parte de organização de cada tipo de vetor que o metodo usa, se ele inseri mais rapido, se ele retira mais rapido, entende?

Seria mais uma organização de dados mais estruturada que a outra(ou menos).

C

chimufox, é mais ou menos isso tbm que eu creio que a JVM deve trabalhar para gerar essa mínima diferença de tempo

M

chimufox:
Acho (acho) que pelo fato da variável de referência definir quais são os métodos que poderão ser invocados, a JVM custa mais a descobrir quais são esses métodos quando usado uma variável de referência no topo da arvore.

Realmente não faz sentido pra mim, pois se for pensar dessa maneira que eu falei, deveria ser ao contrário. Quando mais em baixo da arvore fosse a referencia, mais métodos poderiam ser chamados, e construtores acionados.

Chimufox

Acho que não, a JVM trata todos metodos igualmente, o que diferencia é a construção do metodo, o que ele faz com as informações.
Como disse, um metodo para organizar um Array, há muitas diretrizes, como em Estrutura de dados, Lista, Filha, Pila.

E

O benchmark, tal como foi escrito, não é apropriado para determinar as diferenças de tempo porque não deu oportunidade para a JVM efetuar a compilação just-in-time de seu código. Provavelmente você mediu o tempo de execução interpretado, não compilado, porque você:
a) Não efetuou um “aquecimento” dos métodos para que eles fossem compilados antes,
b) Usou muito poucas iterações, e
c) Usou um método pouco preciso para testar (System.currentTimeMillis()).

M

Segue um link de uma analise feita em Java Collections

Algoritmos

C

entanglement

a) Não efetuou um “aquecimento” dos métodos para que eles fossem compilados antes.

O que seria fazer um "aquecimento dos métodos"?

b) Usou muito poucas iterações,

O uso foi de iterações utilizando apenas escrita de objetos, mas futuramente irei detalhar mais os benchmarks, se possível suas sugestões serão bem-vindas!

c) Usou um método pouco preciso para testar (System.currentTimeMillis()).

Sobre a medição de tempo a princípio eu estava medindo em nanosegundos (System.nanoTime()) , ai mudei para millisegundos pois achei q por mais que as diferenças fossem mais detalhadas, não seria muito útil para benchmark em nanosegundos, é muito detalhe desnecessário, pelo menos na minha opinião.
C

mateuscs muito interessante essee pdf :smiley:

C

entanglement:
O benchmark, tal como foi escrito, não é apropriado para determinar as diferenças de tempo porque não deu oportunidade para a JVM efetuar a compilação just-in-time de seu código. Provavelmente você mediu o tempo de execução interpretado, não compilado, porque você:
a) Não efetuou um “aquecimento” dos métodos para que eles fossem compilados antes,
b) Usou muito poucas iterações, e
c) Usou um método pouco preciso para testar (System.currentTimeMillis()).

Essa parece a resposta mais plausível xD

M

Então o kara usa um teoria de analise interessante para avaliar.

O mais bacana é que usa tipo diversos de dados.

M

chimufox:
entanglement:
O benchmark, tal como foi escrito, não é apropriado para determinar as diferenças de tempo porque não deu oportunidade para a JVM efetuar a compilação just-in-time de seu código. Provavelmente você mediu o tempo de execução interpretado, não compilado, porque você:
a) Não efetuou um “aquecimento” dos métodos para que eles fossem compilados antes,
b) Usou muito poucas iterações, e
c) Usou um método pouco preciso para testar (System.currentTimeMillis()).

Essa parece a resposta mais plausível xD

A analise de dados por essa lado, deixou a desejar realmente.

É isso que estamos discutindo a analise feita por ele.

Mais em tese, qual seria melhor chimufox?

Poste opiniões e dados pra gente ver sua resolução
;D

C

É isso ai pessoal, postem opiniões que ai quando estiver com tempo livre irei melhorar esse mini-app :smiley:

M

Caio, como na analise feita pelo autor da analise Kapil Viren Ahuja,

Tente usar diversos tipos de dados, desde Integer a Objetos complexos.

E poste a analise ae pra gente
;D

C

Blz, mas eu ainda gostaria de saber o que significa “aquecimento de métodos”.

L

Muito interessante este assunto! Pela minha lógica eu diria que ArrayList seria o mais eficiente…

Por exemplo, quando queremos uma lista mais genérica, fazemos:

List lista = new ArrayList();

ou

Collection lista = new ArrayList();

então, na verdade, quando precisamos de algo mais específico e prático, usamos o ArrayList em si, sem os recursos que as outras coleções oferecem, ou seja, uma classe mais enxuta de recursos, o que me leva a crer que tem uma performance melhor (lembrando de que Collection é interface e List uma ‘sub’ interface de Collection)

antes que me joguem pedra e comprovem que eu não entendo nada de Java…só queria dizer que é a minha opinião.

C

lucasportela é essa a idéia do post, mostrar q faz pequena diferença de performance a maneira como instanciamos uma mesma estrutura só que polimórficamente, se na maioria dos casos só irá utilizar os metodos basicos, pelos testes que fiz no meu mini-benchmark, Instancias usando Collections ou AbstractCollections são as que menos demoram no processamento, mas vale lembrar que ainda tem muitas outras medidas a implementar nesse app para comprovar realmente essa teoria.

enfim, o código-fonte ta ai quem quiser colaborar nele, será bem-vindo!

L

Caio, primeiramente parabéns pela iniciativa e pela bela orientação ao objeto

Verifiquei que está alimentando as coleções desta forma:

for (int i = 0; i < getMax(); i++) { array[i]; }

ai então eu questiono, será que a iteração do for each pode influenciar na performance? Teria quer ser verificado a questão de performance dos loops também

for (Object i: array ) {}
C

lucasportela me corrija caso eu esteja errado, mas ate o que eu sei, um ForEach de array é convertido para um For comun pela JVM em tempo de compilacao ou runtime. (Nao sei bem em qual dos dois tempos) e esse foi um dos motivos por ter feito com um comando For, pois haveria um delay ao percorrer esse loop usando um ForEach e outro problema é que se vc verificar bem o código verá que o loop é apenas para realizar os metodos “add()” de objetos no array baseado no total de objetos que o usuario digitar, e nisso eu teria que criar um tamanho fixo no array para depois percorrer um ForEach nas Collections que inicialmente estariam sem objetos incluidos e isso causaria um NullPointerException utilizando um ForEach. Agora caso no futuro seja implementado nesse projeto metodos de “get()” para medir a performance de leitura, poderia testar se há uma pequena diferença entre um loop For, ForEach ou ate mesmo um iterator(). :smiley:

e obrigado pelo elogio! enfim esse projetinho soh foi um meio de matar a curiosidade sobre como realmente funciona essas Collections e suas respectivas performance. Ta no git, quem quiser colaborar sera bem-vindo!! XD

E

Por “aquecimento” você precisa entender o seguinte.

A JVM, ao ler um arquivo .class e executar um método, começa inicialmente a INTERPRETAR os bytecodes. Ela faz isso porque muitas vezes um determinado método será executado uma única vez, não tem nenhum loop e o tempo de execução interpretado pode ser inferior ao tempo de compilação para instruções de máquina + tempo de execução do código compilado.
Se o método for executado uma quantidade suficiente de vezes então a JVM “se toca’” e vê que provavelmente você irá executar mais vezes ainda esse método. Aí ela compila esse método para instruções de máquina.
Isso é bem complexo (e põe complexo nisso) porque podemos ter casos em que um pedaço do método continua interpretado e partes do método (tipicamente contendo loops) são compiladas para código nativo.

http://www.java.net/node/671068
http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

C

Nossa muito interessante esse esquema de “aquecimento de método”, me corrija caso esteja errado, mas ao que entendi do que vc disse, a JVM pega os trechos de códigos que mais se repetem e faz um tipo de cache na JVM para dar um desempenho melhor na execução delas, é mais ou menos isso o q eu disse??

E

Não é um cache; é realmente compilação para código nativo, tal como se fosse em uma outra linguagem como C/C++ ou Delphi. Como essa compilação é relativamente lenta, ela deve ser postergada até quando realmente necessário.

Criado 11 de agosto de 2011
Ultima resposta 12 de ago. de 2011
Respostas 24
Participantes 6