49 votes

Pourquoi est-Collections.sort() beaucoup plus lent que les Tableaux.sort()?

J'ai essayé de faire un test, concernant l' Collection.sort() et Arrays.sort(). Dans le test, j'ai créé un tableau d' ints de longueur 1e5 100 fois, qui contient des nombres aléatoires de 1 à 1e5. J'ai également créé une liste de type Integer, qui contient les mêmes valeurs à la même position que celle de la matrice. Puis j'ai trié le tableau à l'aide d' Arrays.sort() et la liste à l'aide de Collections.sort().


Mise à JOUR: Comme @Holger souligné, mon code a un bug. Le code corrigé est maintenant:

import java.util.* ;


class TestClass {
    public static void main(String args[] ) throws Exception {
        double ratSum = 0 ;
        for(int j=0;j<100;j++)
        {
        int[] A = new int[(int)1e5] ;
        List<Integer> L = new ArrayList<Integer>() ;
        for(int i=0;i<A.length;i++)
        {
            int no = (int)(Math.random()*(int)1e5) ;
            A[i] = no ;
            L.add(A[i]) ;
        }

        long startTime = System.nanoTime() ;
        Arrays.sort(A) ;
        long endTime = System.nanoTime() ;
        Collections.sort(L) ;
        long endTime2 = System.nanoTime() ;
        long t1 = (endTime-startTime), t2 = (endTime2-endTime) ;
        ratSum+=(double)t2/t1 ;
        System.out.println("Arrays.sort took :"+t1+" Collections.sort took :"+t2+" ratio :"+((double)t2/t1)) ;
    }
    System.out.println("Average ratio :"+(ratSum/100)) ;
    }
}

Et la sortie est:

