Какой самый быстрый способ прочитать геттер из Java-класса, не зная этого класса во время компиляции? Фреймворки Java часто делают это. Много. И это может напрямую влиять на их производительность. Итак, давайте оценим различные подходы, такие как отражение, дескрипторы методов и генерация кода.
Вариант использования
 Предположим, у нас есть простой класс Person с именем и адресом: 
| 1 2 3 4 5 6 7 | publicclassPerson {   ...   publicString getName() {...}   publicAddress getAddress() {...}} | 
и мы хотим использовать такие фреймворки, как:
- XStream , JAXB или Jackson для сериализации экземпляров в XML или JSON.
- JPA / Hibernate для хранения лиц в базе данных.
- OptaPlanner для назначения адресов (если они туристы или бездомные).
  Ни одна из этих платформ не знает класс Person .  Поэтому они не могут просто вызвать person.getName() : 
| 1 2 3 4 5 | // Framework code   publicObject executeGetter(Object object) {      // Compilation error: class Person is unknown to the framework      return((Person) object).getName();   } | 
Вместо этого код использует отражение, дескрипторы методов или генерацию кода.
Но такой код называется очень много :
-   Если вы добавите 1000 разных людей в базу данных, JPA / Hibernate, вероятно, вызовет такой код 2000 раз:
-   1000 вызовов Person.getName()
-   еще 1000 звонков в Person.getAddress()
 
-   1000 вызовов 
- Точно так же, если вы пишете 1000 разных людей в XML или JSON, скорее всего, будет 2000 вызовов XStream, JAXB или Jackson.
Очевидно, что когда такой код вызывается x раз в секунду, его производительность имеет значение .
Тесты
Используя JMH, я запустил ряд микро-тестов, используя OpenJDK 1.8.0_111 в Linux на 64-битном 8-ядерном настольном компьютере Intel i7-4790 с 32 ГБ оперативной памяти. Тест JMH проходил с 3 вилками, 5 итерациями разогрева по 1 секунде и 20 итерациями измерения по 1 секунде.
Исходный код этого теста находится в этом репозитории GitHub .
Результаты TL; DR
- Отражение Java медленно. (*)
- Java MethodHandles тоже медленный. (*)
-   Сгенерированный код с javax.toolsбыстро. (*)
(*) В тех случаях использования, которые я сравнивал с нагрузкой, которую использовал. Ваш пробег может варьироваться.
  Итак, дьявол кроется в деталях.  Давайте пройдемся по реализациям, чтобы подтвердить, что я применил типичные магические приемы (такие как setAccessible(true) ). 
Реализации
Прямой доступ (базовый уровень)
  Я использовал нормальный person.getName() в качестве базовой линии: 
| 1 2 3 4 5 6 7 | publicfinalclassMyAccessor {    publicObject executeGetter(Object object) {        return((Person) object).getName();    }} | 
Это занимает около 2,7 наносекунд на операцию:
| 1 2 3 | Benchmark           Mode  Cnt  Score   Error  Units===================================================DirectAccess        avgt   602.667± 0.028ns/op | 
  Прямой доступ, естественно, самый быстрый подход во время выполнения, без затрат на загрузку.  Но он импортирует Person во время компиляции, поэтому он непригоден для всех сред. 
