Производительность отражения Java

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

14 ответов

Решение

Да, конечно. Поиск класса с помощью рефлексии, по величине, дороже.

Цитирую документацию 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.

Да, это медленнее.

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

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

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

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

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

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

РЕДАКТИРОВАТЬ:

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

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

Вы можете обнаружить, что 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 нс.

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

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

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

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

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

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

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

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

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

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

Да, при использовании 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 миллис

Интересно, что установка 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

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

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

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

В doReflection() это накладные расходы из-за Class.forName("misc.A") (что потребовало бы поиска класса, потенциально сканирующего путь к классу в файловой системе), а не newInstance(), вызываемого в классе. Мне интересно, как будет выглядеть статистика, если Class.forName ("misc.A") выполняется только один раз вне цикла for, это не обязательно должно выполняться для каждого вызова цикла.

Да, всегда будет медленнее создавать объект путем отражения, потому что JVM не может оптимизировать код во время компиляции. См. Учебники Sun / Java Reflection для получения более подробной информации.

Смотрите этот простой тест:

public class TestSpeed {
    public static void main(String[] args) {
        long startTime = System.nanoTime();
        Object instance = new TestSpeed();
        long endTime = System.nanoTime();
        System.out.println(endTime - startTime + "ns");

        startTime = System.nanoTime();
        try {
            Object reflectionInstance = Class.forName("TestSpeed").newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        endTime = System.nanoTime();
        System.out.println(endTime - startTime + "ns");
    }
}

Часто вы можете использовать Apache commons BeanUtils или PropertyUtils, которые самоанализ (в основном они кэшируют метаданные о классах, поэтому им не всегда нужно использовать отражение).

Я думаю, это зависит от того, насколько легким / тяжелым является целевой метод. если целевой метод очень легкий (например, метод получения / установки), он может быть в 1-3 раза медленнее. если целевой метод занимает около 1 миллисекунды или выше, производительность будет очень близка. вот тест, который я сделал с Java 8 и refleasm:

public class ReflectionTest extends TestCase {    
    @Test
    public void test_perf() {
        Profiler.run(3, 100000, 3, "m_01 by refelct", () -> Reflection.on(X.class)._new().invoke("m_01")).printResult();    
        Profiler.run(3, 100000, 3, "m_01 direct call", () -> new X().m_01()).printResult();    
        Profiler.run(3, 100000, 3, "m_02 by refelct", () -> Reflection.on(X.class)._new().invoke("m_02")).printResult();    
        Profiler.run(3, 100000, 3, "m_02 direct call", () -> new X().m_02()).printResult();    
        Profiler.run(3, 100000, 3, "m_11 by refelct", () -> Reflection.on(X.class)._new().invoke("m_11")).printResult();    
        Profiler.run(3, 100000, 3, "m_11 direct call", () -> X.m_11()).printResult();    
        Profiler.run(3, 100000, 3, "m_12 by refelct", () -> Reflection.on(X.class)._new().invoke("m_12")).printResult();    
        Profiler.run(3, 100000, 3, "m_12 direct call", () -> X.m_12()).printResult();
    }

    public static class X {
        public long m_01() {
            return m_11();
        }    
        public long m_02() {
            return m_12();
        }    
        public static long m_11() {
            long sum = IntStream.range(0, 10).sum();
            assertEquals(45, sum);
            return sum;
        }    
        public static long m_12() {
            long sum = IntStream.range(0, 10000).sum();
            assertEquals(49995000, sum);
            return sum;
        }
    }
}

Полный тестовый код доступен на GitHub: ReflectionTest.java

Другие вопросы по тегам