Розганяємо продуктивність 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:




Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.