Java, отражение, производительность и т. д. - PullRequest
14 голосов
/ 10 марта 2011

Итак, я знаю, что эта тема уже была сделана, например, Java Reflection Performance , но мой конкретный вопрос заключается в том, что многие популярные библиотеки реализованы с помощью аннотаций и рефлексии (например, реализации Gson, Jackson, Jaxb, поиск в спящем режиме). Многие (если не все) библиотеки обеспечивают хорошую (или отличную) производительность, даже если они используют рефлексию. У меня вопрос, как они это делают? Есть ли какие-то «хитрости», которые нужно знать, или они просто используют прямое отражение, а проблемы с выступлениями преувеличены?

EDIT: Так, например, когда мы пишем: MyObject obj = new Gson (). FromJson (someInputStream, MyObject.class);

Я могу понять, как библиотека может внутренне кэшировать объекты Field, но мне кажется, что ей нужно каждый раз рефлексивно создавать экземпляр объекта и вызывать метод установки для каждого поля (рефлексивно) на основе проанализированного значения. из JSON. Или есть какой-то способ оплатить (все) затраты на отражение только при запуске?

Я определенно заметил, что Gson / Jackson и т. Д. Имеют относительно большую стоимость запуска и после этого становятся очень быстрыми. Очевидно, мне интересно, если я напишу библиотеку, которая делает что-то неопределенно похожее, есть ли уловки, о которых мне нужно знать? Потому что, кажется, вы не можете избежать некоторого количества размышлений при каждом вызове.

Ответы [ 6 ]

10 голосов
/ 10 марта 2011

Что является дорогостоящим, так это поиск метода, но вызов метода один раз очень похож.

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

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

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

import java.lang.reflect.*;
class ReflectionOrNot { 
    public void run() { 
        try { 
            Thread.currentThread().sleep( 0 );
        } catch( InterruptedException ie ){}
    }

    public static void main( String ... args ) throws Exception { 

        ReflectionOrNot ron = new ReflectionOrNot();
        int max = 1000000;

        long start = System.currentTimeMillis();
        for( int i = 0 ; i < max ; i++ ) { 
            ron.run();
        }
        System.out.println( "Direct access took: " + ( System.currentTimeMillis() - start ) );


        Method m = ReflectionOrNot.class.getDeclaredMethod("run");
        start = System.currentTimeMillis();
        for( int i = 0 ; i < max ; i++ ) { 
            m.invoke( ron );
        }
        System.out.println( "Reflection    Took: " + ( System.currentTimeMillis() - start ) );


        start = System.currentTimeMillis();
        for( int i = 0 ; i < max ; i++ ) { 
             m = ReflectionOrNot.class.getDeclaredMethod("run");
            m.invoke( ron );
        }
        System.out.println( "Lookup + Reflect  : " + ( System.currentTimeMillis() - start ) );


    }
}

Вызов 1 миллион раз с разными подходами дал мне:

C:\Users\oreyes\java>java ReflectionOrNot
Direct access took: 422
Reflection    Took: 1156
Lookup + Reflect  : 3016

C:\Users\oreyes\java>java ReflectionOrNot
Direct access took: 422
Reflection    Took: 1125
Lookup + Reflect  : 2750

C:\Users\oreyes\java>java ReflectionOrNot
Direct access took: 485
Reflection    Took: 1203
Lookup + Reflect  : 2797
4 голосов
/ 10 марта 2011

Там, как правило, нет трюков. Большинство основанных на отражении действий выполняется на application startup, что не влияет на производительность во время выполнения после инициализации. Прекрасным примером этого является весь API Hibernate-аннотации.

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

2 голосов
/ 10 марта 2011

Отражение медленное по сравнению с использованием данных, помещенных в приложение компилятором, но быстрое по сравнению с предоставлением тех же данных, скажем, из базы данных.

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

1 голос
/ 10 марта 2011

Хитрость заключается в том, чтобы использовать отражение во время конфигурации, а не во время выполнения.

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

0 голосов
/ 16 августа 2018

Я создал эталон с миллионом итераций и десятью полями, заполненными случайными числами в Java 8 на ноутбуке с Windows.Вот результаты:

  • Прямой доступ: 941 нс номинал объекта (эталонное время)
  • Наивный самоанализ: 4613 нс на объект (+ 390%)
  • Самоанализс полем, кэшированным в HashMap: 1376 нс номинальный объект (+ 46%)
  • Самоанализ с полем, кэшированным в локальной переменной: 1105 нс на объект (+ 17%)

Iможет опубликовать код, если потребуется.

Для меня, если вы исключите из поиска все классы и поля, затраты на самоанализ незначительны.

С уважением,

0 голосов
/ 10 марта 2011

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

Поскольку бизнес-уровень должен работать долго, запуск происходит немного медленнее, но после этого производительность не падает.

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