是Java的System.arraycopy()有效的小arrays?
对于小型数组,Java的System.arraycopy()
效率如何,或者它是一个本地方法,这使得它可能比一个简单的循环和一个函数调用效率低得多?
本地方法是否会导致跨越某种Java系统桥的额外性能开销?
Sid写的东西有点扩大了, System.arraycopy
很可能只是一个JIT内在的东西; 这意味着当代码调用System.arraycopy
,它很可能会调用特定于JIT的实现(一旦JIT标记System.arraycopy
为“热”),而不是通过JNI接口执行,所以它不会产生本地方法的正常开销。
通常,执行本地方法确实会有一些开销(通过JNI接口,当执行本地方法时也不会发生一些内部的JVM操作)。 但是这并不是因为一个方法被标记为“native”,而是使用JNI实际执行它。 JIT可以做一些疯狂的事情。
最简单的检查方法是,如前所述,编写一个小基准,小心Java的基本标记(首先预热代码,避免代码没有副作用,因为JIT只是将其优化为no-op等等)。
这是我的基准代码:
public void test(int copySize, int copyCount, int testRep) { System.out.println("Copy size = " + copySize); System.out.println("Copy count = " + copyCount); System.out.println(); for (int i = testRep; i > 0; --i) { copy(copySize, copyCount); loop(copySize, copyCount); } System.out.println(); } public void copy(int copySize, int copyCount) { int[] src = newSrc(copySize + 1); int[] dst = new int[copySize + 1]; long begin = System.nanoTime(); for (int count = copyCount; count > 0; --count) { System.arraycopy(src, 1, dst, 0, copySize); dst[copySize] = src[copySize] + 1; System.arraycopy(dst, 0, src, 0, copySize); src[copySize] = dst[copySize]; } long end = System.nanoTime(); System.out.println("Arraycopy: " + (end - begin) / 1e9 + " s"); } public void loop(int copySize, int copyCount) { int[] src = newSrc(copySize + 1); int[] dst = new int[copySize + 1]; long begin = System.nanoTime(); for (int count = copyCount; count > 0; --count) { for (int i = copySize - 1; i >= 0; --i) { dst[i] = src[i + 1]; } dst[copySize] = src[copySize] + 1; for (int i = copySize - 1; i >= 0; --i) { src[i] = dst[i]; } src[copySize] = dst[copySize]; } long end = System.nanoTime(); System.out.println("Man. loop: " + (end - begin) / 1e9 + " s"); } public int[] newSrc(int arraySize) { int[] src = new int[arraySize]; for (int i = arraySize - 1; i >= 0; --i) { src[i] = i; } return src; }
在我的testing中,使用copyCount
= 10000000(1e7)或更高的值调用test()
允许在第一次copy/loop
调用期间实现预热,所以使用testRep
= 5就足够了; 对于copyCount
= 1000000(1e6),预热需要至less2或3次迭代,因此应该增加testRep
以获得可用的结果。
通过我的configuration(CPU Intel Core 2 Duo E8500 @ 3.16GHz,Java SE 1.6.0_35-b10和Eclipse 3.7.2),从基准testing中可以看出:
- 当
copySize
= 24时,System.arraycopy()
和手动循环几乎是一样的时间(有时一个比另一个快一些,其他时间则相反), - 当
copySize
<24时,手动循环比System.arraycopy()
快(copySize
= 23时稍快,copySize
<5时更快), - 当
copySize
> 24时,System.arraycopy()
比手动循环快(copySize
= 25稍快copySize
,比率循环时间/copySize
时间随copySize
增加而增加)。
注意:我不是英语母语的人,请原谅我所有的语法/词汇错误。
这是一个有效的关注。 例如,在java.nio.DirectByteBuffer.put(byte[])
,作者试图避免less量元素的JNI副本
// These numbers represent the point at which we have empirically // determined that the average cost of a JNI call exceeds the expense // of an element by element copy. These numbers may change over time. static final int JNI_COPY_TO_ARRAY_THRESHOLD = 6; static final int JNI_COPY_FROM_ARRAY_THRESHOLD = 6;
对于System.arraycopy()
,我们可以检查JDK如何使用它。 例如,在ArrayList
,总是使用System.arraycopy()
,而不pipe“长度是多less”(即使是0),也不要使用“逐个元素拷贝”。 由于ArrayList
非常注重性能,所以我们可以推导出System.arraycopy()
是数组复制最有效的方法,不pipe长度如何。
System.arraycopy
使用memmove
操作来移动单词,并使用汇编来移动场景后面的其他基本types。 所以它尽最大努力提高效率。
字节代码无论如何都是本地执行的,所以性能可能比循环更好。
所以在循环的情况下,它将不得不执行字节代码,这将导致开销。 而arrays副本应该是直的memcopy。
原生函数应该比JVM函数更快,因为没有VM开销。 然而对于很多(> 1000)非常小(len <10)的数组,它可能会变慢。