Arrays.sort took :24106021 Collections.sort took :92353602 ratio :3.8311425182944956
Arrays.sort took :8672831 Collections.sort took :50936497 ratio :5.873110752417521
Arrays.sort took :8561227 Collections.sort took :25611480 ratio :2.991566512603859
Arrays.sort took :7123928 Collections.sort took :17368785 ratio :2.4380910362934607
Arrays.sort took :6280488 Collections.sort took :16929218 ratio :2.6955258890710403
Arrays.sort took :6248227 Collections.sort took :16844915 ratio :2.695951187432851
Arrays.sort took :6220942 Collections.sort took :16979669 ratio :2.7294369566538315
Arrays.sort took :6213841 Collections.sort took :17439817 ratio :2.8066081832476883
Arrays.sort took :6286385 Collections.sort took :19963612 ratio :3.175690321225951
Arrays.sort took :6209668 Collections.sort took :17008307 ratio :2.7390042430609816
Arrays.sort took :6286623 Collections.sort took :17007163 ratio :2.705293923303497
Arrays.sort took :6256505 Collections.sort took :16911950 ratio :2.703098614961548
Arrays.sort took :6225031 Collections.sort took :16914494 ratio :2.7171742598550916
Arrays.sort took :6233918 Collections.sort took :17005995 ratio :2.72797861633727
Arrays.sort took :6210554 Collections.sort took :17606028 ratio :2.834856278522013
Arrays.sort took :6239384 Collections.sort took :20342378 ratio :3.260318326296314
Arrays.sort took :6207695 Collections.sort took :16519089 ratio :2.6610664666997974
Arrays.sort took :6227147 Collections.sort took :16605884 ratio :2.666692146499834
Arrays.sort took :6225187 Collections.sort took :16687597 ratio :2.680657946500242
Arrays.sort took :6152338 Collections.sort took :16475373 ratio :2.6779043999208105
Arrays.sort took :6184746 Collections.sort took :16511024 ratio :2.6696365541931715
Arrays.sort took :6130221 Collections.sort took :16578032 ratio :2.7043122915144493
Arrays.sort took :6271927 Collections.sort took :16507152 ratio :2.631910734930429
Arrays.sort took :6232482 Collections.sort took :16562166 ratio :2.657394919070765
Arrays.sort took :6218992 Collections.sort took :16552468 ratio :2.661599821964717
Arrays.sort took :6230427 Collections.sort took :21954967 ratio :3.52383022865046
Arrays.sort took :8204666 Collections.sort took :16607560 ratio :2.024160398485447
Arrays.sort took :6272619 Collections.sort took :22061291 ratio :3.5170781136236715
Arrays.sort took :8618253 Collections.sort took :19979549 ratio :2.3182829513127543
Arrays.sort took :6198538 Collections.sort took :17002645 ratio :2.743008915973412
Arrays.sort took :6265018 Collections.sort took :17079646 ratio :2.7261926462142645
Arrays.sort took :6302335 Collections.sort took :17040082 ratio :2.7037728080148073
Arrays.sort took :6293948 Collections.sort took :17133482 ratio :2.722215372608735
Arrays.sort took :6272364 Collections.sort took :17099717 ratio :2.7261997231028046
Arrays.sort took :6219540 Collections.sort took :17026849 ratio :2.737637992520347
Arrays.sort took :6231000 Collections.sort took :17149439 ratio :2.7522771625742255
Arrays.sort took :6309215 Collections.sort took :17118779 ratio :2.713297771592821
Arrays.sort took :6200511 Collections.sort took :17123517 ratio :2.7616299688848227
Arrays.sort took :6263169 Collections.sort took :16995685 ratio :2.7135919532109063
Arrays.sort took :6212243 Collections.sort took :17101848 ratio :2.7529264389689843
Arrays.sort took :6247580 Collections.sort took :17089850 ratio :2.735435160494143
Arrays.sort took :6283626 Collections.sort took :17088109 ratio :2.7194662763188004
Arrays.sort took :6312678 Collections.sort took :17055856 ratio :2.7018415955954036
Arrays.sort took :6222695 Collections.sort took :17071263 ratio :2.7433873908330715
Arrays.sort took :6300990 Collections.sort took :17016171 ratio :2.7005551508572463
Arrays.sort took :6262923 Collections.sort took :17084477 ratio :2.727875945465081
Arrays.sort took :6256482 Collections.sort took :17062232 ratio :2.7271287602202006
Arrays.sort took :6259643 Collections.sort took :17036036 ratio :2.721566709155778
Arrays.sort took :6248649 Collections.sort took :16944960 ratio :2.711779778316881
Arrays.sort took :6264515 Collections.sort took :16986876 ratio :2.7116027338109974
Arrays.sort took :6241864 Collections.sort took :17367903 ratio :2.782486609769133
Arrays.sort took :6297429 Collections.sort took :17080086 ratio :2.7122316107097038
Arrays.sort took :6184084 Collections.sort took :17584862 ratio :2.843567778186713
Arrays.sort took :6315776 Collections.sort took :22279278 ratio :3.5275598754610678
Arrays.sort took :6253047 Collections.sort took :17091694 ratio :2.7333384828228544
Arrays.sort took :6291188 Collections.sort took :17147694 ratio :2.725668665441249
Arrays.sort took :6327348 Collections.sort took :17034007 ratio :2.6921242517402235
Arrays.sort took :6284904 Collections.sort took :17049315 ratio :2.712740719667317
Arrays.sort took :6190436 Collections.sort took :17143853 ratio :2.7694096183209065
Arrays.sort took :6301712 Collections.sort took :17070237 ratio :2.7088253160411013
Arrays.sort took :6208193 Collections.sort took :17060372 ratio :2.74804149935416
Arrays.sort took :6247700 Collections.sort took :16961962 ratio :2.7149130079869392
Arrays.sort took :6344996 Collections.sort took :17084627 ratio :2.6926143058246215
Arrays.sort took :6214232 Collections.sort took :17150324 ratio :2.759846108095095
Arrays.sort took :6224359 Collections.sort took :17081254 ratio :2.744259127727048
Arrays.sort took :6256722 Collections.sort took :17005451 ratio :2.7179489515436357
Arrays.sort took :6286439 Collections.sort took :17061112 ratio :2.713954911516679
Arrays.sort took :6250634 Collections.sort took :17091313 ratio :2.7343327092899696
Arrays.sort took :6252900 Collections.sort took :17041659 ratio :2.7254008540037424
Arrays.sort took :6222192 Collections.sort took :17125062 ratio :2.75225547524088
Arrays.sort took :6227037 Collections.sort took :17013314 ratio :2.7321684454420296
Arrays.sort took :6223609 Collections.sort took :17086112 ratio :2.745370411283871
Arrays.sort took :6280777 Collections.sort took :17091821 ratio :2.7212908530266238
Arrays.sort took :6254551 Collections.sort took :17148242 ratio :2.741722307484582
Arrays.sort took :6250927 Collections.sort took :17053331 ratio :2.7281283240069834
Arrays.sort took :6270616 Collections.sort took :17067948 ratio :2.721893351466586
Arrays.sort took :6223093 Collections.sort took :17034584 ratio :2.737317922132933
Arrays.sort took :6286002 Collections.sort took :17128280 ratio :2.7248289135129133
Arrays.sort took :6239485 Collections.sort took :17032062 ratio :2.7297224049741287
Arrays.sort took :6191290 Collections.sort took :17017219 ratio :2.748574045150526
Arrays.sort took :6134110 Collections.sort took :17069485 ratio :2.782715830006309
Arrays.sort took :6207363 Collections.sort took :17052862 ratio :2.747199092432648
Arrays.sort took :6238702 Collections.sort took :17056945 ratio :2.734053493819708
Arrays.sort took :6185356 Collections.sort took :17006088 ratio :2.749411351585907
Arrays.sort took :6309226 Collections.sort took :17056503 ratio :2.703422416632405
Arrays.sort took :6256706 Collections.sort took :17082903 ratio :2.7303349398229675
Arrays.sort took :6194988 Collections.sort took :17069426 ratio :2.7553606237816766
Arrays.sort took :6184266 Collections.sort took :17054641 ratio :2.757746998592881
Arrays.sort took :6271022 Collections.sort took :17086036 ratio :2.724601508334686
Arrays.sort took :6246482 Collections.sort took :17077804 ratio :2.733987546910405
Arrays.sort took :6194985 Collections.sort took :17119911 ratio :2.763511291794895
Arrays.sort took :6319199 Collections.sort took :17444587 ratio :2.760569337980969
Arrays.sort took :6262827 Collections.sort took :17065589 ratio :2.7249018693954024
Arrays.sort took :6301245 Collections.sort took :17195611 ratio :2.728922776371971
Arrays.sort took :6214333 Collections.sort took :17024645 ratio :2.739577199998777
Arrays.sort took :6213116 Collections.sort took :17382033 ratio :2.7976353572024086
Arrays.sort took :6286394 Collections.sort took :17124874 ratio :2.7241171965995132
Arrays.sort took :6166308 Collections.sort took :16998293 ratio :2.756640278104824
Arrays.sort took :6247395 Collections.sort took :16957056 ratio :2.7142602636779007
Arrays.sort took :6245054 Collections.sort took :16994147 ratio :2.72121698227109
Average ratio :2.792654880602193

