Analyse comparative petits tableaux vs listes en Java: mon code d'étalonnage ne va pas?

StackOverflow https://stackoverflow.com/questions/2227947

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:

  1. 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.
  2. Dois-je spécifier d'options Java pour minimiser cette différence?
  3. 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;
    }
}
Était-ce utile?

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é.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top