Analyse comparative petits tableaux vs listes en Java: mon code d'étalonnage ne va pas?
-
19-09-2019 - |
Question
Disclaimer: Je l'ai regardé à travers cette question et cette question mais ils ont tous deux se sont déraillé par les petits détails et générale optimisation est-inutile préoccupations. J'ai vraiment besoin de toute la performance I peut obtenir dans mon application actuelle, qui est réception-traitement de données MIDI crachant en temps réel. En outre, il a besoin d'intensifier aussi bien que possible.
Je compare la performance de array
sur un grand nombre de lectures pour les petites listes ArrayList
et aussi avoir juste les variables en main. Je trouve qu'un tableau bat ArrayList
par un facteur de 2,5 et même bat juste avoir les références d'objet.
Ce que je voudrais savoir est:
- Mon indice de référence d'accord? J'ai changé l'ordre des essais et le nombre de pistes sans changement . J'ai aussi utilisé millisecondes au lieu de nanosecondes en vain.
- Dois-je spécifier d'options Java pour minimiser cette différence?
- Si cette différence est réelle, dans ce cas, ne devrais-je préfère
Test[]
àArrayList<Test>
dans cette situation et mettre dans le code nécessaire pour les convertir? Il est évident que je lis beaucoup plus que l'écriture .
JVM est Java 1.6.0_17 sur OSX et il est certainement en cours d'exécution en mode 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;
}
}
La solution
microbenchmarks sont très, très difficile d'obtenir à droite sur une plate-forme comme Java. Vous avez certainement d'extraire le code à benchmarkée en méthodes distinctes, les exécuter quelques milliers de fois comme warm-up, puis mesurer. Je l'ai fait (le code ci-dessous) et le résultat est que l'accès direct par des références est alors trois fois plus vite que par un tableau, mais la collection est encore plus lente par un facteur 2.
Ces chiffres sont basés sur les options JVM -server -XX:+DoEscapeAnalysis
. Sans -server
, en utilisant la collection est de façon drastique plus lent (mais étrangement, un accès direct et ce tableau sont un peu plus rapide, ce qui indique qu'il ya quelque chose de bizarre se passe). -XX:+DoEscapeAnalysis
donne un autre 30% pour la collecte speedup, mais il est très bien questionabled si cela fonctionnera aussi bien pour votre code de production réelle.
Dans l'ensemble, ma conclusion serait: oublier microbenchmarks, ils peuvent trop facilement induire en erreur. Mesurez aussi près le code de production que vous pouvez sans avoir à réécrire votre application entière.
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;
}
}
Autres conseils
Votre référence est valable uniquement si votre cas réel d'utilisation correspond au code de référence, à savoir très peu d'opérations sur chaque élément, de sorte que le temps d'exécution est en grande partie déterminée par le temps d'accès plutôt que les opérations elles-mêmes. Si tel est le cas, alors oui, vous devriez utiliser des tableaux si les performances sont critiques. Si toutefois votre vrai cas d'utilisation implique beaucoup plus de calcul réel par élément, le temps d'accès par élément deviendra beaucoup moins important.
Il est probablement pas valide. Si je comprends la façon dont les compilateurs JIT fonctionnent, la compilation d'une méthode n'affectera pas un appel à cette méthode qui est déjà en cours d'exécution. Puisque la méthode main
est appelée une seule fois, il finira par être interprété, et que la plupart du travail se fait dans le corps de cette méthode, les chiffres que vous obtenez ne sera pas particulièrement révélateur de l'exécution normale.
JIT effets de compilation peuvent aller d'une certaine façon d'expliquer pourquoi l'aucun cas de recouvrement a été plus lente que le cas des tableaux. Ce résultat est contre-intuitif, et il met un doute sur l'autre résultat de référence que vous avez déclaré.