Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance of java.lang.reflect.Array

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:

  1. 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)?
  2. 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?
  3. 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: What is the purpose of java.lang.reflect.Array's getter and setter methods?
like image 482
Balder Avatar asked May 18 '15 14:05

Balder


People also ask

Is Java reflection slow or expensive?

Adding 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).

What is Java Lang reflect array?

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.

Why is array performance better than collection?

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.

What is reflection array?

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.


1 Answers

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.

like image 181
apangin Avatar answered Oct 18 '22 05:10

apangin