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:
- Do others here experience the same performance issues with
Array#get
, or is this perhaps a hardware/platform/Java-version issue (I tested with Java 8 on a dual core Windows 7 laptop)? - Do I miss something concerning the functionality of the method
Array#get
? I.e. is there some functionality that must necessarily be implemented using a native call? - Is there a specific reason, why
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
- The results are similar when running the JVM with "-server"
- The other
Array
methods (e.g.Array#getInt
,Array#getLength
,Array#set
etc.) also perform much slower than similarly implemented utility methods - This question is somewhat related to: Why Use java.lang.reflect.Array For Anything Other Than Array Creation?.
Aucun commentaire:
Enregistrer un commentaire