Разгоняем производительность iOS-приложений

в 17:52, , рубрики: edisonsoftware, ios development, Блог компании Edison, мобильные приложения, Программирование, проектирование, производительность, разработка, разработка мобильных приложений, разработка под iOS, сервисы

image
Тормоз для броненосца USS Iowa BB-1, 1910 год. Эта штуковина должна тормозить 11 346 тонн брони.

Оригинал: iOS App Performance: Instruments & beyond
Автор: Igor M

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

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

Тем не менее, оптимизация производительности является сложной проблемой. Большинство узких мест противоречит здравому смыслу. При отсутствии надлежащих измерений, крайне трудно понять, что замедляет ваше приложение.

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

Инструменты которые я затрону:

  • аналитика использования CPU, GPU, памяти и потребления энергии вашим приложением;
  • отзывчивость приложения;
  • время запуска;
  • метрики производительности, собираемые у ваших пользователей.

Аналитика использования CPU, GPU, памяти и потребления энергии вашим приложением
Первая задача состоит в том, чтобы с помощью профилировщика найти неэффективный код, который злоупотребляет CPU, GPU или памятью. Apple, имеет отличный инструмент для достижения этой цели: “Instruments”.

Есть 4 основных направления, которые нужно использовать в первую очередь:

  • CPU («Time Profiler» инструмент);
  • GPU («Core Animation» инструмент);
  • использование памяти («Allocations» инструмент);
  • потребляемая мощность («Energy diagnostics» инструмент).

image

Видео WWDC являются наилучшим источником информации об использовании Instruments для профилирования вашего приложения.

Вот некоторые варианты, с чего можно начать.

  1. Обучение Instruments.
  2. Производительность iOS 1, 2, 3.
  3. Оптимизация вашего приложения с помощью Instruments.
  4. Продвинутая Графика и Анимация для iOS приложений.
  5. Профилирование в глубину.
  6. Cocoa Touch передовой опыт.
  7. Производительность iOS и Энергооптимизация с помощью Instruments.
  8. Полировка вашего приложения.

Отзывчивость приложения

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

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

Для измерения, вы можете использовать пример:

CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
// Your method
NSUInteger milliseconds = (NSUInteger)((CFAbsoluteTimeGetCurrent() — startTime) * 1000);
NSLog(“Done in %lu ms”, milliseconds);

Еще один подход был описан разработчиками Viber. Они создают специальный поток, который следит за главным потоком и проверяет, что он не блокируется более чем на 400 мс.

image

image
Более подробную информацию можно найти в их презентации (PDF, 7MB).

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

Время запуска

Следующая важная вещь для измерений — это как быстро запускается ваше приложение. Типичный пользователь тратит всего несколько минут в вашем приложении. Долгое время запуска приводит к разочарованию.

Есть 2 варианта запуска вашего приложения.

  • Холодный запуск: процесс вашего приложения еще не был запущен, запуск выполняется сначала через ОС.
  • Теплый запуск: ваше приложение было свернуто, но не было убито. Оно восстанавливается из фона.

Этот раздел посвящен холодной загрузке, так как это требует больше ресурсов, и является тяжелой работой.

Существует последовательность запуска из приложения IOS. Фазы запуска приложения (из документации)

image

1. Измерьте общее время, затраченное на старте

Мы должны измерить время, от начала main () до конца applicationDidBecomeActive:

main.m

int main(int argc, char * argv[]) {
	
    // Save the initial time for startup
	[[StartipTimeMonitor sharedMonitor] appWillStartLoading];
	
    @autoreleasepool {
    	return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
	}
}

AppDelegate.m

- (void)applicationDidBecomeActive:(UIApplication *)application {
  // Your code
  // We assume that the app is loaded then the main thread become free
  // after this callback is finished.
  dispatch_async(dispatch_get_main_queue(), ^{
	[[StartipTimeMonitor sharedMonitor] appDidFinishLoading];
  });
}

