Профилирование CPU мобильного приложения (Instruments/Android Profiler)
Разработчик говорит «приложение тормозит при переходе между экранами». Это — не данные для работы. Данные — это «переход с HomeViewController на DetailViewController занимает 380 мс, из которых 240 мс уходит на viewDidLoad в DetailViewController, а в нём 200 мс — это синхронный NSJSONSerialization.jsonObject на main thread». Именно за такой точностью — к CPU-профилировщику.
Xcode Instruments: как получить реальные данные
Instruments запускается через Xcode → Product → Profile или ⌘I. Для CPU — используем Time Profiler (sampling-профилировщик, 1ms интервал по умолчанию) или CPU Profiler (instrumentation-based, точнее но с overhead).
Time Profiler — первый выбор для большинства задач. Показывает call tree с временем выполнения каждого метода. Критичные настройки:
- Call Tree Options → Hide System Libraries — убираем шум от системных фреймворков, видим только свой код
- Separate by Thread — понимаем, на каком потоке тормозит
- Invert Call Tree — показывает «листья» дерева вызовов, то есть методы, где реально тратится время
Типичный сценарий: записываем 10 секунд работы приложения, открываем call tree. [MyImageProcessor processImage:] занимает 67% CPU. Раскрываем — там vImageScale_ARGB8888 вызывается на main thread из didSelectRowAt. Выносим в DispatchQueue.global(qos: .userInitiated), результат применяем через DispatchQueue.main.async — проблема решена.
Signposts и os_log для точного измерения
Системный профилировщик имеет overhead и шум. Для точного замера конкретной операции — os_signpost:
import os.signpost
let log = OSLog(subsystem: "com.app", category: "Performance")
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: "Image Processing", signpostID: id)
processImage(data)
os_signpost(.end, log: log, name: "Image Processing", signpostID: id)
В Instruments → Logging track видим точные временные метки. Это позволяет измерять не «где тормозит в целом», а «сколько конкретно занимает эта операция при разных входных данных».
Flame graph и где его читать
В Xcode 14+ Time Profiler показывает flame graph. Широкие горизонтальные прямоугольники — методы, потребляющие много времени. Вложенность показывает стек вызовов. Главное правило: смотреть на «плато» — широкие блоки без дочерних методов. Это «дно» стека, где реально расходуется время.
Android Studio Profiler: CPU
Android Studio CPU Profiler поддерживает три режима:
| Режим | Когда использовать | Overhead |
|---|---|---|
| Sample Java/Kotlin Methods | Первичная диагностика | Низкий |
| Trace Java/Kotlin Methods | Точный анализ, нужен полный стек | Высокий |
| Sample C/C++ Functions | Native код, NDK | Низкий |
| System Trace | Системные события, janky frames | Минимальный |
System Trace — наиболее информативный для анализа jank. Показывает Choreographer#doFrame, RenderThread, hwuiTask, binder-вызовы. Видно конкретный кадр, который задержался, и почему.
Запись через UI или программно:
Debug.startMethodTracing("myapp_trace")
// операция
Debug.stopMethodTracing()
Файл .trace открывается в Android Studio Profiler для анализа.
Типичная находка на Android
Профилирование показало: при открытии экрана чата 180 мс уходит на SharedPreferences.getAll() — разработчик загружал все настройки при каждом открытии для проверки флага. SharedPreferences на главном потоке с большим файлом (2 MB из-за кэшированных данных) — реальный блокировщик UI. Переход на DataStore с фоновым чтением через Flow убрал эту задержку полностью.
Что мы делаем в рамках услуги
- Записываем Instruments / Android Profiler сессии для ключевых пользовательских сценариев
- Анализируем call tree и flame graph, выявляем top-3 узких мест по времени CPU
- Добавляем
os_signpost/Traceмаркеры для точного измерения критичных операций - Устраняем найденные проблемы: выносим на фоновые потоки, оптимизируем алгоритмы, добавляем кэш
- Повторное профилирование для подтверждения улучшения
Сроки
Профилирование и анализ — 1–2 дня. Устранение найденных проблем — зависит от количества и сложности: от 2 дней до 2 недель. Оцениваем после анализа.







