web-dev-qa-db-ja.com

Collections.sort()がArrays.sort()よりもずっと遅いのはなぜですか?

Collection.sort()Arrays.sort()に関するテストを試みました。テストでは、長さ1e5intsの配列を100回作成しました。これには、1から1e5までの乱数が含まれていました。また、Integer型のリストを作成しました。このリストには、配列と同じ位置に同じ値が含まれていました。次に、Arrays.sort()を使用して配列をソートし、Collections.sort()を使用してリストをソートしました。


PDATE: @Holgerが指摘したように、私のコードにはバグがありました。修正されたコードは次のとおりです。

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)) ;
    }
}

出力は次のとおりです。

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

さらに、コードをローカルで(100回ではなく)1000回実行し、平均比率は:3.0616でした。したがって、比率は依然として重要であり、したがって議論に値します。

質問:Collections.sort()Arrays.sort()が同じ値をソートするのにかかる時間の約3倍を要するのはなぜですか?今はプリミティブを比較していないからでしょうか?なぜもっと時間がかかるのでしょうか?

47
Mooncrater

したがって、ここではまったく異なるアルゴリズムを持つ2つの異なる方法があります。

Arrays.sort(int[])は、デュアルピボットクイックソートアルゴリズムを使用します。

Collections.sort(List<T>)list.sort(null)を呼び出し、次にArrays.sort(T[])が呼び出されます。これは、Timsortアルゴリズムを使用します。

それでは、Arrays.sort(int[])Arrays.sort(T[])を比較しましょう。

  1. T[]はボックス化された配列であるため、間接的なレベルが1つ余分にあります。呼び出しごとに、整数のラップを解除する必要があります。これは確かにオーバーヘッドにつながります。一方、int[]はプリミティブ配列であるため、すべての要素が「即座に」利用可能です。
  2. TimSortは、古典的なマージソートアルゴリズムのバリエーションです。 mergesortよりも高速ですが、quicksortよりも依然として低速です。
    • クイックソートでは、ランダムデータのデータ移動が少なくなります
    • クイックソートにはO(log(n))余分なスペースが必要ですが、TimSortには安定性を提供するためにO(n)が必要であり、オーバーヘッドも発生します。
75
ZhekaKozlov

ここには2つの問題があります。

問題#1:

カバーの下で、Collections.sortは、コレクションを配列にコピーし、配列をソートしてから、配列をコレクションにコピーして戻すことで機能します。

Arrays.sortは、配列を所定の位置に並べ替えるだけです。

配列/コレクションが十分に大きい場合、ソートのコスト(O(NlogN))がコピーのコスト(O(N))を支配します。小さな配列/コレクションの場合、コピーが重要になります。

