是Java';s System.arraycopy()对于小型阵列是否有效?
Java的是Java';s System.arraycopy()对于小型阵列是否有效?,java,performance,caliper,Java,Performance,Caliper,Java的System.arraycopy()对小型数组有效吗?还是因为它是本机方法,所以它的效率可能比简单的循环和函数调用低很多 本机方法是否会因跨越某种Java系统桥而产生额外的性能开销?字节码以本机方式执行,因此性能可能比循环更好 因此,在循环的情况下,它必须执行字节码,这将导致开销。而数组拷贝应该是直接的memcopy。本机函数应该比JVM函数快,因为没有VM开销。然而,对于许多(>1000)非常小(len在Sid编写的内容上稍微扩展一下)的人来说,System.arraycopy很可能
System.arraycopy()
对小型数组有效吗?还是因为它是本机方法,所以它的效率可能比简单的循环和函数调用低很多
本机方法是否会因跨越某种Java系统桥而产生额外的性能开销?字节码以本机方式执行,因此性能可能比循环更好
因此,在循环的情况下,它必须执行字节码,这将导致开销。而数组拷贝应该是直接的memcopy。本机函数应该比JVM函数快,因为没有VM开销。然而,对于许多(>1000)非常小(len在Sid编写的内容上稍微扩展一下)的人来说,
System.arraycopy
很可能只是一个JIT内在特性;这意味着当代码调用System.arraycopy
时,它很可能会调用特定于JIT的实现(一旦JIT标记System.arraycopy
)不是通过JNI接口执行的,因此不会产生本机方法的正常开销
一般来说,执行本机方法确实会有一些开销(通过JNI接口,在执行本机方法时也不会发生一些内部JVM操作)。但并不是因为一个方法被标记为“本机”,所以您实际上是在使用JNI执行它。JIT可以做一些疯狂的事情
最简单的检查方法是,正如所建议的那样,编写一个小的基准测试,小心Java微基准测试的正常警告(首先预热代码,避免代码没有副作用,因为JIT只是将其优化为无操作等)。这是一个有效的问题。例如,在
Java.nio.DirectByteBuffer.put(byte[])
,作者试图避免对少量元素进行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()
,无论长度如何(即使是0),都不会“逐个元素复制”。因为ArrayList
非常注重性能,我们可以推导出System.arraycopy()
是最有效的数组复制方法,无论长度如何。以下是我的基准代码:
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;
}
在我的测试中,使用copyCount
=10000000(1e7)或更大的值调用test()
,可以在第一次copy/loop
调用期间实现预热,因此使用testRep
=5就足够了;使用copyCount
=1000000(1e6)预热至少需要2或3次迭代,因此应增加testRep
,以获得可用的结果
对于我的配置(CPU Intel Core 2 Duo E8500@3.16GHz、Java SE 1.6.0_35-b10和Eclipse 3.7.2),从基准测试中可以看出:
- 当
=24时,copySize
和手动循环所用的时间几乎相同(有时一个比另一个稍微快一点,有时则相反)System.arraycopy()
- 当
<24时,手动循环比copySize
快(当System.arraycopy()
=23时稍快,当copySize
<5时确实快)copySize
- 当
>24时,copySize
比手动循环快(当System.arraycopy()
=25时,循环时间/arraycopy时间的比率随着copySize
的增加而增加)copySize
注意:我不是英语母语的人,请原谅我所有的语法/词汇错误。
系统。arraycopy
使用memmove
操作来移动单词,并在幕后移动C中的其他基元类型。因此,它会尽最大努力以尽可能高效的方式移动。推测和可能过时的信息,我使用运行了一些基准测试。事实上,Caliper附带了一些示例,包括一个测量这个问题的工具!您所要做的就是运行
mvn exec:java-Dexec.mainClass=com.google.caliper.runner.CaliperMain-Dexec.args=examples.copyraraybenchmark
我的结果是基于Oracle的Java HotSpot(TM)64位服务器VM,1.8.0_31-b13,运行在2010年年中的MacBook Pro(macOS 10.11.6,采用Intel Arrandale i7,8 GiB RAM)上。我不相信发布原始计时数据是有用的。相反,我将用支持的可视化来总结结论
总之:
- 为循环编写手册
,将每个元素复制到新实例化的数组中从来都不是一件好事,即使数组只有5个元素。
- 这两种技术在性能上几乎是相同的,你选择哪一种取决于你的品味
- 几乎与
和array.copyOf(array,array.length)
一样快,但速度并不一致(请参见50000array.clone()
s的例子)。因此,鉴于调用的冗长性,如果您需要精确控制哪些元素被复制到哪里,我建议您使用int
System.arraycopy()
你试过并运行过基准测试吗?我很想在上面看到一个微基准。我认为内置的本机代码不受JNI的影响latencies@glowcoder,我想看看你的一个微型基准测试,这是一个很难正确回答的问题:有一个特定的基准测试可以处理大量的小数据(小于缓存线)复制,所有JDK实现者都知道这一点,并希望以此为目标。从Java到本机代码的转换会带来额外的成本。@AndyThomas Cramer,在上下文切换到JNI方面根本没有本机代码。旁注:JIT足够聪明,可以通过循环将数组复制优化为与
System.arraycopy
相同的代码JIT足够聪明,可以