En outre, j'ai couru le code en local, 1000 fois (au lieu de 100) et le ratio moyen était: :3.0616 Ainsi, le ratio est encore importante et donc digne de discussion.

Question: Pourquoi est - Collections.sort() prendre environ 3 fois le temps pris par Arrays.sort() pour trier les mêmes valeurs? Est-ce parce que nous ne sommes pas en comparant les primitives? Pourquoi cela prendrait plus de temps?

76voto

orionll Points 1045

Donc, il existe deux méthodes différentes avec totalement différents algorithmes ici:

Arrays.sort(int[]) utilise un double pivot algorithme quicksort.

Collections.sort(List<T>) des appels list.sort(null) qui appelle à son tour Arrays.sort(T[]). Il utilise un Timsort algorithme.

Donc, nous allons comparer Arrays.sort(int[]) et Arrays.sort(T[]).

  1. T[] est un coffret de tableau donc il y a un niveau supplémentaire d'indirection: pour chaque appel, vous devez déballer Entier. Ce qui a certainement conduit à une surcharge. D'autre part, int[] est une primitive de la matrice de sorte que tous les éléments sont disponibles "immédiatement".
  2. TimSort est une variante d'un classique mergesort algorithme. Il est plus rapide que mergesort mais c'est encore plus lent que quicksort parce que
    • quicksort a moins de mouvements de données sur des données aléatoires
    • quicksort exige O(log(n)) d'espace supplémentaire tout en TimSort exige O(n) pour assurer la stabilité qui a également conduit à une surcharge.

