Java Arrays.sort基本类型和对象的性能

Java Arrays.sort基本类型和对象的性能,java,performance,Java,Performance,我在这里读了一些关于array.sort的文章,对基本类型使用“调优快速排序”,对对象使用合并排序。我做了一个小测试只是为了证明这一点,但我发现它恰恰相反 int a[] = new int[50000]; //Integer a[] = new Integer[50000]; for(int i=0; i<50000; i++) { //a[i] = new Integer(new Random().nextInt(5000));

我在这里读了一些关于array.sort的文章,对基本类型使用“调优快速排序”,对对象使用合并排序。我做了一个小测试只是为了证明这一点,但我发现它恰恰相反

            int a[] = new int[50000];
    //Integer a[] = new Integer[50000];

    for(int i=0; i<50000; i++) {
        //a[i] = new Integer(new Random().nextInt(5000)); 
        a[i] = new Random().nextInt(5000);
    }
    System.out.println(System.currentTimeMillis());

    Arrays.sort(a);

    System.out.println(System.currentTimeMillis());
inta[]=newint[50000];
//整数a[]=新整数[50000];

对于(inti=0;i这对我来说一点也不奇怪

首先,您有原语与需要向下追踪引用的间接方式,两个原语之间的比较会更快,等等

第二,基本数组可以很好地与CPU缓存配合使用。非基本数组不一定会这样做,因为不能保证引用对象在内存中是连续的(不太可能),此外,引用对象更大,这意味着在任何时候都可以装入缓存的对象更少

请参阅,在这两种情况下,数组中的值都适合缓存,但是
整数[]的问题
是指您仍然必须离开缓存,点击内存总线来追踪引用并在主内存中找到它们;这些引用可能指向堆上的所有位置。这将使性能差的CPU一直等待,因为现在缓存未命中的可能性更大

也就是说,您有这样一个基元数组

  _   _   _   _       _
 |5| |7| |2| |1| ... |4|
           _               _
     |--->|7|     ______> |1| 
 _   |   _       |   _
| | |_| | | ... |_| | |         _
 |     _ |_____      |________>|4|
 |___>|5|      |    _           
               |__>|2|
它们在内存中彼此相邻。当一个值从内存拉入缓存时,相邻的值也会被拉入缓存。快速排序和合并排序操作在阵列的相邻部分上,因此它们从CPU缓存的良好性能中获益匪浅(这是)

但是当你有一个像这样的
整数数组时

  _   _   _   _       _
 |5| |7| |2| |1| ... |4|
           _               _
     |--->|7|     ______> |1| 
 _   |   _       |   _
| | |_| | | ... |_| | |         _
 |     _ |_____      |________>|4|
 |___>|5|      |    _           
               |__>|2|
引用的存储位置在内存中是连续的,因此它们可以很好地与缓存配合使用。问题在于*间接性、引用的
整数
对象在内存中被碎片化的可能性以及缓存中容纳的对象较少的事实。这种额外的间接性、碎片和大小问题是至少不会很好地处理缓存

同样,对于像quicksort或mergesort这样在阵列的连续部分上播放的东西,这是巨大的,巨大的,巨大的,几乎可以肯定地说是性能差异的绝大多数原因

我运行错误了吗


是的,下次需要进行基准测试时,请使用
System.nanoTime
System.currentTimeMillis
分辨率很差,不适合进行基准测试。

您的int[]适合二级缓存。它大约是4b*50K,即200KB,二级缓存是256KB。这将比您的对象运行得快[]它将在您的三级缓存中,因为它的大小约为28 B*50K或1400 KB

二级缓存(约11个时钟周期)比三级缓存(约45-75个时钟周期)快4-6倍

我敢打赌,如果你不止一次地运行它,随着代码的升温,你会得到更好的结果

public static void test_int_array() {
    int a[] = new int[50000];
    //Integer a[] = new Integer[50000];

    Random random = new Random();
    for (int i = 0; i < 50000; i++) {
        //a[i] = new Integer(new Random().nextInt(5000));
        a[i] = random.nextInt(5000);
    }
    long start = System.nanoTime();
    Arrays.sort(a);
    long time = System.nanoTime() - start;
    System.out.printf("int[] sort took %.1f ms%n", time / 1e6);
}

public static void test_Integer_array() {
    Integer a[] = new Integer[50000];

    Random random = new Random();
    for (int i = 0; i < 50000; i++) {
        a[i] = random.nextInt(5000);
    }
    long start = System.nanoTime();
    Arrays.sort(a);
    long time = System.nanoTime() - start;
    System.out.printf("Integer[] sort took %.1f ms%n", time / 1e6);
}

public static void main(String... ignored) {
    for (int i = 0; i < 10; i++) {
        if (test_int_array()[0] > 0) throw new AssertionError();
        if (test_Integer_array()[0] > 0) throw new AssertionError();
    }
}
您可以看到预热代码可以产生多大的不同

我运行错误了吗

您的基准测试非常原始,它并没有真正建立任何东西。对于每种情况,排序时间是如何随着数组大小而增长的?原始排序和对象排序之间的差异有多少可以归因于比较原始排序和比较对象的不同成本?(这与排序算法的性能无关,但测试将其归因于排序算法。)


正如其他人所指出的,如果你正在计时的时间是几十毫秒,那么你应该使用
系统。nanoTime
系统。currentTimeMillis
的分辨率通常不超过10毫秒。然而,简单地切换计时技术并不能解决测试中更严重的问题。

多精确y这是否与预期不一致?除了间接的目标成本绩效(这会使您的基准无效)之外快速排序比合并排序快。你想证明什么?这在很大程度上取决于比较器的实现吗?我假设基本比较器只使用机器整数比较,但是使用integer对象会花费更多的时间进行比较…更有可能得出错误的结论。Accessin由于额外的解引用,g对象比访问原语的成本更高,因此您可能会认为它会更慢。此外,它更大,因此不太可能适合较小的CPU缓存。您应该使用
System.nanoTime()
并应将适当的微基准标记发布为,并且原语比较应比对象创建、访问等更快。我认为这不太正确。在32位JVM上,
int
Integer[]中的引用
具有相同的大小;它们都将消耗相同数量的缓存。问题在于间接性,引用仍然必须通过内存总线逐出。由于分配的对象不一定是连续的,这就是缓存差异的原因。@Jason即使在32位JVM上,每个整数都有一个引用是4个字节,头是12个字节,值是4个字节,总共是20个字节,每个字节都很重要,因为它们占用了缓存线上的空间。在64位JVM上,引用仍然是32位(除非您有32 GB或更多的堆)但是头是16字节,值是4字节,填充是4字节,因为对象是8字节对齐的。当在Eden空间中分配对象时,它们在内存中通常是连续的。您可以通过使用
Unsafe.getInt(数组,偏移量)看到这一点
我明白你的观点,我认为我们都是对的。间接寻址、碎片化和较大的对象都会影响缓存性能。还有一点:
快速排序
不进行堆分配,而merge/timsort则会:因此在排序过程中会发生较小的GC。这是错误的