Подтвердить что ты не робот

МетодHandle производительность

Я написал небольшой тест, который проверяет производительность 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 version "1.7.0_11" Java (TM) SE Runtime Environment (сборка 1.7.0_11-b21) 64-разрядная виртуальная машина Java HotSpot TM (сборка 23.6-b04, смешанный режим) ОС - Windows 7 64

4b9b3361

Ответ 1

Похоже, это косвенно ответили @AlekseyShipilev в отношении другого запроса. В следующей ссылке Как повысить производительность Field.set(perhap using MethodHandles)?

Если вы прочитаете, вы увидите дополнительные контрольные показатели, которые показывают похожие результаты. Вероятно, прямые вызовы могут быть просто оптимизированы JIT способами, которые Согласно вышеприведенным выводам, разница заключается в следующем: МетодHandle.invoke = ~ 195ns МетодHandle.invokeExact = ~ 10ns Прямые вызовы = 1.266ns

Таким образом, прямые вызовы будут выполняться быстрее, но MH работает очень быстро. Для большинства случаев использования этого должно быть достаточно и, безусловно, быстрее, чем исходная структура отражения (кстати, согласно вышеприведенным выводам, отражение также значительно быстрее при java8 vm)

Если эта разница значительна в вашей системе, я бы предложил найти разные шаблоны, а не прямое отражение, которое будет поддерживать прямые вызовы.

Ответ 2

Похоже, что другие видели аналогичные результаты: 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 прокомментирует вопрос о ссылках на его работу и выглядит очень полезно: Вызов геттера в Java, хотя отражение: какой самый быстрый способ повторного вызова (производительность и масштабируемость)?

Ответ 3

Если publicStaticMethod была простой реализацией, такой как возврат константы, очень возможно, что прямой вызов был выровнен компилятором JIT. Это может быть невозможно с помощью методаHandles.

RE http://vanillajava.blogspot.com/2011/08/methodhandle-performance-in-java-7.html пример, как уже упоминалось, замечает его не очень большую реализацию. если вы измените тип casting на int (вместо Integer) в цикле вычислений, результаты ближе к прямому вызову метода.

С запутанной реализацией (создание и вызов будущей задачи, которая возвращает случайный int) дал ориентир с более близкими числами, где MethodStatic был на 10% медленнее, чем прямой метод. Таким образом, вы можете видеть в 3 раза более медленную производительность из-за оптимизации JIT

Ответ 4

запустите его с помощью опций VM: -Djava.compiler = NONE, что означает запрет JIT. Тогда вы увидите разницу.