MethodHandle производительность
Я написал небольшой тест, который тестирует производительность java.lang.invoke.MethodHandle
, java.lang.reflect.Method
и прямые вызовы методов.
Я прочитал это MethodHandle.invoke()
производительность почти такая же, как прямые звонки. Но результаты моего теста показывают другое: MethodHandle
вызвать примерно в три раза медленнее, чем отражение. В чем моя проблема? Может быть, это результат некоторых JIT-оптимизаций?
public class Main {
public static final int COUNT = 100000000;
static TestInstance test = new TestInstance();
static void testInvokeDynamic() throws NoSuchMethodException, IllegalAccessException {
int [] ar = new int[COUNT];
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType mt = MethodType.methodType(int.class);
MethodHandle handle = lookup.findStatic(TestInstance.class, "publicStaticMethod", mt) ;
try {
long start = System.currentTimeMillis();
for (int i=0; i<COUNT; i++) {
ar[i] = (int)handle.invokeExact();
}
long stop = System.currentTimeMillis();
System.out.println(ar);
System.out.println("InvokeDynamic time: " + (stop - start));
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
static void testDirect() {
int [] ar = new int[COUNT];
try {
long start = System.currentTimeMillis();
for (int i=0; i<COUNT; i++) {
ar[i] = TestInstance.publicStaticMethod();
}
long stop = System.currentTimeMillis();
System.out.println(ar);
System.out.println("Direct call time: " + (stop - start));
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
static void testReflection() throws NoSuchMethodException {
int [] ar = new int[COUNT];
Method method = test.getClass().getMethod("publicStaticMethod");
try {
long start = System.currentTimeMillis();
for (int i=0; i<COUNT; i++) {
ar[i] = (int)method.invoke(test);
}
long stop = System.currentTimeMillis();
System.out.println(ar);
System.out.println("Reflection time: " + (stop - start));
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
static void testReflectionAccessible() throws NoSuchMethodException {
int [] ar = new int[COUNT];
Method method = test.getClass().getMethod("publicStaticMethod");
method.setAccessible(true);
try {
long start = System.currentTimeMillis();
for (int i=0; i<COUNT; i++) {
ar[i] = (int)method.invoke(test);
}
long stop = System.currentTimeMillis();
System.out.println(ar);
System.out.println("Reflection accessible time: " + (stop - start));
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
public static void main(String ... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InterruptedException {
Thread.sleep(5000);
Main.testDirect();
Main.testInvokeDynamic();
Main.testReflection();
Main.testReflectionAccessible();
System.out.println("\n___\n");
System.gc();
System.gc();
Main.testDirect();
Main.testInvokeDynamic();
Main.testReflection();
Main.testReflectionAccessible();
}
}
Среда: версия Java "1.7.0_11" Java(TM) SE Runtime Environment (сборка 1.7.0_11-b21) 64-разрядная серверная виртуальная машина Java HotSpot (TM) (сборка 23.6-b04, смешанный режим) ОС - Windows 7 64
3 ответа
Похоже, что @AlekseyShipilev косвенно ответил на другой запрос. По следующей ссылке Как я могу улучшить производительность Field.set (perhap используя MethodHandles)?
Если вы прочитаете, вы увидите дополнительные тесты, которые показывают аналогичные результаты. Вполне вероятно, что прямые вызовы могут быть просто оптимизированы с помощью JIT таким образом, что, согласно полученным выше выводам, разница заключается в следующем: MethodHandle.invoke =~195ns MethodHandle.invokeExact =~10ns Прямые вызовы = 1,266ns
Так что - прямые звонки все равно будут быстрее, но MH очень быстр. Для большинства случаев использования этого должно быть достаточно, и, конечно, он быстрее, чем старый каркас отражения (кстати, согласно выводам выше, рефлексия также значительно быстрее при java8 vm)
Если это различие является значительным в вашей системе, я бы предложил найти другие шаблоны, а не прямые отражения, которые будут поддерживать прямые вызовы.
Похоже, что другие видели похожие результаты: http://vanillajava.blogspot.com/2011/08/methodhandle-performance-in-java-7.html
Вот чей-то еще: http://andrewtill.blogspot.com/2011/08/using-method-handles.html
Я запустил этот второй и увидел, что они были примерно с той же скоростью, даже устанавливая тест для прогрева. Тем не менее, я исправил это, чтобы он не создавал массив args каждый раз. При подсчете по умолчанию это привело к тому же результату: методы обработки были немного быстрее. Но я сделал счет 10000000 (по умолчанию *10), и рефлексия пошла намного быстрее.
Итак, я бы порекомендовал тестирование с параметрами. Интересно, будет ли MethodHandles более эффективно работать с параметрами? Также проверьте изменение количества - сколько итераций.
Комментарий @meriton по этому вопросу связан с его работой и выглядит очень полезным: хотя вызов метода get в Java является размышлением: какой самый быстрый способ повторно его назвать (с точки зрения производительности и масштабируемости)?
Если publicStaticMethod
Это была простая реализация, такая как возврат константы. Очень возможно, что прямой вызов был встроен компилятором JIT. Это может быть невозможно с methodHandles.
RE http://vanillajava.blogspot.com/2011/08/methodhandle-performance-in-java-7.html пример, как упомянуто, что комментирует его не очень хорошая реализация. если вы измените приведение типа к int (вместо Integer) в цикле вычисления, результаты будут ближе к прямому вызову метода.
С запутанной реализацией (создание и вызов будущей задачи, которая возвращает случайное целое число) дал эталон с более близкими числами, где MethodStatic был макс ~10% медленнее, чем прямой метод. Таким образом, производительность может быть в 3 раза ниже из-за оптимизации JIT