Производительность отражения Java - PullRequest
159 голосов
/ 12 января 2009

Приводит ли создание объекта с использованием отражения вместо вызова конструктора класса какие-либо существенные различия в производительности?

Ответы [ 14 ]

160 голосов
/ 12 января 2009

Да - абсолютно. Поиск класса с помощью отражения - по величине , более дорогой.

Цитирование Документация Java по рефлексии :

Поскольку отражение включает в себя типы, которые разрешаются динамически, определенные оптимизации виртуальных машин Java не могут быть выполнены. Следовательно, отражающие операции имеют более низкую производительность, чем их неотражающие аналоги, и их следует избегать в разделах кода, которые часто вызываются в чувствительных к производительности приложениях.

Вот простой тест, который я взломал за 5 минут на моей машине, используя Sun JRE 6u10:

public class Main {

    public static void main(String[] args) throws Exception
    {
        doRegular();
        doReflection();
    }

    public static void doRegular() throws Exception
    {
        long start = System.currentTimeMillis();
        for (int i=0; i<1000000; i++)
        {
            A a = new A();
            a.doSomeThing();
        }
        System.out.println(System.currentTimeMillis() - start);
    }

    public static void doReflection() throws Exception
    {
        long start = System.currentTimeMillis();
        for (int i=0; i<1000000; i++)
        {
            A a = (A) Class.forName("misc.A").newInstance();
            a.doSomeThing();
        }
        System.out.println(System.currentTimeMillis() - start);
    }
}

С этими результатами:

35 // no reflection
465 // using reflection

Имейте в виду, что поиск и создание экземпляров выполняются вместе, и в некоторых случаях поиск может быть подвергнут рефакторингу, но это только базовый пример.

Даже если вы просто создадите экземпляр, вы все равно получите удар по производительности:

30 // no reflection
47 // reflection using one lookup, only instantiating

Опять же, YMMV.

83 голосов
/ 14 февраля 2009

Да, медленнее.

Но помните чертово правило № 1 - ОПТИМИЗАЦИЯ ДАТЧИКА - КОРЕНЬ ВСЕГО ЗЛА

(Ну, может быть связано с # 1 для СУХОГО)

Клянусь, если бы кто-нибудь подошел ко мне на работе и спросил меня об этом, я бы очень внимательно следил за их кодом в течение следующих нескольких месяцев.

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

О, и я тоже не имею в виду писать глупый код. Просто подумайте о самом чистом способе, которым вы можете это сделать - без копирования и вставки и т.д. это "плохое" программирование)

Меня пугает, когда я слышу подобные вопросы, но потом я забываю, что все должны пройти изучение всех правил самостоятельно, прежде чем они действительно получат это. Вы получите это после того, как потратили человек-месяц на отладку чего-то, что кто-то «Оптимизировал».

EDIT:

В этой теме произошла интересная вещь. Проверьте ответ № 1, это пример того, насколько мощным компилятором является оптимизация. Тест полностью недействителен, потому что неотражающий экземпляр может быть полностью исключен.

Урок? Никогда не оптимизируйте, пока вы не написали чистое, аккуратно закодированное решение и не доказали, что оно слишком медленное.

34 голосов
/ 14 февраля 2009

Вы можете обнаружить, что A a = new A () оптимизируется JVM. Если вы помещаете объекты в массив, они работают не так хорошо. ;) Следующие отпечатки ...

new A(), 141 ns
A.class.newInstance(), 266 ns
new A(), 103 ns
A.class.newInstance(), 261 ns

public class Run {
    private static final int RUNS = 3000000;

    public static class A {
    }

    public static void main(String[] args) throws Exception {
        doRegular();
        doReflection();
        doRegular();
        doReflection();
    }

    public static void doRegular() throws Exception {
        A[] as = new A[RUNS];
        long start = System.nanoTime();
        for (int i = 0; i < RUNS; i++) {
            as[i] = new A();
        }
        System.out.printf("new A(), %,d ns%n", (System.nanoTime() - start)/RUNS);
    }

    public static void doReflection() throws Exception {
        A[] as = new A[RUNS];
        long start = System.nanoTime();
        for (int i = 0; i < RUNS; i++) {
            as[i] = A.class.newInstance();
        }
        System.out.printf("A.class.newInstance(), %,d ns%n", (System.nanoTime() - start)/RUNS);
    }
}

Это говорит о том, что на моей машине разница составляет около 150 нс.

