Aferição pequenas Arrays vs. listas em Java: é meu código de benchmarking errado?
-
19-09-2019 - |
Pergunta
Disclaimer: Eu olhei através este pergunta e esta questão mas ambos se descarrilou por pequenas detalhes e geral otimização é-desnecessárias preocupações. Eu realmente preciso de todo o desempenho I pode entrar em meu aplicativo atual, que é De processamento de recepção de dados MIDI-expelindo em tempo real. Também ele precisa ampliar tão bem quanto possível.
Eu estou comparando o desempenho array
em um número alto de leituras para pequenas listas para ArrayList
e também para ter apenas as variáveis ??na mão. Eu estou achando que um array bate ArrayList
por um fator de 2,5 e ainda bate apenas ter as referências de objeto.
O que eu gostaria de saber é:
- É a minha referência ok? eu mudei a ordem dos testes e número de corridas sem alteração . Eu também usado milissegundos em vez de nanossegundos sem sucesso.
- eu deveria estar especificando quaisquer opções Java para minimizar esta diferença?
- Se esta diferença é real, neste caso, não deve prefiro
Test[]
paraArrayList<Test>
nesta situação e colocar no código necessário para convertê-los? Obviamente, eu estou lendo muito mais do que a escrita .
JVM é Java 1.6.0_17 no OSX e é definitivamente em execução no modo Hotspot.
public class ArraysVsLists {
static int RUNS = 100000;
public static void main(String[] args) {
long t1;
long t2;
Test test1 = new Test();
test1.thing = (int)Math.round(100*Math.random());
Test test2 = new Test();
test2.thing = (int)Math.round(100*Math.random());
t1 = System.nanoTime();
for (int i=0; i<RUNS; i++) {
test1.changeThing(i);
test2.changeThing(i);
}
t2 = System.nanoTime();
System.out.println((t2-t1) + " How long NO collection");
ArrayList<Test> list = new ArrayList<Test>(1);
list.add(test1);
list.add(test2);
// tried this too: helps a tiny tiny bit
list.trimToSize();
t1= System.nanoTime();
for (int i=0; i<RUNS; i++) {
for (Test eachTest : list) {
eachTest.changeThing(i);
}
}
t2 = System.nanoTime();
System.out.println((t2-t1) + " How long collection");
Test[] array = new Test[2];
list.toArray(array);
t1= System.nanoTime();
for (int i=0; i<RUNS; i++) {
for (Test test : array) {
test.changeThing(i);
}
}
t2 = System.nanoTime();
System.out.println((t2-t1) + " How long array ");
}
}
class Test {
int thing;
int thing2;
public void changeThing(int addThis) {
thing2 = addThis + thing;
}
}
Solução
microbenchmarks são muito, muito difícil de acertar em uma plataforma como o Java. Você definitivamente tem que extrair o código a ser aferido em métodos separados, executá-los alguns milhares de vezes mais aquecimento e depois medir. Eu já fiz isso (código abaixo) eo resultado é que o acesso direto através de referências é seguida, três vezes mais rápido que através de uma matriz, mas a coleção ainda mais lento é por um fator de 2.
Estes números são baseados no opções JVM -server -XX:+DoEscapeAnalysis
. Sem -server
, usando a coleção é drasticamente mais lento (mas, estranhamente, acesso directo e variedade são um pouco mais rápido, indicando que há algo estranho acontecendo). -XX:+DoEscapeAnalysis
produz outra aceleração de 30% para a recolha, mas é muito questionabled se ele vai funcionar tão bem para o seu código de produção real.
No geral a minha conclusão seria: esquecer microbenchmarks, eles podem muito facilmente ser enganosa. Meça o mais próximo possível código de produção possível, sem ter que reescrever o aplicativo inteiro.
import java.util.ArrayList;
public class ArrayTest {
static int RUNS_INNER = 1000;
static int RUNS_WARMUP = 10000;
static int RUNS_OUTER = 100000;
public static void main(String[] args) {
long t1;
long t2;
Test test1 = new Test();
test1.thing = (int)Math.round(100*Math.random());
Test test2 = new Test();
test2.thing = (int)Math.round(100*Math.random());
for(int i=0; i<RUNS_WARMUP; i++)
{
testRefs(test1, test2);
}
t1 = System.nanoTime();
for(int i=0; i<RUNS_OUTER; i++)
{
testRefs(test1, test2);
}
t2 = System.nanoTime();
System.out.println((t2-t1)/1000000.0 + " How long NO collection");
ArrayList<Test> list = new ArrayList<Test>(1);
list.add(test1);
list.add(test2);
// tried this too: helps a tiny tiny bit
list.trimToSize();
for(int i=0; i<RUNS_WARMUP; i++)
{
testColl(list);
}
t1= System.nanoTime();
for(int i=0; i<RUNS_OUTER; i++)
{
testColl(list);
}
t2 = System.nanoTime();
System.out.println((t2-t1)/1000000.0 + " How long collection");
Test[] array = new Test[2];
list.toArray(array);
for(int i=0; i<RUNS_WARMUP; i++)
{
testArr(array);
}
t1= System.nanoTime();
for(int i=0; i<RUNS_OUTER; i++)
{
testArr(array);
}
t2 = System.nanoTime();
System.out.println((t2-t1)/1000000.0 + " How long array ");
}
private static void testArr(Test[] array)
{
for (int i=0; i<RUNS_INNER; i++) {
for (Test test : array) {
test.changeThing(i);
}
}
}
private static void testColl(ArrayList<Test> list)
{
for (int i=0; i<RUNS_INNER; i++) {
for (Test eachTest : list) {
eachTest.changeThing(i);
}
}
}
private static void testRefs(Test test1, Test test2)
{
for (int i=0; i<RUNS_INNER; i++) {
test1.changeThing(i);
test2.changeThing(i);
}
}
}
class Test {
int thing;
int thing2;
public void changeThing(int addThis) {
thing2 = addThis + thing;
}
}
Outras dicas
A sua referência só é válido se o seu caso de uso real corresponde ao código de referência, isto é, muito poucas operações em cada elemento, de modo que o tempo de execução é largamente determinado pelo tempo de acesso, em vez das próprias operações. Se for esse o caso, então sim, você deve estar usando matrizes se o desempenho é crítico. Se, contudo, o seu caso de uso real, envolve computação muito mais real por elemento, então o tempo de acesso por elemento se tornará muito menos significativa.
Provavelmente não é válido. Se eu entender a maneira que compiladores JIT trabalho, compilando um método não irá afectar uma chamada para esse método que já está em execução. Como o método main
só é chamado uma vez, ele vai acabar sendo interpretado, e uma vez que a maior parte do trabalho é feito no corpo desse método, os números que você começa não será particularmente indicativo de execução normal.
efeitos de compilação JIT pode ir alguma maneira a explicar por que o caso não coleções foi mais lento que o caso matrizes. Esse resultado é contra-intuitivo, e coloca uma dúvida sobre o outro resultado do benchmark que você relatou.