Поскольку я активно использую рефлексивный доступ к массивам в проекте, я решил сравнить производительность 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
Дополнительная информация
- Результаты аналогичны при запуске JVM с "-сервером"
- Другие методы
Array
(например,Array#getInt
,Array#getLength
,Array#set
и т.д.) также работают намного медленнее, чем аналогично реализованные методы утилиты - Этот вопрос несколько связан с: Какова цель java.lang.reflect.Array в методах getter и setter?