Главная » Хабрахабр » Производительность в iOS — Core Animation, Offscreen Rendering и System Trace. Часть 2

Производительность в iOS — Core Animation, Offscreen Rendering и System Trace. Часть 2

6% — читай во второй части статьи по материалам доклада Люка Пархэма на прошлогодней конференции MBLT DEV.
Как избежать проблем с производительностью с помощью пресета Core Animation, что использовать для трассировки участков кода и с помощью каких функций сократить долю вычислительных операций в приложении с 26% до 0. Первая часть статьи доступна здесь.

Под катом не только полезные советы, но и последние early bird билеты на MBLT DEV 2018 — купить их можно только сегодня.

Core Animation

Зачастую, даже если найдены проблемные места приложения, трудности с производительностью остаются. Core Animation (CA) — пресет в профилировщике, который используется измерения FPS (кадры в секудну), чтобы увидеть, лагают анимации, или нет. Сервер для рендеринга отвечает за создание анимации. Причина в том, что при работе с UI-фреймворками, используется UIView, но под капотом создается экземпляр CATransaction (или система делает это сама), и все эти инструкции передаются серверу на обработку. Если анимация выполняется с помощью UIView, например, классового метода animate(withDuration:animations:), она обрабатывается the render server, который считается отдельным потоком и работает со всеми анимациями в приложении.

Вот как это выглядит: Можно заставить the render server работать медленно, чтобы он не появлялся в Time Profiler, но он всё равно будет тормозить работу приложения.


Внизу — самая важная часть — параметры отладки.
Вверху расположен датчик частоты кадров. Первый — color blended layers. Есть два ключевых и при этом лёгких в настройке параметра. На самом деле, проблемы возникают даже в iMessage, всеми любимом приложении от Apple. Исправить его довольно просто.

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

Правило #3

У слоя есть свойство opacity, которому и надо выставить единицу. Делайте слои непрозрачными, когда это возможно — при условии, что у них одинаковые цвета, которые накладываются друг на друга. Всегда проверяйте, что фоновый цвет задан и он непрозрачный.

Offscreen rendering

Следующий параметр — внеэкранный рендеринг. Если включить эту функцию, то участки будут выделены жёлтым цветом.

Можно включить параметры, запустить приложение и увидеть, что происходит не так. Удобство Core Animation заключается в возможности просматривать другие приложения. На экране в Instagram вверху есть небольшие жёлтые кружочки, в которых показываются сториз пользователей.

А если посмотреть на них на iPhone 5 или на старой модели iPod, загрузка будет идти ещё медленнее.
Например, на iPhone 6s они загружаются довольно медленно. Он нагружает графический процессор. Это происходит из-за того, что внутрисистемный рендеринг гораздо хуже alpha blending. В итоге устройству приходится постоянно выполнять дополнительные вычисления между графическим и центральным процессором, поэтому возникают дополнительные задержки, которые в большинстве случаев можно избежать.

Правило #4

Применение viewLayer.cornerRadius приводит к offscreen rendering. Не используйте параметр cornerRadius. В этом случае используется UIGraphics context. Вместо этого можно использовать класс UIBezierPath, а также что-то похожее на работу с CGBitMap, как было ранее с декодированием JPEG.

Здесь можно задавать размер и делать закруглённые углы.
Это ещё один метод экземпляра класса UIImage. Затем фрагмент возвращается из UIImageContext. Bezierpath применяется для выделения области изображения. Таким образом, получаем готовое изображение с закруглёнными углами вместо того, чтобы закруглять рамки, в которые будет вставляться изображение.

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

Правило #5

Лучше его избегать. Свойство класса shouldRasterize из набора СALayer позволяет кэшировать текстуры, которые прошли рендеринг. Так что это создаёт больше проблем, чем пользы. Если shouldRasterize не использовался определённое количество миллисекунд, он оставит кэш и будет предлагать рендеринг каждого кадра.

Ускоряйтесь

  • Избегайте внеэкранного рендеринга и смешения прозрачных слоёв.
  • Используйте их только тогда, когда без них не обойтись. Внеэкранный рендеринг появляется из-за наличия теней, закругления углов и так далее.
  • Делайте изображения непрозрачными.
  • Не используйте cornerRadius, используйте кривые Безье.
  • При работе с текстом не используйте свойство layer.shadow, замените на NSShadow.

Activity trace

Трассировка активности похожа на то, что делает Time Profiler, но в меньшем масштабе. Она позволяет рассмотреть потоки и их взаимодействие между собой.

Правило #6

Можно придумать способ для трассировки ивентов или участков кода и увидеть, сколько времени они занимают в реальной работе приложения. Используйте System Trace для отслеживания периода времени для конкретных событий. System Trace даёт информацию о том, что происходит в системе:

  • “Sing Post” сигнализирует о том, что происходит что-то важное.
  • Отметки — это единичные события, за которыми стоит следить, например — появление анимации.
  • По интервалу события можно отследить, сколько времени занимает декодирование.

Итак, программа показывает, как код взаимодействует с остальными элементами системы.

На скрине — образец создания шаблона трассировки системы:

  • 1 — загрузка изображения
  • 2 — декодирование изображения
  • 3 — наклон анимаций.

Как правило, им присваиваются цифры, например 1 или 2, и они становятся красными или зелёными, в зависимости от настройки. Нужно добавить несколько опций, чтобы понять, какой цвет получится. В Swift они уже доступны. На Objective-C нужно прописать команду #import для kdebug_signpost.