12voto

Stephen C Points 255558

Il y a deux problèmes ici:

Question #1:

Sous les couvertures, Collections.sort fonctionne en copiant la collection d'un tableau de tri le tableau, puis de copier le tableau à la collection.

Arrays.sort seulement trie le tableau en place.

Maintenant, pour un assez grand tableau ou d'une collection, le coût de tri (O(NlogN)) va dominer le coût de la copie (O(N)). Pour un petit tableau ou d'une collection, la copie devient significative.

(Ce comportement peut dépendre du type de collection. Pour un ArrayList, l' Collections.sort mise en œuvre peut être capable de trier le support de matrice, sans la copie de données. J'aurais besoin de vérifier le code source. MISE en place du tri confirmé ArrayList pour Java 8 et les versions ultérieures.)

Question n ° 2:

Vous comparez le tri d'un int[] avec le tri List<Integer>.

C'est des pommes et des oranges. Parce que:

  1. La comparaison de deux int valeurs à l'aide des opérateurs relationnels est plus rapide que la comparaison de deux Integer valeurs à l'aide d' compareTo(Integer).
  2. Arrays.sort(int[]) utilise un autre (plus rapide) algorithme à celle utilisée par Arrays.sort(Object[])

Si vous voulez une plus juste comparaison, comparer Collections.sort sur ArrayList<Integer> avec Arrays.sort(Object[]) sur Integer[].

2voto

Brixomatic Points 114

Il y a une chose qui n'était pas mentionné sur le chemin, qui est le "pointeur de chasse", qui est liée à la "unboxing" de la partie. Pour un tableau de cette petite taille, si vous utilisez timsort ou quicksort ne devrait pas faire une différence significative (pour des primitifs tableaux avec des courants de vitesse du PROCESSEUR, ce qui est très probablement pas ce qui tue votre vitesse).

Alors que la boxe ne se fait pas en dehors de l'initialisation dans votre exemple, la grande différence qui se passe là où les données sont lues.

