Since I'm making heavy use of reflective access to arrays in a project, I decided to compare the performance of array[index]
vs java.lang.reflect.Array.get(array, index)
. While I anticipated, that reflective calls are quite a bit slower, I was surprised to see that they are between 10-16 times slower.
So I decided to write a simple utility method that does about the same as Array#get
but receives the array at the given index by casting the object instead of using a native method (as does Array#get
):
public static Object get(Object array, int index){
Class<?> c = array.getClass();
if (int[].class == c) {
return ((int[])array)[index];
} else if (float[].class == c) {
return ((float[])array)[index];
} else if (boolean[].class == c) {
return ((boolean[])array)[index];
} else if (char[].class == c) {
return ((char[])array)[index];
} else if (double[].class == c) {
return ((double[])array)[index];
} else if (long[].class == c) {
return ((long[])array)[index];
} else if (short[].class == c) {
return ((short[])array)[index];
} else if (byte[].class == c) {
return ((byte[])array)[index];
}
return ((Object[])array)[index];
}
I believe that this method provides the same functionality as Array#get
, with the notable difference of the thrown exceptions (e.g. a ClassCastException
gets thrown instead of an IllegalArgumentException
, if one calls the method with an Object
that is no array.).
To my surprise, this utility method performs much better than Array#get
.
Three questions:
Array#get
, or is this perhaps a hardware/platform/Java-version issue (I tested with Java 8 on a dual core Windows 7 laptop)?Array#get
? I.e. is there some functionality that must necessarily be implemented using a native call?Array#get
was implemented using native methods, when the same functionality could have been implemented in pure Java with a much higher performance?Test Classes and Results
The tests have been done using Caliper (latest Caliper from git necessary to compile the code). But for your convenience I also included a main method that performs a simplified test (you need to remove the Caliper annotations to make it compile).
TestClass:
import java.lang.reflect.Array;
import com.google.caliper.BeforeExperiment;
import com.google.caliper.Benchmark;
public class ArrayAtBenchmark {
public static final class ArrayUtil {
public static Object get(Object array, int index){
Class<?> c = array.getClass();
if (int[].class == c) {
return ((int[])array)[index];
} else if (float[].class == c) {
return ((float[])array)[index];
} else if (boolean[].class == c) {
return ((boolean[])array)[index];
} else if (char[].class == c) {
return ((char[])array)[index];
} else if (double[].class == c) {
return ((double[])array)[index];
} else if (long[].class == c) {
return ((long[])array)[index];
} else if (short[].class == c) {
return ((short[])array)[index];
} else if (byte[].class == c) {
return ((byte[])array)[index];
}
return ((Object[])array)[index];
}
}
private static final int ELEMENT_SIZE = 100;
private Object[] objectArray;
@BeforeExperiment
public void setup(){
objectArray = new Object[ELEMENT_SIZE];
for (int i = 0; i < objectArray.length; i++) {
objectArray[i] = new Object();
}
}
@Benchmark
public int ObjectArray_at(int reps){
int dummy = 0;
for (int i = 0; i < reps; i++) {
for (int j = 0; j < ELEMENT_SIZE; j++) {
dummy |= objectArray[j].hashCode();
}
}
return dummy;
}
@Benchmark
public int ObjectArray_Array_get(int reps){
int dummy = 0;
for (int i = 0; i < reps; i++) {
for (int j = 0; j < ELEMENT_SIZE; j++) {
dummy |= Array.get(objectArray, j).hashCode();
}
}
return dummy;
}
@Benchmark
public int ObjectArray_ArrayUtil_get(int reps){
int dummy = 0;
for (int i = 0; i < reps; i++) {
for (int j = 0; j < ELEMENT_SIZE; j++) {
dummy |= ArrayUtil.get(objectArray, j).hashCode();
}
}
return dummy;
}
// test method to use without Cailper
public static void main(String[] args) {
ArrayAtBenchmark benchmark = new ArrayAtBenchmark();
benchmark.setup();
int warmup = 100000;
// warm up
benchmark.ObjectArray_at(warmup);
benchmark.ObjectArray_Array_get(warmup);
benchmark.ObjectArray_ArrayUtil_get(warmup);
int reps = 100000;
long start = System.nanoTime();
int temp = benchmark.ObjectArray_at(reps);
long end = System.nanoTime();
long time = (end-start)/reps;
System.out.println("time for ObjectArray_at: " + time + " NS");
start = System.nanoTime();
temp |= benchmark.ObjectArray_Array_get(reps);
end = System.nanoTime();
time = (end-start)/reps;
System.out.println("time for ObjectArray_Array_get: " + time + " NS");
start = System.nanoTime();
temp |= benchmark.ObjectArray_ArrayUtil_get(reps);
end = System.nanoTime();
time = (end-start)/reps;
System.out.println("time for ObjectArray_ArrayUtil_get: " + time + " NS");
if (temp == 0) {
// sanity check to prevent JIT to optimize the test methods away
System.out.println("result:" + result);
}
}
}
The Caliper results can be viewed here.
The results of the simplified main method look like this on my machine:
time for ObjectArray_at: 620 NS
time for ObjectArray_Array_get: 10525 NS
time for ObjectArray_ArrayUtil_get: 1287 NS
Additional information
Array
methods (e.g. Array#getInt
, Array#getLength
, Array#set
etc.) also perform much slower than similarly implemented utility methodsAdding setAccessible(true) call makes these reflection calls faster, but even then it takes 5.5 nanoseconds per call. Reflection is 104% slower than direct access (so about twice as slow).
The java. lang. reflect. Array class provides static methods to dynamically create and access Java arrays. Array permits widening conversions to occur during a get or set operation, but throws an IllegalArgumentException if a narrowing conversion would occur.
Arrays due to fast execution consumes more memory and has better performance. Collections, on the other hand, consume less memory but also have low performance as compared to Arrays. Arrays can hold the only the same type of data in its collection i.e only homogeneous data types elements are allowed in case of arrays.
The Array class in java. lang. reflect package is a part of the Java Reflection. This class provides static methods to create and access Java arrays dynamically. It is a final class, which means it can't be instantiated or changed.
Yes, Array.get
is slow in OpenJDK / Oracle JDK because it is implemented by a native method and is not optimized by JIT.
There is no special reason for Array.get
to be native except that it has been so from the earliest releases of JDK (when JVM was not so good and there was no JIT at all). Moreover, there is a pure Java compatible implementation of java.lang.reflect.Array
from GNU Classpath.
Currently (as of JDK 8u45) only Array.newInstance and Array.getLength are optimized (being JVM intrinsics). Looks like nobody really cared about performance of reflective get/set methods. But as @Marco13 noticed there is an open issue JDK-8051447 to improve the performance of Array.*
methods somewhen in future.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With