(この動作はコレクションのタイプに依存する場合があります。ArrayListの場合、Collections.sort実装はデータをコピーせずにバッキング配列をソートできる場合があります。ソースコードを確認する必要があります。UPDATE-Java 8以降のArrayListのインプレースソートが確認されました。

問題#2:

int[]の並べ替えとList<Integer>の並べ替えを比較しています。

これはリンゴとオレンジです。なぜなら:

  1. 関係演算子を使用して2つのint値を比較することは、compareTo(Integer)を使用して2つのInteger値を比較するよりも高速です。
  2. Arrays.sort(int[])は、Arrays.sort(Object[])が使用するアルゴリズムとは異なる(より高速な)アルゴリズムを使用します

より公平な比較が必要な場合は、Collections.sortArrayList<Integer>Integer[]Arrays.sort(Object[])を比較してください。

12
Stephen C

collections.sort()が表示されている場合 ここにOracleドキュメント

この実装は、指定されたリストを配列にダンプし、配列をソートし、配列内の対応する位置から各要素をリセットしてリストを反復処理します

これは、配列のソートと追加の反復を実行していることを意味し、これはCollections.sort()がarray.sortよりも遅いことを意味します

  1. 指定されたリストを配列にダンプします
  2. 配列をソートします〜array.sort
  3. リストを反復処理して、配列内の対応する位置から各要素をリセットします

途中で言及されなかったものが1つあります。それは「ポインター追跡」であり、「ボックス化解除」部分に関連しています。この小さなサイズの配列では、timsortとquicksortのどちらを使用しても大きな違いはありません(現在のCPU速度のプリミティブ配列の場合、これはおそらく速度を落とすものではありません)。

この例では、初期化以外ではボクシングは発生しませんが、データが読み取られる場所で大きな違いが発生します。

Intはプリミティブであるため、int []はデータ自体を含む連続したメモリの一部であり、Integer []は個々のデータオブジェクトへの参照(つまりポインタ)を含む連続したメモリの一部であり、Integerオブジェクト自体はメモリ中に散らばっている。

したがって、int []での並べ替え操作の場合、CPUはメモリのチャンクをフェッチし、それを直接操作できます。しかし、Integer []の場合、CPUは、個々のオブジェクトのポインターを追跡してメモリから取得する必要があります。その後、それを比較して、配列であるメモリチャンクを操作し、再配置します。これは「ポインター追跡」と呼ばれます。

Integer []が値の読み取り、ベースアドレスへのヘッダー長の追加、そこからの値の取得など、各データに対してより多くの操作を必要とするほどではありません(CPUはこれらの命令を非常にうまくパイプライン処理し、その影響の)、それはあなたを殺すメモリの待ち時間です。ランダムなメモリ位置から個々の整数オブジェクトをフェッチすると、ほとんどすべての違いが生じます。

通常、これは大したことではありません。通常、タイトループでかなり少量のInteger []を初期化し、バックグラウンドで多くの処理が行われないため、メモリ内でIntegerオブジェクトが近接している可能性がありますただし、ビジーなアプリケーション全体で作成および変更される巨大な配列およびリストの場合、これは大きな違いを生む可能性があり、予想外のレイテンシスパイクが発生する可能性があります。信頼性の高い低遅延が必要な場合は、それを避けたいでしょう。しかし、膨大な数のアプリケーションの場合、ソートにさらに数ミリ秒かかると、誰も気付かないでしょう。

[編集]

コメントでそれを求めたように、それはティムソートとクイックソートではないことを示すコードです:

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;
        }
    }

}

この「テスト」では、ボックス化解除が原因である可能性があります。

[編集2]

このテストは、「ボックス化解除」が問題ではないことを示すことです。ボックス化解除とは、アドレスに数バイトのオブジェクトヘッダーを追加するだけで(アウトオブオーダー実行とパイプライン処理によりコストがほとんどなくなるため)、その場所から値を取得します。このテストでは、参照用と値用の2つのプリミティブ配列を使用します。したがって、各アクセスは間接的です。これは、オブジェクトヘッダーに数バイトを追加することなく、ボックス化解除に非常によく似ています。主な違いは、「間接」バージョンでは、ヒープ上の各値のポインタを追跡する必要がなく、配列とインデックスの両方をrefs-arrayからvalues-arrayにロードできることです。

ポインター追跡がボックス化解除ではなく違いをもたらす場合、間接バージョンは、ボックス化解除を行うオブジェクトバージョンよりも高速である必要があります。

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;
    }

}

結果:これらのテストの両方で、「プリミティブ」バージョンと「間接」バージョンは、ヒープ上のオブジェクトにアクセスするよりも高速です。ボックス化解除が速度を犠牲にするのではなく、ポインタ追跡によるメモリ遅延が予想されます。

プロジェクトValhallaのこのビデオも参照してください:(「JVM内の値型と汎用特殊化は、より良いJITコード、データローカリティを提供し、ポインター追跡の専制政治を取り除くことを約束します。」) https://vimeo.com/ 28966728

1
Brixomatic

Collection.sort()はMergeソートアルゴリズムを使用し、Arrays.sort()はクイックソートを使用します。クイックソートには、マージソートに関して大きな欠点があり、非プリミティブになると安定しません。要件に応じて、オブジェクトまたはプリミティブを比較するために必要なArrays.sort()またはCollection.sort()のいずれかの天気予報を使用します。

1
John