Comme ints sont primitives, int[] est juste une petite pièce de la mémoire qui contient les données lui-même, un Integer[] est une pièce contiguë de mémoire qui contient des références (c'est à dire des pointeurs) à la personne, des objets de données et l'Entier des objets eux-mêmes peuvent être dispersées au-dessus de la mémoire.

Ainsi, pour une opération de tri sur un int[] le PROCESSEUR va chercher un morceau de la mémoire et ne peuvent fonctionner directement. Mais pour un Entier[] le CPU doit chasser le pointeur de chaque objet, et obtenir qu'à partir de la mémoire, avant de pouvoir les comparer et ensuite fonctionner sur ce morceau de mémoire qui est la matrice et de réorganiser les que. Ceci est appelé le "pointeur de chasse".

Ce n'est pas tellement que l'Entier[] nécessite plus d'opérations pour chaque élément de données, comme la lecture d'une valeur, en ajoutant une longueur d'en-tête à l'adresse de base et l'obtention d'une valeur à partir de là (le CPU ne pipeline ces instructions très bien et qui cache beaucoup de son impact), c'est la latence mémoire qui vous tue. L'extraction de chaque individu Entier de l'objet à partir d'un emplacement de mémoire vive fait que presque la totalité de la différence.

Habituellement ce n'est pas une grosse affaire, comme d'habitude vous initialisez une assez petite quantité de Integer[] dans une boucle serrée, et il n'y a pas beaucoup de choses en arrière-plan, de sorte que le nombre Entier d'objets sont probablement à la proximité dans de la mémoire peut être récupérée dans le cache et accessibles à partir de là assez rapidement, mais pour les gros tableaux et listes sont créées et modifiées sur une longue application, cela peut faire une différence significative et peut venir avec inattendus des pics de latence. Vous voulez éviter cela si vous avez besoin d'fiable, à faible temps de latence. Pour un grand nombre d'applications cependant, si un tri prend que quelques millisecondes de plus, pas un avis.

[MODIFIER]

Comme vous l'avez demandé dans les commentaires, voici le code à montrer qu'il n'est pas question de timsort vs quicksort:

import java.util.Arrays;
import java.util.Random;

public class Pointerchasing1 {

    public static void main(String[] args) {

        //use the exact same algorithm implementation (insertionSort), to show that slowness is not caused by timsort vs quicksort.
        //expect that the object-version is slower.

        final int[] direct = new int[1024]; 
        final Integer[] refs = new Integer[direct.length];

        final Random rnd = new Random(0);
        for (int t = 0; t < 1000; ++t) {
            Arrays.setAll(direct, index -> rnd.nextInt());
            Arrays.setAll(refs, index -> direct[index]); // boxing happens here

            //measure direct:
            long t1 = System.nanoTime();
            insertionSortPrimitive(direct);
            long e1 = System.nanoTime()-t1;
            //measure refs:         
            long t2 = System.nanoTime();
            insertionSortObjects(refs);
            long e2 = System.nanoTime()-t2;

            // use results, so compiler can't eliminate the loops
            System.out.println(Arrays.toString(direct));
            System.out.println(Arrays.toString(refs));
            System.out.println("-");            
            System.out.println(e1);
            System.out.println(e2);
            System.out.println("--");           
        }
    }

    private static void insertionSortPrimitive(final int[] arr) {
        int i, key, j;
        for (i = 1; i < arr.length; i++) {
            key = arr[i];
            j = i - 1;
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j];
                j = j - 1;
            }
            arr[j + 1] = key;
        }
    }

    private static void insertionSortObjects(final Integer[] arr) {
        int i, key, j;
        for (i = 1; i < arr.length; i++) {
            key = arr[i];
            j = i - 1;
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j];
                j = j - 1;
            }
            arr[j + 1] = key;
        }
    }

}

Ce "test" feuilles unboxing comme coupable possible.

[EDIT2]

