我在这里读了一些关于Arrays.sort的线程,使用“tuned quick-sort”表示原始类型,并使用merge-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());
对于原始类型数组,花费了22ms,对于带有对象的数组花费了98ms。我的笔记本电脑i7配备8核和8GB内存。我是否错误地运行了它?
非常感谢!
答案 0 :(得分:12)
这对我来说并不奇怪。
首先,你有原语和需要追逐引用的间接,两个原语之间的比较会更快,等等。
其次,原始数组将与CPU缓存非常匹配。非原始数组不一定是因为不能保证引用的对象在内存中是连续的(不太可能),另外, referrent 对象更大,这意味着 less 它们中的任何一个都可以适应缓存。
请注意,在这两种情况下,数组中的值都适合缓存,但Integer[]
的问题是你仍然需要离开缓存并点击内存总线追逐参考文献并在主存中找到它们;那些引用可能指向堆上的所有位置。这将使可怜的CPU等待并等待现在缓存未命中变得更有可能。
也就是说,你有这样的基元数组
_ _ _ _ _
|5| |7| |2| |1| ... |4|
这些都在记忆中彼此相邻。当一个值从内存中拉入缓存时,邻居也会被拉入缓存。 Quicksort和mergesort在数组的连续部分上运行,因此它们从这里的CPU缓存中获益很多非常(这是locality of reference)
但是当你有一个像这样的Integer
数组时
_ _
|--->|7| ______> |1|
_ | _ | _
| | |_| | | ... |_| | | _
| _ |_____ |________>|4|
|___>|5| | _
|__>|2|
引用的存储位置在内存中是连续的,因此它们可以很好地与缓存一起使用。问题是*间接, referrent Integer
对象在内存中被碎片化的可能性以及它们的 less 将适合缓存的事实。这个额外的间接,碎片和大小问题是不与缓存很好地协作。
同样,对于像在阵列的连续部分上播放的quicksort或mergesort这样的东西,这是巨大的,巨大的,巨大的,几乎可以肯定地占据了绝大多数的性能差异。
我是否错误地运行了它?
是的,请在下次需要进行基准测试时使用System.nanoTime
。 System.currentTimeMillis
分辨率很高,不适合进行基准测试。
答案 1 :(得分:9)
你的int []适合你的L2缓存。它大约是4 B * 50K,它是200 KB,你的L2缓存是256 KB。这将比您的L3 []运行速度快得多,因为它大约是28 B * 50K或1400 KB。
L2缓存(~11个时钟周期)比L3缓存快约4-6倍(约45-75个时钟周期)
我敢打赌,如果你不止一次地运行它,你会得到更好的结果,因为代码会变暖。
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();
}
}
打印
int[] sort took 32.1 ms
Integer[] sort took 104.1 ms
int[] sort took 4.0 ms
Integer[] sort took 83.8 ms
int[] sort took 33.4 ms
Integer[] sort took 76.7 ms
int[] sort took 4.4 ms
Integer[] sort took 40.5 ms
int[] sort took 3.8 ms
Integer[] sort took 17.4 ms
int[] sort took 4.7 ms
Integer[] sort took 22.4 ms
int[] sort took 4.4 ms
Integer[] sort took 12.1 ms
int[] sort took 3.7 ms
Integer[] sort took 11.2 ms
int[] sort took 3.9 ms
Integer[] sort took 10.7 ms
int[] sort took 3.6 ms
Integer[] sort took 11.9 ms
您可以看到代码升温的差异有多大。
答案 2 :(得分:0)
我是否错误地运行了它?
您的基准测试非常原始,并没有真正建立任何东西。对于每种情况,排序时间如何随阵列大小增长?原始排序和对象排序之间的差异有多大可归因于比较基元与比较对象的不同成本? (这将与排序算法的性能无关,但会归因于您的测试的排序算法。)
正如其他人所指出的那样,如果你计时需要数十毫秒的时间,你应该使用System.nanoTime
; System.currentTimeMillis
的分辨率通常不超过10毫秒。但是,简单地切换定时技术并不能解决测试中更严重的问题。