Эффективность отражения Java

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

Ответ 1

Да - абсолютно. Поиск класса через рефлексию по величине дороже.

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

Ответ 2

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

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

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

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

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

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

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

EDIT:

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

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

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

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

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

Ответ 7

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

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

Ответ 8

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

Ответ 9

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

Ответ 10

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

Ответ 11

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

Ответ 12

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

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

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");
    }
}

Ответ 13

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

Ответ 14

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

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