Производительность java.lang.reflect.Array
Поскольку я активно использую рефлексивный доступ к массивам в проекте, я решил сравнить производительность array[index]
vs 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
, или это, возможно, проблема с оборудованием/платформой/Java-версией (я тестировал с Java 8 на двухъядерном ноутбуке Windows 7)?
- Я пропустил что-то относительно функциональности метода
Array#get
? То есть есть ли какая-то функциональность, которая обязательно должна быть реализована с использованием собственного вызова?
- Есть ли конкретная причина, почему
Array#get
был реализован с использованием собственных методов, когда одна и та же функциональность могла быть реализована в чистой Java с гораздо более высокой производительностью?
Тест-классы и результаты
Тесты были выполнены с использованием Caliper (последний калибр от git, необходимый для компиляции кода). Но для вашего удобства я также включил основной метод, который выполняет упрощенный тест (вам нужно удалить аннотации суппорта, чтобы скомпилировать его).
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);
}
}
}
Результаты калибровки можно просмотреть здесь.
Результаты упрощенного основного метода выглядят так на моей машине:
time for ObjectArray_at: 620 NS
time for ObjectArray_Array_get: 10525 NS
time for ObjectArray_ArrayUtil_get: 1287 NS
Дополнительная информация
Ответы
Ответ 1
Да, Array.get
медленнее в OpenJDK/Oracle JDK, потому что он реализован нативным методом и не оптимизирован JIT.
Нет никакой особой причины, чтобы Array.get
был родным, за исключением того, что это было так из самых ранних выпусков JDK (когда JVM был не очень хорош, и вообще не было JIT). Кроме того, существует чистая Java совместимая реализация java.lang.reflect.Array
из класса GNU Class.
В настоящее время (с JDK 8u45) только Array.newInstance и Array.getLength оптимизированы (являются встроенными JVM). Похоже, никто не заботился о производительности рефлексивных методов get/set. Но поскольку @Marco13 заметил, есть открытая проблема JDK-8051447, чтобы улучшить производительность методов Array.*
в будущем.