После добавления кода проверяйте, что не становится хуже, когда вы вносите новые функции в приложение. Старайтесь держать время холодного запуска до 1 сек.

2. Измерьте время фаз при запуске

Как правило, недостаточно знать только общее время, затраченное на запуск. Важно также знать, какая фаза последовательности запуска замедляет его.

Наиболее важные этапы выглядят так:

  • [AppDelegate application: didFinishLaunchingWithOptions:] — этот метод вызывается, на этапе когда показано загрузочное изображение, или storyboard. Как только точка исполнения возвращается из этого метода, начинается фактическая загрузка пользовательского интерфейса.
  • [UIViewController loadView] — если ваше приложение создает собственный UIView, это место, где происходит его инициализация.
  • [UIViewController viewDidLoad] — UIView был загружен; время для окончательной инициализации.
  • [AppDelegate applicationDidBecomeActive:] — пользовательский интерфейс уже инициализирован, но он по-прежнему заблокирован, пока вызов этого метода не будет закончен. Этот метод также вызывается, когда приложение восстанавливается из фона.

Если некоторые из методов занимает слишком много времени, то надо оптимизировать их.

3. Измерьте время запуска «под давлением»

Существует одно важное различие между реальным миром и типичной тестовой средой.
Ваше приложение не живет в изоляции в реальном мире. Пользователь, как правило, переходит в ваше приложение из другого приложения. «Другое приложение» может быть очень тяжелым.

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

  • 4. Приложение уже запустилось, но по-прежнему бесполезно

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

Показатели производительности, подлежащие сборке от пользователей

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

Они могут иметь разные:

  • сетевые условия;
  • аппаратных средств;
  • программное обеспечение (версия ОС, Jailbreak...);
  • количество свободного места на устройстве;
  • и т.п.

Они могут использовать приложение необычным способом.

Вы можете получить оценку “одна звезда” при обзоре с жалобами ( «Ваше приложение медленно!»). Даже если все метрики которые вы измеряете в лаборатории находятся в безопасной зоне.

Что с этим делать?

Определить набор показателей эффективности (KPI) и собрать их от ваших реальных пользователей. Вы можете использовать практически любой пакет аналитики, чтобы сделать это.

Вот примеры ключевых показателей эффективности, которые вы можете получать от пользователей:

  • общее холодное время запуска;
  • общее теплое время запуска;
  • время запуска по фазам;
  • время, затраченное на скачивание необходимых данных с сервера;
  • количество раз, когда основной поток блокируется более чем на 400 мс;
  • количество предупреждений нехватки памяти;
  • количество FOOMS;
  • длительность операций, когда интерфейс заблокирован, или является непригодным для использования.

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

Выводы

Как вы можете видеть, измерения производительности выходят за рамки только запуска Instruments.app. Есть и другие важные моменты для анализа. Некоторые из описанных способов анализа быстро и легко осуществить, другие требуют больше времени и усилий. Тем не менее, они помогут вам контролировать производительность приложения, чтобы найти и устранить проблемы и сделать ваше приложение более приятным в использовании.


Достижение высокой производительности прокрутки, на примере приложения Facebook

Оригинал: Delivering high scroll performance
Автор: Clément Genzmer

Одной из наших целей в Facebook, является максимальное удобство для пользователей от использования нашего iOS приложения. Одна из задач, это убедиться, что лента новостей прокручивается плавно, но в сложном UIScrollView, с весьма разнообразным содержанием, не существует в настоящее время хороших способов в iOS, чтобы определить, почему частота кадров снизилась. Мы разработали стратегию идентификации, которая работает очень хорошо на практике и помогает нам поддерживать высокую производительность прокрутки. Далее, мы подробно расскажем, как это работает.

Измерение производительности прокрутки на устройстве

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

Мы измеряем частоту кадров на устройстве с помощью CADisplayLink API Apple. Каждый раз, когда кадр обрисовывается, мы измеряем время, которое потребовалось на это. Если потребовалось более одну шестидесятую секунды (16.6 мс), то частота кадров была низкой и прокрутка дергалась.

