由于我在项目中大量使用反射访问数组,所以我决定比较 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
相同的功能,不同之处在于所抛出的异常(例如,如果使用不是数组的Object
调用该方法,会抛出ClassCastException
而不是IllegalArgumentException
)。
令我惊讶的是,这种实用程序方法的性能比Array#get
要好得多。
- 其他人是否也遇到了使用
Array#get
时的性能问题,或者这可能是硬件/平台/Java版本问题(我在双核Windows 7笔记本电脑上测试了Java 8)? - 我是否错过了关于
Array#get
方法功能的某些内容?例如,是否有必须使用本机调用实现的一些功能? - 为什么使用本机方法实现
Array#get
,而可以使用纯Java实现具有更高性能的相同功能?
测试类和结果
使用 Caliper 进行测试(需要最新的Caliper从git进行编译)。但是,为了您的方便,我还包括了执行简化测试的主函数(您需要删除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的结果可以在这里查看。
在我的机器上,简化后的主方法的结果如下:
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
等)的执行速度也比实现方式相似的实用方法要慢得多。 - 这个问题与java.lang.reflect.Array的getter和setter方法的目的是什么?有些相关。