26 голосов
/ 14 февраля 2009

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

Некоторые примеры приложений, использующих генерацию кода:

  • Вызов методов на прокси, сгенерированных CGLIB , немного быстрее, чем динамические прокси Java , поскольку CGLIB генерирует байт-код для своих прокси, но динамические прокси используют только отражение ( Я измерил CGLIB примерно в 10 раз быстрее в вызовах методов, но создание прокси было медленнее).

  • JSerial генерирует байт-код для чтения / записи полей сериализованных объектов вместо использования отражения. На сайте JSerial есть некоторые тесты .

  • Я не уверен на 100% (и сейчас мне не хочется читать исходники), но я думаю, что Guice генерирует байт-код для внедрения зависимостей. Поправь меня, если я ошибаюсь.

24 голосов
/ 12 января 2009

«Значительный» полностью зависит от контекста.

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

В общем, гибкость дизайна (где это необходимо!) Должна стимулировать использование отражения, а не производительности. Однако, чтобы определить, является ли производительность проблемой, вам нужно профилировать, а не получать произвольные ответы от дискуссионного форума.

22 голосов
/ 12 января 2009

Существуют некоторые издержки с отражением, но на современных виртуальных машинах они намного меньше, чем раньше.

Если вы используете отражение для создания каждого простого объекта в вашей программе, значит что-то не так. Иногда его использование, когда у вас есть веская причина, не должно быть проблемой вообще.

7 голосов
/ 20 июня 2013

Да, при использовании Reflection наблюдается снижение производительности, но возможный обходной путь оптимизации - кэширование метода:

  Method md = null;     // Call while looking up the method at each iteration.
      millis = System.currentTimeMillis( );
      for (idx = 0; idx < CALL_AMOUNT; idx++) {
        md = ri.getClass( ).getMethod("getValue", null);
        md.invoke(ri, null);
      }

      System.out.println("Calling method " + CALL_AMOUNT+ " times reflexively with lookup took " + (System.currentTimeMillis( ) - millis) + " millis");



      // Call using a cache of the method.

      md = ri.getClass( ).getMethod("getValue", null);
      millis = System.currentTimeMillis( );
      for (idx = 0; idx < CALL_AMOUNT; idx++) {
        md.invoke(ri, null);
      }
      System.out.println("Calling method " + CALL_AMOUNT + " times reflexively with cache took " + (System.currentTimeMillis( ) - millis) + " millis");

приведет к:

[java] Метод вызова 1000000 раз рефлексивно с поиском занял 5618 миллисекунд

[java] Вызов метода 1000000 раз с рефлексивным использованием кеша занял 270 миллис

7 голосов
/ 11 июля 2011

Отражение медленное, хотя распределение объектов не так безнадежно, как другие аспекты отражения. Для достижения эквивалентной производительности с помощью основанной на отражении реализации требуется, чтобы вы написали свой код, чтобы jit мог определить, какой класс создается. Если идентификация класса не может быть определена, тогда код распределения не может быть встроен. Что еще хуже, анализ escape не выполняется, и объект не может быть размещен в стеке. Если вам повезет, профилирование во время выполнения JVM может прийти на помощь, если этот код перегреется, и может динамически определить, какой класс преобладает, и может оптимизировать для этого.

Имейте в виду, что микробенчмарки в этой теме глубоко испорчены, поэтому возьмите их с небольшим количеством соли. Наименее ошибочным является метод Питера Лоури: он выполняет прогрев, чтобы соединить методы, и он (сознательно) побеждает анализ побега, чтобы убедиться, что распределение действительно происходит. Но даже у этого есть свои проблемы: например, можно ожидать, что огромное количество хранилищ массивов победит кеши и буферы хранилищ, так что это будет в основном ориентиром для памяти, если ваши выделения очень быстрые. (Благодарность Питеру за то, что он сделал правильное заключение: что разница составляет «150 нс», а не «2,5х». Я подозреваю, что он зарабатывает этим на жизнь.)

6 голосов
/ 27 октября 2011

Интересно, что расчет setAccessible (true), который пропускает проверки безопасности, снижает стоимость на 20%.

Без setAccessible (true)

new A(), 70 ns
A.class.newInstance(), 214 ns
new A(), 84 ns
A.class.newInstance(), 229 ns

С setAccessible (true)

new A(), 69 ns
A.class.newInstance(), 159 ns
new A(), 85 ns
A.class.newInstance(), 171 ns
6 голосов
/ 12 января 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...