Насколько медленно работает рефлексия в Android?

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

Естественно, рефлексия - это очень полезная вещь в Java и Android разработке. Даже несмотря на то, что она часто может быть причиной значительных проблем с производительностью в Android приложениях. Давайте посмотрим на несколько реальных примеров, чтобы понять как это может на них повлиять.

Два примера из реальной жизни

Наш первый пример - NYTimes Android app. С помощью NimbleDroid, наши друзья в NYTimes нашли, что “reflective type adapters” в Gson замедляют их приложение на 700ms. В итоге они починили это с помощью написания custom type adapters.

Наш второй пример - Photobucket, большая платформа для обмена фотографиями. Тут, рефлексия снова причина больших проблем.

com.photobucket.android Icicle Graph
660ms для вызова конструктора com.photobucket.api.client.jersey.UserClient

Мы видим, что время выполнения конструктора com.photobucket.api.client.jersey.UserClient - это целых 660ms. На iricle graph мы можем увидеть, что причина такой большой задержки - рефлексия. Давайте посмотрим:

com.photobucket.android Iricle Graph
много reflection вызовов, например: java.lang.Class.getGenericInterfaces

Напомним, что метод getGenericInterfaces() возвращает типы интерфейсов, которые реализуют данный класс. Тут он вызывается 5 раз и это занимает ~81ms. Конечно, изначально это не выглядит слишком много, но все эти вызовы вместе занимают около 600ms. Давайте копнем поглубже и попытаемся понять, почему это занимает столько времени.

Похоже, что эта библиотека позволяет разработчикам настроить REST клиент с помощью аннотаций. Проблема в том, что она не обрабатывает аннотации во время сборки, но создает REST клиент в runtime (с помощью рефлексии). С точки зрения производительности - это катастрофа.

Micro-benchmarks

Мы создали простой тест чтобы посмотреть, насколько медленно работает рефлексия.

Мы будем работать в android.app.Activity и повторять опериции по 10.000 раз, например:

Class<?> clazz = android.app.Activity.class;
for (int i = 0; i < 10000; i++) {
	clazz.getFields();
}

Мы также добавили два теста, которые включают создания обьектов (типа DummyItem - полностью пустой класс), чтобы посмотреть на замедления только со стороны рефлексии. Вот пример:

try {
    for (int i = 0; i < 1_000_000; i++) {
        DummyItem.class.newInstance();
    }
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

И вот наши результаты (все числа в миллисекундах, измерено на наших реальных устройствах, используемых каждый день, чтобы сделать тест более приближенным к реальности):

NEXUS 5 (6.0) ART GALAXY S5 (5.0) ART GALAXY S3 mini (4.1.2) Dalvik
getFields 1108 1626 27083
getDeclaredFields 347 951 7687
getGenericInterfaces 16 23 2927
getGenericSuperclass 247 298 665
makeAccessible 14 147 449
getObject 21 167 127
setObject 21 201 161
createDummyItems 312 358 774
createDummyItemsWithReflection 1332 6384 2891

Очевидно, что рефлексия на Android - это довольно медленно: сравните данные с рефлексией (1332ms, 6384ms, 2891ms) и данные без нее (312ms, 358ms, 774ms). Интересно, что на Android 5.0 ART рефлексия исполняется медленнее, чем на Android 4.1 Dalvik.

Больше примеров

ActiveAndroid - еще одна библиотека, использующая рефлексию. Давайте посмотрим, как она влияет на время запуска, проанализировав несколько приложений из Play store:

Вот Scribd:

Scribd Iricle Graph
1093ms на вызов com.activeandroid.ActiveAndroid.initialize

У Myntra похожие проблемы:

Myntra Iricle Graph
1421ms на вызов com.activeandroid.ActiveAndroid.initialize

Как видите, библиотеке нужно больше секунды для инициализации. Это довольно много, учитывая, что пользователи ожидают время запуска приложения около 2 секунд..

В заключение хочется сказать, что рефлексия на Android работает довольно медленно. Чтобы улучшить UX ваших пользователей, мы советуем:

Рекомендация: избегайте использования рефлексии (или библиотек, которые используют рефлексию) вовсе, и в частности не используйте "reflective type adapters" для сериализации Java объектов.