[CADisplayLink displayLinkWithTarget:self selector:@selector(_update)];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

Обнаружение и фиксация регрессии

В отличие от видеоигр, приложение Facebook не очень интенсивно использует GPU. Оно отображает в основном текст и изображения, и, таким образом, большинство кадров падает из-за нагрузки на процессор. Для поддержания высокой производительности процессора, мы хотим убедиться, что все операции, которые составляют рендеринг данных в Ленте новостей выполняются, менее чем за 16.6 миллисекунд. На практике рендеринг кадра состоит из нескольких этапов, и приложение, как правило, имеет только от 8 до 10 мс в основном потоке до падения частоты кадров.

Знание, того где основной поток проводит большую часть времени на CPU, позволяет получить наилучшую производительность прокрутки. Можно использовать инструмент Time Profiler, чтобы оценить, где основной поток проводит большую часть времени, но бывает трудно воссоздать точные условия на устройстве, когда частота кадров падает.

Другой подход заключается в сборе данных во время работы приложения, чтобы помочь определить наиболее вероятную причину падения кадра. То есть, можно сказать, что приложение профилирует само себя. Чтобы сделать это, надо использовать сигналы. Полученные данные могут быть не точными, но это позволяет получить данные профилирования в изолированном окружении. Это невозможно с традиционным профилированием на iOS с помощью стандартных инструментов, таких как Instruments и DTrace.

Сигналы и профилирование на устройстве

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

static void _callstack_signal_handler(int signr, siginfo_t *info, void *secret) {
  callstack_size = backtrace(callstacks, 128);
}
 
struct sigaction sa;
sigfillset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = _callstack_signal_handler;
sigaction(SIGPROF, &sa, NULL);

Операции, которые являются безопасными, при работе с сигналами довольно ограничены. Выделение памяти, например, не являются безопасной операцией, поэтому единственное, что мы делаем в обработчике сигнала — это захватываем текущую трассировку стека.

Срабатывание сигнала

image

После того, как сигнал установлен, нам нужен механизм, чтобы запустить сигнал. Это не может быть отправлено из главного потока, так как этот поток мы пытаемся отслеживать. GCD является отличной абстракцией для управления потоком исполнения. Тем не менее, источники отправки, стандартный механизм для поддержки блоков исполнения, будут выполняться с временным разрешением не чаще чем каждые 10 мс. NSThread предлагает необходимую детализацию с более высоким временным разрешением.

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

_trackerThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(_trackerLoop) object:nil];	
_trackerThread.threadPriority = 1.0;
[_trackerThread start];

Как это часто бывает с измерением производительности, акт измерения влияет на приложение и может иметь дополнительные последствия для производительности приложения. Захват трассировки на iPhone 4S занимает примерно 1 микросекунду, а когда у вас есть всего 16 миллисекунд, казалось бы, это совсем немного. Кроме того, акт приостановления основного потока (отправка сигнала) генерирует больше переключений контекста между потоками и может замедлить приложение в целом.

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

Отчетность и символизация

После того, как трассировка захвачена, мы собираем эти данные на устройстве и отправляем их на сервер в пакетном режиме. Трассировка, конечно же, является нечитаемой — коллекция адресов — и должна быть символизирована, для чего существует целый ряд инструментов. Apple, Atos API, Google Breakpad и atosl Facebook, вот несколько примеров. После символизации мы агрегируем стеки вызовов с помощью инструмента визуализации данных для идентификации частей системы, на которых сосредоточить наши усилия для предотвращения регрессии, поскольку мы продолжаем повышать эффективность работы нашей прокрутки.

Ниже приведен пример, показывающий потребление процессора двух версий приложения Facebook:

image

Попробуйте это

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


Читать еще

В портфолио компании EDISON Software есть 8 проектов, связанных с разработкой под Android и 4 крупных проекта, связанных с разработкой под iOS:

Автор: Edison

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js