Как стать автором
Обновить

Комментарии 15

Отличный ресерч!

Единственное, немного смущает формулировка: «Однако на самом деле с IO запрос выполнился быстрее в 10.92 раз!»

Ведь время выполнения самого сетевого запроса не сильно изменилось. Изменилось лишь время от запуска корутины до следующего действия на ui (hideKeyboard())

Спасибо!) Да, эта формулировка не совсем корректная, в статье идет речь про время до начала сетевого запроса. Поправлю

А вы случайно не измеряете время создания экземпляра Retrofit и вашего ApiService? Функция provideApiService() уж очень подозрительно выглядит, и конечно, если на каждый запрос создавать новый инстанс ApiService, будет медленно.

Другими словами, удалось ли понять, за счёт чего именно вы добились ускорения? Шаги "заметить проблему" и "решить проблему" были, а промежуточного "понять, где тормоза" я не заметил :)

Конечно, на реальных проектах я не создаю новый объект ApiService на каждый запрос.

Здесь для демо-проекта добавила такую реализацию, т.к. задача была в том числе показать, что есть проблемы со скоростью первого запроса.

Описанное поведение с задержкой было замечено именно на боевом проекте в первую очередь

Тогда расскажите, что именно было медленным, какая именно часть кода внутри вызова provideApiService().getSomethingFromApi() исполнялась на главном потоке?

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

Точно, я подумал что это с другого проекта результаты, невнимательно прочитал) Спасибо!

Спасибо за любопытную и немного провокационную статью. :)

Время в 500+ миллисекунд меня сильно удивило, да честно говоря и 50 мс после исправления - это неприлично много.

Очень здорово, что вы выложили тестовый проект. Без него вряд ли удалось бы воспроизвести результаты. Погонял его на стареньком (6 лет, Snapdragon 410) аппарате, и вот что получилось (время в формате "первый запуск / последующие запуски"):

getMainThreadResult 620 / 18 мс
getRetrofitFixResult 57 / 8 мс
getWithContextResult 12 / 2 мс

Основную часть времени занимает создание reflection-based JSON-адаптера:

Замена его на kotlin-codegen дает ускорение в разы:
getMainThreadResult 92 / 11 мс
getRetrofitFixResult 63 / 8 мс (тут нет ускорения, разрыв сильно сокращается)

Но в принципе вызывать Retrofit.Builder()....create(ApiService::class.java) каждый раз по нажатию кнопки - сомнительная идея, обычно это делается единожды при старте приложения.

Такой рефакторинг ускоряет getMainThreadResult еще в разы: 25 / 3 мс

Из этих 25 мс основная часть уходит на создание CatFactResponseJsonAdapter (один раз при первом вызове любого метода, возвращающего CatFactResponse).

3 мс - это главное время, от которого имеет смысл отталкиваться. getRetrofitFixResult и getWithContextResult по-прежнему чуть быстрее (1-3 мс), но стоит ли это мучений с обертками над обертками? ;) Простые архитектурные изменения (см. выше) дают гораздо больший эффект.

Цифры выше можно рассматривать как worst-case scenario: старый аппарат, debug build без AOT-компиляции и прочих рекомендуемых хитростей.

Мораль: профайлер - наше всё. ;)

Большое спасибо за такой анализ!)

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

Сочувствую. Reflection - это боль и по производительности, и в сочетании с R8, а kotlin-reflect еще и здоровенная либа в рантайме.

Вы пробовали закинуть issue в Retrofit? Сходу не нашел. Логично было бы создавать адаптеры в Dispatchers.IO, но возможно у них были причины так не делать.

Пока что не создали issue

Применил решение описанное в статье в рабочем коде и в сложной цепочке flatMapMerge()... .toList() получаю бесконечное ожидание, меняю решение из статьи на обычный заворот в корутин диспатчер, все ок.

Спасибо за замечание, видимо это еще один кейс в копилку нерабочих вместе с async/await, описанных в статье

Не рассматривали вариант просто вернуться к адаптору от Вортана?

Мы исправили наш фикс в том числе и для работы с async{}/await(). Скоро опубликуем его. Пока взяли паузу и ждем решений от желающих миновать тех. собеседование, как описано в конце статьи)

Зарегистрируйтесь на Хабре, чтобы оставить комментарий