Затем нужно вызвать эту функцию, прописав kdebug_signpost или kdebug_signpost_start и kdebug_signpost_end.

Последнее число обозначает цвет.
Указываем 3 события вместе с числами, которые написаны в коде, и ключевой элемент для каждого конкретного события. Например, 2 — красный.

Упрощенная схема описана в тестовом проекте Люка на Swift. Далее идут важные события, особые объекты.

Сначала, когда приложение будет запущено, программа не будет выдавать информацию, но как только приложение даст сбой, мы увидим расчёты. На скрине видно, как это будет выглядеть при запуске трассировки.

Затем идёт декодинг, который занимает около 40 миллисекунд.
Загрузка изображений занимает порядка 200 миллисекунд. Можно перечислить в программе события, а затем наблюдать и получать информацию о том, сколько времени требуется на их выполнение, и как они взаимодействуют между собой. Полезно отслеживать эти данные, если в приложении происходит множество операций.

Дополнительные инструменты

Приложение похоже на Snapchat. На скрине — AR-проект по замедленной съёмке смартфона. Из-за этого камера снимала медленно, около 10 кадров в секунду. В нём использован эффект ретуширования фото, и для каждого кадра на него тратилось 26,4% от всех вычислительных операций. Она отвечала за активное отправление данных. При изучении видим, что самая верхняя строчка выполняла основную часть работы. Если исследовать причины проседания производительности, можно заметить, что дело в интенсивном использовании NSDispatchData.

Кажется, что это так просто, однако всё, что этот метод делает внутри, занимает 18% из 26% вычислений.
Этот подкласс NSData всего лишь получает последовательность байт с помощью метода getBytes в определённом интервале и передаёт её в другое место.

Правило #1

Используйте NSData и getBytes. Это несложная операция на Objective-C, но если она является причиной проблем в системе, следует переключиться на более низкоуровневую функцию на plain C. Так как проблема связана с получением плавающих значений, можно воспользоваться функцией копирования памяти. С её помощью можно переместить фрагмент данных в другое место. Это сильно экономит вычислительные мощности устройства.

В примере исходный код выделен красным. В NSData происходит много операций. Метод с копированием памяти — практически то же самое. С помощью функции getBytes можно перевести данные в числа с плавающей точкой. Он довольно простой и выполняет на порядок меньше операций.

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

Правило #2

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

Применяя CADisplayLink, можно замедлить обновление UI. В большинстве случаев это будет происходить с частотой выше 60 кадров в секунду. Он применяется только для iOS 10 и более поздних версий. Существует параметр preferredFramesPerSecond. При работе в новых версиях iOS можно установить желаемое количество кадров в секунду. Для старых систем придётся проделать это вручную. В большинстве случаев — 15 кадров в секунду или около того, чтобы не тратить вычислительную мощность впустую и не добавлять приложению лишней работы.

Правило #3

При работе с Objective-C полезно применять кэширование IMP pointers (указателей на имплементацию методов). Когда в Objective-C вызывается метод under the hood, происходит вызов функции objc_msgSend(). Если при трассировке видно, что вызов занимает большой отрезок времени, можно от него избавиться. По сути, это хранилище кэша с указателями на функцию, им можно дать названия некоторых методов. Поэтому вместо того, чтобы каждый раз производить этот поиск, стоит делать кэширование следующим образом: поместить указатели функций в кэш и вызывать их напрямую. Как правило, это происходит в два раза быстрее.

Для вызова этого метода помощи обычной функции вызова нужно внести название объекта в селектор, который выдаёт результаты поиска. Если нет указателя, то метод вызывается при помощи команды methodForSelector. Возможно, это не самый удобный, но зато быстрый способ.

Правило #4

ARC добавляет много лишней работы. Не применяйте ARC (automatic reference counting).

Однако, если есть места, где retain и release занимают слишком много времени, присмотритесь к отказу от ARC. Компилятор при включенном ARC сам разбрасывает retain/release в нужных местах. Делайте это в том случае, если оптимизация необходима, так как это займёт много времени.

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

Полезные материалы

Для дальнейшего погружения в тему Люк Пархэм рекомендует почитать книгу “iOS and MacOS: Performance Tuning” и посмотреть его туториалы. Первая часть статьи доступна здесь.

Видеозапись доклада Люка на MBLT DEV 2017 теперь в открытом доступе:

Ещё больше знаний и советов на MBLT DEV 2018

Первые спикеры объявлены:

  • John C. Fox из Netflix расскажет о высококачественной локализации, работе с агрессивными сетевыми условиями и A/B-тестировании.
  • Krzysztof Zabłocki, The New York Times, готовит доклад о паттернах в iOS.
  • Laura Morinigo, Google Developer Expert, расскажет о лучших Firebase-практиках.

Последние early bird билеты улетят сегодня.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Перевод] GPU консоли Nintendo DS и его интересные особенности

Я хотел бы рассказать вам о работе GPU консоли Nintendo DS, об его отличиях от современных GPU, а также выразить своё мнение о том, почему использование Vulkan вместо OpenGL в эмуляторах не принесёт никаких преимуществ. Это может пригодиться для эмуляции ...

Использование UTF-8 в HTTP заголовках

1 — это текстовой протокол передачи данных. Как известно, HTTP 1. При этом в теле сообщений можно использовать другую кодировку, которая должна быть обозначена в заголовке «Content-Type». HTTP сообщения закодированы, используя ISO-8859-1 (которую условно можно считать расширенной версией ASCII, содержащей ...