Maintenant, ce test est de montrer que "unboxing" n'est pas le problème. Unboxing est juste l'ajout de quelques octets d'objets en-tête à l'adresse (out-of-order-l'exécution et le pipelining faire qui a coûté près de disparaître) et l'obtention de la valeur à partir de cet emplacement. Dans ce test, j'ai utiliser deux primitives de tableaux, l'un pour une référence et un pour une valeur. De sorte que chaque accès est indirect. C'est très bien comme unboxing, juste sans l'ajout de quelques octets supplémentaires pour l'objet de l'en-tête. La principale différence est que le "indirecte" version n'a pas besoin de chasser le pointeur pour chaque valeur sur le tas, il peut charger deux tableaux et index à partir de la refs-tableau avec les valeurs de tableau.

Si le pointeur de chasser qui fait la différence, plutôt que de déballer, puis indirects version devrait être plus rapide que les objets de la version qui ne unboxing.

import java.util.Arrays;
import java.util.Random;

public class Pointerchasing2 {

    public static void main(String[] args) {

        // use indirect access (like unboxing, but just chasing a single array pointer) vs. Integer objects (chasing every object's pointer).
        // expect that the object-version is still slower.

        final int[] values = new int[1024];
        final int[] refs = new int[1024];
        final Integer[] objects = new Integer[values.length];

        final Random rnd = new Random(0);
        for (int t = 0; t < 1000; ++t) {
            Arrays.setAll(values, index -> rnd.nextInt());
            Arrays.setAll(refs, index -> index);
            Arrays.setAll(objects, index -> values[index]); // boxing happens here

            // measure indirect:
            long t1 = System.nanoTime();
            insertionSortPrimitiveIndirect(refs, values);
            long e1 = System.nanoTime() - t1;
            // measure objects:
            long t2 = System.nanoTime();
            insertionSortObjects(objects);
            long e2 = System.nanoTime() - t2;

            // use results, so compiler can't eliminate the loops
            System.out.println(Arrays.toString(indirectResult(refs, values)));
            System.out.println(Arrays.toString(objects));
            System.out.println("-");
            System.out.println(e1);
            System.out.println(e2);
            System.out.println("--");
        }
    }

    private static void insertionSortPrimitiveIndirect(final int[] refs, int[] values) {
        int i, keyIndex, j;
        for (i = 1; i < refs.length; i++) {
            keyIndex = refs[i];
            j = i - 1;
            while (j >= 0 && values[refs[j]] > values[keyIndex]) {
                refs[j + 1] = refs[j];
                j = j - 1;
            }
            refs[j + 1] = keyIndex;
        }
    }

    private static void insertionSortObjects(final Integer[] arr) {
        int i, key, j;
        for (i = 1; i < arr.length; i++) {
            key = arr[i];
            j = i - 1;
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j];
                j = j - 1;
            }
            arr[j + 1] = key;
        }
    }

    private static int[] indirectResult(final int[] refs, int[] values) {
        final int[] result = new int[1024];
        Arrays.setAll(result, index -> values[refs[index]]);
        return result;
    }

}

Résultat: Dans ces deux essais, le "primitif" et "indirects" sont des versions plus rapide que l'accès à des objets sur le tas. Il est attendu que l'unboxing est de ne pas tuer la vitesse, mais la latence de la mémoire au travers de l'aiguille à la poursuite.

Voir aussi cette vidéo sur le projet Valhalla: ("Types de valeur et générique de spécialisation au sein de la JVM promesse de nous donner une meilleure JIT code, la localité des données et de supprimer la tyrannie du pointeur de la poursuite.") https://vimeo.com/289667280

1voto

John Points 144

Collection.sort() utilisée Fusion algorithme de tri et de Tableaux.sort() utilise la fonction de tri rapide. Tri rapide a des inconvénients majeurs quand il s'agit de fusionner les trier, il n'est pas stable, tandis qu'il s'agit de non primitif. Cela dépend exigence, nous allons utiliser des Tableaux.sort() ou de la Collection.sort() de la météo besoin de comparer des objets ou des primitives.

1voto

si vous voyez des Collections.sort() oracle doc ici il lit

Cette mise en œuvre décharges de la liste dans un tableau, trie le tableau, et parcourt la liste réinitialisation de chaque élément à partir de la position correspondante dans le tableau

ce qui signifie qu'il est en train de faire de tableau de tri et de supplémentaires de l'itération, ce qui implique des Collections.sort() est plus lent que les tableaux.tri

  1. les vidages de la liste dans un tableau
  2. trie le tableau ~ tableaux.tri
  3. itère sur la liste de la réinitialisation de chaque élément à partir de la position correspondante dans le tableau

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X