java.lang.reflect.Array的性能

由于我在项目中大量使用对数组的reflection访问,因此我决定比较array[index]java.lang.reflect.Array.get(array, index) 。 虽然我预计,reflection调用会慢得多,但我惊讶地发现它们的速度要慢10-16倍。

所以我决定编写一个与Array#get大致相同的简单实用程序方法,但是通过强制转换对象而不是使用本机方法接收给定索引处的数组(就像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]; } 

我相信这个方法提供了与Array#get相同的function,抛出exception的显着差异(例如,如果使用不是数组的Object调用方法,则抛出ClassCastException而不是IllegalArgumentException )。

令我惊讶的是,这个实用程序方法比Array#get执行更好。

三个问题:

  1. 这里的其他人是否遇到与Array#get相同的性能问题,或者这可能是硬件/平台/ Java版本问题(我在双核Windows 7笔记本电脑上使用Java 8进行了测试)?
  2. 我是否想念有关Array#get方法function的内容? 即是否必须使用本机调用实现一些function?
  3. 是否有一个特定的原因,为什么使用本机方法实现Array#get ,当相同的function可以在纯Java中以更高的性能实现时?

测试类和结果

测试是使用Caliper完成的(最新的Caliper来自编译代码所需的git)。 但为了您的方便,我还包括一个执行简化测试的主方法(您需要删除Caliper注释以使其编译)。

识别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); } } } 

可以在此处查看Caliper结果。

简化主方法的结果在我的机器上看起来像这样:

 time for ObjectArray_at: 620 NS time for ObjectArray_Array_get: 10525 NS time for ObjectArray_ArrayUtil_get: 1287 NS 

附加信息

  • 使用“-server”运行JVM时的结果类似
  • 其他Array方法(例如Array#getIntArray#getLengthArray#set等)也比类似实现的实用方法执行得慢得多
  • 这个问题与java.lang.reflect.Array的getter和setter方法的目的有什么关系?

是的, Array.get在OpenJDK / Oracle JDK中很慢,因为它是由本机方法实现的,并没有被JIT优化。

除了从最早的JDK版本(当JVM不太好并且根本没有JIT)时, Array.get没有特殊原因。 此外,还有一个来自GNU Classpath的java.lang.reflect.Array的纯Java 兼容实现 。

目前(从JDK 8u45开始),只优化了Array.newInstance和Array.getLength (是JVM内在函数)。 看起来没有人真正关心reflectionget / set方法的性能。 但正如@ Marco13所注意到的那样 , JDK-8051447是一个未解决的问题,可以提高Array.*方法的性能。