java.lang.reflect.Array 的表现
Performance of java.lang.reflect.Array
由于我在项目中大量使用反射访问数组,所以我决定比较 array[index]
与 java.lang.reflect.Array.get(array, index)
的性能。虽然我预计反射调用会慢很多,但我惊讶地发现它们慢了 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
相同的功能,但抛出的异常有显着差异(例如,抛出 ClassCastException
而不是 IllegalArgumentException
,如果使用非数组的 Object
调用方法。)。
令我惊讶的是,这个实用方法比 Array#get
好。
三个问题:
- 这里的其他人是否遇到与
Array#get
相同的性能问题,或者这可能是 hardware/platform/Java-version 问题(我在双核 Windows 上使用 Java 8 进行了测试7 台笔记本电脑)?
- 我是否遗漏了有关方法
Array#get
功能的某些信息? IE。是否有某些功能必须使用本机调用才能实现?
- 为什么
Array#get
是使用本机方法实现的,而在纯 Java 中可以以更高的性能实现相同的功能,是否有特定原因?
测试类和结果
测试已使用 Caliper(编译代码所需的 git 的最新 Caliper)完成。但为了您的方便,我还包含了一个执行简化测试的主要方法(您需要删除 Caliper 注释才能使其编译)。
测试类:
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 结果 here。
简化的 main 方法的结果在我的机器上看起来像这样:
time for ObjectArray_at: 620 NS
time for ObjectArray_Array_get: 10525 NS
time for ObjectArray_ArrayUtil_get: 1287 NS
附加信息
- 当 运行 带有“-server”的 JVM 时结果相似
- 其他
Array
方法(例如 Array#getInt
、Array#getLength
、Array#set
等)的执行速度也比类似实现的实用方法慢得多
- 这个问题有点相关:What is the purpose of java.lang.reflect.Array's getter and setter methods?
是的,Array.get
在OpenJDK / Oracle JDK中很慢,因为它是通过native方法实现的,没有经过JIT优化。
Array.get
成为本机没有特殊原因,除了从 JDK 的最早版本开始就是这样(当时 JVM 不是很好,根本没有 JIT) .此外,还有来自 GNU Classpath 的纯 Java compatible implementation of java.lang.reflect.Array
。
目前(从 JDK 8u45 开始)仅 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 there is an open issue JDK-8051447 可以在将来的某个时候改进 Array.*
方法的性能。
由于我在项目中大量使用反射访问数组,所以我决定比较 array[index]
与 java.lang.reflect.Array.get(array, index)
的性能。虽然我预计反射调用会慢很多,但我惊讶地发现它们慢了 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
相同的功能,但抛出的异常有显着差异(例如,抛出 ClassCastException
而不是 IllegalArgumentException
,如果使用非数组的 Object
调用方法。)。
令我惊讶的是,这个实用方法比 Array#get
好。
三个问题:
- 这里的其他人是否遇到与
Array#get
相同的性能问题,或者这可能是 hardware/platform/Java-version 问题(我在双核 Windows 上使用 Java 8 进行了测试7 台笔记本电脑)? - 我是否遗漏了有关方法
Array#get
功能的某些信息? IE。是否有某些功能必须使用本机调用才能实现? - 为什么
Array#get
是使用本机方法实现的,而在纯 Java 中可以以更高的性能实现相同的功能,是否有特定原因?
测试类和结果
测试已使用 Caliper(编译代码所需的 git 的最新 Caliper)完成。但为了您的方便,我还包含了一个执行简化测试的主要方法(您需要删除 Caliper 注释才能使其编译)。
测试类:
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 结果 here。
简化的 main 方法的结果在我的机器上看起来像这样:
time for ObjectArray_at: 620 NS
time for ObjectArray_Array_get: 10525 NS
time for ObjectArray_ArrayUtil_get: 1287 NS
附加信息
- 当 运行 带有“-server”的 JVM 时结果相似
- 其他
Array
方法(例如Array#getInt
、Array#getLength
、Array#set
等)的执行速度也比类似实现的实用方法慢得多 - 这个问题有点相关:What is the purpose of java.lang.reflect.Array's getter and setter methods?
是的,Array.get
在OpenJDK / Oracle JDK中很慢,因为它是通过native方法实现的,没有经过JIT优化。
Array.get
成为本机没有特殊原因,除了从 JDK 的最早版本开始就是这样(当时 JVM 不是很好,根本没有 JIT) .此外,还有来自 GNU Classpath 的纯 Java compatible implementation of java.lang.reflect.Array
。
目前(从 JDK 8u45 开始)仅 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 Array.*
方法的性能。