Контроль критических операций с os_signpost и xctrace
Локальный замер в Instruments может показывать, что «первый экран открывается примерно за секунду», тогда как объединенная версия при удаленном тестировании работает заметно медленнее. Обычно проблема не в инструменте, а в отсутствии четких границ измеряемой операции: ожидание сети, разбор JSON, запись в базу данных и обновление UI объединяются в одно общее время. Стоит немного измениться состоянию машины — и результаты уже нельзя корректно сравнивать. Более надежный подход — обозначить бизнес-интервалы с помощью os_signpost, а затем многократно записать один и тот же сценарий через xctrace на облачном Mac.
Сначала определите воспроизводимый критический путь
Не начинайте с вопроса «насколько быстро работает все приложение». Сначала выберите пользовательское действие со стабильными начальной и конечной точками. Например, после перехода на страницу заказа можно измерить интервал от начала чтения локального кеша до завершения привязки данных к первой группе ячеек списка. Разделите этот путь на три интервала:
| Интервал | Начало | Конец | Подходит для контроля |
|---|---|---|---|
load_cache |
Запуск чтения кеша | Возврат структурированных данных | Да |
decode_payload |
Начало декодирования | Создание объектов предметной области | Да |
render_first_batch |
Передача первой группы моделей | Завершение компоновки первой группы представлений | Да |
wait_remote_api |
Отправка внешнего запроса | Получение ответа | Только для диагностики |
Время внешнего сетевого запроса зависит от маршрута и состояния сервера, поэтому не стоит использовать его для прямой блокировки слияния. Контроль производительности должен в первую очередь охватывать операции внутри процесса с фиксированными входными данными, которые можно воспроизводить многократно.
Один интервал должен отвечать только на один вопрос. Если одна метка одновременно охватывает сеть, диск и отрисовку, то после превышения порога все равно будет непонятно, что именно нужно исправлять.
Добавьте стабильные и однозначно сопоставляемые метки
Используйте постоянные subsystem и category, а также не добавляйте пользовательские данные к именам интервалов. При параллельных измерениях создавайте отдельный OSSignpostID для каждого бизнес-действия, чтобы начальные и конечные точки соседних задач не были ошибочно сопоставлены друг с другом.
import os
enum PerfTrace {
static let log = OSLog(
subsystem: "com.example.mobile",
category: "CriticalPath"
)
static func begin(_ name: StaticString) -> OSSignpostID {
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: name, signpostID: id)
return id
}
static func end(_ name: StaticString, id: OSSignpostID) {
os_signpost(.end, log: log, name: name, signpostID: id)
}
}
В вызывающем коде обязательно закрывайте интервал через defer. Иначе при возникновении исключения останется интервал с начальной меткой, но без конечной:
let traceID = PerfTrace.begin("decode_payload")
defer { PerfTrace.end("decode_payload", id: traceID) }
let models = try decoder.decode([Item].self, from: fixtureData)
Не записывайте в текст signpost токены, содержимое файлов или идентификаторы пользователей. Данные о производительности попадают в артефакты сборки, поэтому поля должны иметь низкую кардинальность и подходить для безопасного архивирования.
Зафиксируйте условия запуска xctrace
Сначала убедитесь в графическом интерфейсе, что имя шаблона указано правильно и нужные интервалы действительно отображаются. Только после этого переходите к командной строке. Тестовое приложение должно использовать фиксированную тестовую выборку данных, одну и ту же модель симулятора и одинаковую конфигурацию сборки. Перед записью завершайте старый процесс, чтобы состояние предыдущего запуска не влияло на результат.
set -euo pipefail
OUT="$PWD/perf-artifacts"
mkdir -p "$OUT"
xcrun xctrace record \
--template "Points of Interest" \
--time-limit 45s \
--output "$OUT/critical-path.trace" \
--launch -- "$APP_PATH" \
-PerfScenario first-batch \
-PerfFixture "$PWD/Fixtures/items.json"
xcrun xctrace export \
--input "$OUT/critical-path.trace" \
--toc > "$OUT/toc.xml"
XPath и структура таблиц, создаваемых xctrace export, могут меняться в зависимости от версии инструмента. Поэтому скрипт разбора должен сначала проверить наличие нужной schema и лишь затем считывать интервалы. Не используйте зависящий от позиций столбцов awk в качестве долгосрочного интерфейса. Предпочтительнее разбирать экспортированный XML, а при обновлении версии сохранять минимальный trace для контрактного тестирования.
При запуске в удаленной среде LemonVM сначала зафиксируйте вывод xcodebuild -version, версию системы, конфигурацию сборки и хеш тестовой выборки. После смены узла или набора инструментов необходимо построить новую базовую линию, а не сравнивать напрямую показатели из разных сред.
Постройте порог по нескольким сериям измерений
Один запуск легко искажается первоначальной загрузкой, фоновой индексацией и температурным режимом. Рекомендуется выполнить один прогревочный запуск, а затем провести как минимум пять полноценных измерений. При проверке следует сравнивать медианы, одновременно задавая более мягкий верхний предел для самого медленного результата.
Если медиана базовой линии равна 420 ms, порог предупреждения можно установить на уровне 1.15 от базового значения, то есть 483 ms. Однако не следует применять это соотношение ко всем интервалам. Короткая функция, выполняющаяся несколько десятков миллисекунд, и подготовка данных продолжительностью несколько секунд имеют разные модели разброса.
Скрипт проверки должен различать как минимум три типа результатов:
- Не удалось создать trace: сбой инфраструктуры, который не считается регрессией производительности.
- Целевой интервал отсутствует: ошибка сценария или инструментирования, требующая исправления теста.
- Валидные измерения превысили порог: контроль производительности не пройден, доказательства необходимо архивировать.
Файл базовой линии следует проверять вместе с кодом. Он должен содержать имя интервала, количество измерений, медиану, допустимый верхний предел, версию набора инструментов и хеш тестовой выборки. Причину любого повышения порога нужно указывать в описании коммита, чтобы ограничения не становились со временем только слабее.
Исключите распространенные ложные регрессии
Если медленным оказывается только первый запуск, а последующие выполняются стабильно, причиной обычно служит подготовка кеша или динамического связывания. Холодный запуск и прогретый путь в таком случае следует разделить на две группы. Если медленными являются все запуски, проверьте изменения в коде. Если аномалия возникла только в одном запуске, сначала выясните, не выполнялись ли в этот момент индексация, резервное копирование, дополнительный симулятор или параллельная сборка.
Также проверьте следующее:
- Данные Release и Debug нельзя смешивать.
- Для разных версий системы симулятора нужно поддерживать отдельные базовые линии.
- Перед каждым запуском теста необходимо восстанавливать одинаковое состояние бизнес-сценария.
- Задачи измерения производительности нельзя выполнять одновременно с крупными задачами сборки.
- Trace, результаты разбора и стандартный поток ошибок следует архивировать вместе.
- Если интервал не удалось извлечь, запрещено записывать значение
0 msпо умолчанию. - После завершения по тайм-ауту нужно остановить тестируемое приложение и оставшиеся процессы записи.
Если тест зависит от графического сеанса, перед началом задачи добавьте предварительную проверку его доступности. Если измеряется только разбор данных или слой хранения, по возможности используйте отдельную тестовую цель, чтобы снизить шум, связанный с состоянием интерфейса.
Сделайте результаты сбоев пригодными для немедленных действий
Результат проверки не должен ограничиваться сообщением «ошибка производительности». Выводите как минимум имя интервала, текущую медиану, базовое значение, процент изменения, количество валидных измерений и путь к trace. Например:
FAIL render_first_batch
median_ms=512
baseline_ms=420
change=21.9%
samples=7
trace=perf-artifacts/run-06.trace
Увидев результат, рецензент должен иметь возможность сразу скачать соответствующий trace, перейти к конкретному интервалу и определить, вызвана ли проблема регрессией бизнес-кода или аномалией среды измерения. Стабильный контроль производительности не требует, чтобы каждое измерение давало абсолютно одинаковое число. Его задача — сделать изменения при одинаковых входных данных, наборе инструментов и условиях запуска объяснимыми и воспроизводимыми. Эффективнее начать с одного важного пути, некоторое время наблюдать за его колебаниями и лишь затем постепенно добавлять новые интервалы, чем сразу вводить десятки ненадежных показателей.
Часто задаваемые вопросы
Что лучше использовать в пороге: среднее значение или медиану?
Используйте медиану нескольких корректных запусков и дополнительно ограничивайте высокий перцентиль либо максимальное значение. Среднее слишком чувствительно к единичным системным выбросам.
Следует ли считать ошибку записи xctrace регрессией производительности?
Нет. Ошибка записи, отсутствие интервала и превышение порога должны иметь разные коды завершения. Оценивать производительность можно только после получения корректного образца.
Можно ли проверять в CI все интервалы os_signpost?
Нет. Выбирайте операции со стабильными границами и воспроизводимым входом. Интервалы, зависящие от ручных действий или состояния внешнего сервиса, оставляйте для диагностики.
Выберите облачный Mac LemonVM под срок задачи
Lemon M4 и Lemon M4 Pro доступны в Сингапуре, Токио, Сеуле, Гонконге и на западе США с оплатой за день, неделю, месяц или квартал.