отражение
Очевидный способ чтения фреймворка, который получает getter во время выполнения, не зная об этом заранее, — через Java Reflection:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | publicfinalclassMyAccessor {    privatefinalMethod getterMethod;    publicMyAccessor() {        getterMethod = Person.class.getMethod("getName");        // Skip Java language access checking during executeGetter()        getterMethod.setAccessible(true);    }    publicObject executeGetter(Object bean) {        returngetterMethod.invoke(bean);    }} | 
  Добавление вызова setAccessible(true) ускоряет эти вызовы отражения, но даже тогда это занимает 5,5 наносекунд на вызов. 
| 1 2 3 4 | Benchmark           Mode  Cnt  Score   Error  Units===================================================DirectAccess        avgt   602.667± 0.028ns/opReflection          avgt   605.511± 0.081ns/op | 
Отражение на 106% медленнее, чем прямой доступ (примерно вдвое медленнее). Это также занимает больше времени, чтобы согреться.
Это не было для меня большим сюрпризом, потому что, когда я описываю (используя выборку) искусственно простую задачу коммивояжера с 980 городами в OptaPlanner , стоимость отражения выскакивает, как больной большой палец:
MethodHandles
MethodHandle был введен в Java 7 для поддержки invokedynamic инструкций. Согласно javadoc, это типизированная, непосредственно исполняемая ссылка на базовый метод. Звучит быстро, верно?
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | publicfinalclassMyAccessor {    privatefinalMethodHandle getterMethodHandle;    publicMyAccessor() {        MethodHandle temp = lookup.findVirtual(Person.class, "getName", MethodType.methodType(String.class));        temp = temp.asType(temp.type().changeParameterType(0, Object.class));        getterMethodHandle = temp.asType(temp.type().changeReturnType(Object.class));    }    publicObject executeGetter(Object bean) {        returngetterMethodHandle.invokeExact(bean);    }} | 
К сожалению, MethodHandle даже медленнее, чем отражение в OpenJDK 8. Он занимает 6,1 наносекунды на операцию, что на 132% медленнее, чем прямой доступ.
| 1 2 3 4 5 6 | Benchmark           Mode  Cnt  Score   Error  Units===================================================DirectAccess        avgt   602.667± 0.028ns/opReflection          avgt   605.511± 0.081ns/opMethodHandle        avgt   606.188± 0.059ns/opStaticMethodHandle  avgt   605.481± 0.069ns/op | 
  При этом, если MethodHandle находится в статическом поле, он занимает всего 5,5 наносекунд на операцию, что все еще медленнее, чем отражение .  Кроме того, это непригодно для большинства фреймворков.  Например, реализация JPA может нуждаться в отражении по m получателям ( Person , Company , Order ,…) m ( getName() , getAddress() , getBirthDate() ,…), так как же реализация JPA иметь n * m статических полей, не зная n ни m во время компиляции? 
Я действительно надеюсь, что MethodHandle станет таким же быстрым, как и прямой доступ в будущих версиях Java, заменив необходимость в…
Сгенерированный код с помощью javax.tools.JavaCompiler
  В Java можно скомпилировать и запустить сгенерированный код Java во время выполнения.  Поэтому с javax.tools.JavaCompiler API javax.tools.JavaCompiler мы можем генерировать код прямого доступа во время выполнения: 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | publicabstractclassMyAccessor {    publicstaticMyAccessor generate() {        finalString String fullClassName = "x.y.generated.MyAccessorPerson$getName";        finalString source = "package x.y.generated;\n"                + "public final class MyAccessorPerson$getName extends MyAccessor {\n"                + "    public Object executeGetter(Object bean) {\n"                + "        return ((Person) object).getName();\n"                + "    }\n"                + "}";        JavaFileObject fileObject = new...(fullClassName, source);        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();        ClassLoader classLoader = ...;        JavaFileManager javaFileManager = new...(..., classLoader)        CompilationTask task = compiler.getTask(..., javaFileManager, ..., singletonList(fileObject));        booleansuccess = task.call();        ...        Class compiledClass = classLoader.loadClass(fullClassName);        returncompiledClass.newInstance();    }    // Implemented by the generated subclass    publicabstractObject executeGetter(Object object);} | 
  Для получения дополнительной информации о том, как использовать javax.tools.JavaCompiler , взгляните на страницу 2 этой статьи или этой статьи .  Помимо javax.tools , аналогичные подходы могут использовать ASM или CGLIB, но они выводят дополнительные зависимости и могут иметь разные результаты производительности. 
В любом случае сгенерированный код так же быстр, как прямой доступ :
| 1 2 3 4 | Benchmark           Mode  Cnt  Score   Error  Units===================================================DirectAccess        avgt   602.667± 0.028ns/opGeneratedCode       avgt   602.745± 0.025ns/op | 
Поэтому, когда я снова запустил точно такую же задачу коммивояжера в OptaPlanner , на этот раз с использованием генерации кода для доступа к переменным планирования скорость вычисления баллов в целом была на 18% выше . И профилирование (с использованием выборки) тоже выглядит намного лучше:
Обратите внимание, что в нормальных случаях использования такой прирост производительности вряд ли будет обнаружен из-за огромных потребностей ЦП в реально сложном вычислении оценки…
Единственный недостаток генерации кода во время выполнения — это то, что он приводит к заметной стоимости начальной загрузки, особенно если сгенерированный код не компилируется массово. Поэтому я все еще надеюсь, что когда-нибудь MethodHandles получат такой же быстрый доступ, как и прямой доступ, просто чтобы избежать затрат на загрузку.
Вывод
В этом тесте рефлексия и MethodHandles в два раза медленнее, чем прямой доступ в OpenJDK 8, но сгенерированный код быстрее, чем прямой доступ.
| 1 2 3 4 5 6 7 | Benchmark           Mode  Cnt  Score   Error  Units===================================================DirectAccess        avgt   602.667± 0.028ns/opReflection          avgt   605.511± 0.081ns/opMethodHandle        avgt   606.188± 0.059ns/opStaticMethodHandle  avgt   605.481± 0.069ns/opGeneratedCode       avgt   602.745± 0.025ns/op | 
| Смотрите оригинальную статью здесь: Java Reflection, но гораздо быстрее Мнения, высказанные участниками Java Code Geeks, являются их собственными. | 


