Is Java's System.arraycopy() efficient for small arrays?
Is Java's System.arraycopy()
efficient for small arrays, or does the fact that it's a native method make it likely to be substantially less efficient than a simple loop and a function call?
Do native methods incur additional performance overhead for crossing some kind of Java-system bridge?
Expanding a little on what Sid has written, it's very likely that System.arraycopy
is just a JIT intrinsic; meaning that when code calls System.arraycopy
, it will most probably be calling a JIT-specific implementation (once the JIT tags System.arraycopy
as being "hot") that is not executed through the JNI interface, so it doesn't incur the normal overhead of native methods.
In general, executing native methods does have some overhead (going through the JNI interface, also some internal JVM operations cannot happen when native methods are being executed). But it's not because a method is marked as "native" that you're actually executing it using JNI. The JIT can do some crazy things.
Easiest way to check is, as has been suggested, writing a small benchmark, being careful with the normal caveats of Java microbenchmarks (warm up the code first, avoid code with no side-effects since the JIT just optimizes it as a no-op, etc).
Here is my benchmark code:
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;
}
From my tests, calling test()
with copyCount
= 10000000 (1e7) or greater allows the warm-up to be achieved during the first copy/loop
call, so using testRep
= 5 is enough; With copyCount
= 1000000 (1e6) the warm-up need at least 2 or 3 iterations so testRep
shall be increased in order to obtain usable results.
With my configuration (CPU Intel Core 2 Duo E8500 @ 3.16GHz, Java SE 1.6.0_35-b10 and Eclipse 3.7.2) it appears from the benchmark that:
- When
copySize
= 24,System.arraycopy()
and the manual loop take almost the same time (sometimes one is very slightly faster than the other, other times it’s the contrary), - When
copySize
< 24, the manual loop is faster thanSystem.arraycopy()
(slightly faster withcopySize
= 23, really faster withcopySize
< 5), - When
copySize
> 24,System.arraycopy()
is faster than the manual loop (slightly faster withcopySize
= 25, the ratio loop-time/arraycopy-time increasing ascopySize
increases).
Note: I’m not English native speaker, please excuse all my grammar/vocabulary errors.
This is a valid concern. For example, in java.nio.DirectByteBuffer.put(byte[])
, the author tries to avoid a JNI copy for small number of elements
// 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;
For System.arraycopy()
, we can examine how JDK uses it. For example, in ArrayList
, System.arraycopy()
is always used, never "element by element copy", regardless of length (even if it's 0). Since ArrayList
is very performance conscious, we can derive that System.arraycopy()
is the most effecient way of array copying regardless of length.