Site icon AppTractor

Профилирование SwiftUI с помощью Instruments: 100 000 событий календаря

Мы загрузили 100 000 событий в локальную базу hora Calendar, чтобы посмотреть, как приложение справится с календарём, намного превышающим по размеру типичное использование.

Текущая версия приложения ощущается плавной при наших ручных проверках. В более ранних сборках при навигации по большим календарям были заметные паузы. Чтобы перейти от одного состояния к другому, потребовалось несколько раундов профилирования SwiftUI через Instruments, изменения в путях работы с данными и рендеринга, а также одно неожиданно простое решение: удалить визуальный hover-эффект у блоков событий.

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

Что на самом деле означает наш тест со 100 000 событий

У нас есть отдельный режим для больших наборов данных, который может сгенерировать 30 000, 50 000 или 100 000 событий в изолированном локальном хранилище.

Календарь по-прежнему загружает только нужный диапазон дат и отображает соответствующие события. Мы тестируем большую базу данных и насыщенные представления, а не пытаемся одновременно нарисовать 100 000 блоков событий.

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

hora Calendar отображает диапазон дат из изолированного тестового хранилища со 100 000 событий.

Если интересна предыстория продукта, я писал о том, почему создал нативное приложение Google Calendar для Mac.

Что показал Instruments

Одна из ранних диагностических записей длилась 39,1 секунды и содержала 11 зависаний или микрозависаний при пороге 250 мс, а также 243 зарегистрированных рывка.

Самое длинное зависание длилось около 762 мс. В этот период примерно 577 мс выборок работы главного потока приходились на обновления графа SwiftUI. Стэки SQL-запросов на главном потоке заняли лишь около 0,5 мс выборок.

Эти цифры описывают одну конкретную запись. Это не среднее время запросов и не контролируемый бенчмарк до и после. Запись выполнялась в Debug с включённым Layout Updates, что добавляло диагностические накладные расходы.

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

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

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

Почему мы убрали hover-эффект у событий

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

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

Мы меняли это поэтапно.

Сначала плитки событий начали получать снепшот значений вместо прямого чтения данных для отображения из persistence-моделей. Затем отслеживание указателя было перенесено на уровень колонки дня, а более тяжёлые модификаторы взаимодействия монтировались только для активного события. Неизменившиеся плитки также получили возможность пропускать лишнюю работу через явное сравнение на равенство.

В итоге мы полностью убрали визуальный hover-эффект из общего модификатора всплывающего окна события и дополнительный эффект увеличения яркости для блоков событий с заданным временем.

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

Это было специфично именно для нашей реализации. Hover callback сам по себе вполне может быть нормальным решением. Важно, какие состояние, зависимости и объём работы с ним связаны.

Другие изменения, которые помогли

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

Чтение календарных данных и подготовка представления были перенесены в фоновые снепшоты. Они включают группировку по дням, колонки пересечений и другие значения, необходимые интерфейсу.

Мы сузили отображаемый viewport и переиспользовали его выбор для плиток и доступности. Layout видимых плиток стал проще, а часовая сетка была перенесена в один Canvas на каждую колонку дня.

Навигация тоже потребовала внимания. Кэш теперь сохраняется при переключении между Day, Week и Month и подготавливает соседние диапазоны после попадания в кэш. Мы также убрали фиксированные задержки из путей foreground и prefetch.

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

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

У Instruments тоже оказались проблемы с производительностью

Инструмент профилирования сам оказался довольно требовательным.

На нашем MacBook Pro с Apple M5 и 16 ГБ RAM записи длиннее примерно 50 секунд могли оставлять Instruments обрабатывать данные около часа, после чего приложение падало.

Это наш опыт с конкретными записями, а не общее ограничение длительности. Параметры записи имели значение: Layout Updates показывал полезные детали, но создавал огромное количество данных. Один необработанный экспорт таблицы layout уже превысил 1,9 ГБ после покрытия всего около 3,4 секунды записи, поэтому мы остановили экспорт.

Xcode 27 стал первой версией, в которой мы смогли получить пригодное для этого расследования профилирование SwiftUI. Более ранние попытки не давали нам достаточно надёжного представления о проблеме.

Apple представила новый инструмент SwiftUI ещё в Xcode 26. Введение Apple в SwiftUI instrument посвящено именно этому релизу. Версия, в которой инструмент появился, и версия, в которой наше конкретное расследование наконец стало практически выполнимым, оказались разными.

Как я бы подходил к профилированию SwiftUI в следующий раз

Я бы начал с одного конкретного взаимодействия: прокрутки насыщенной недели, перехода между датами или переключения боковой панели.

Используйте Product > Profile в Xcode, выберите шаблон SwiftUI и запишите это взаимодействие. Проверьте Hangs и Hitches, затем изучите соответствующий интервал в SwiftUI и Time Profiler. Используйте граф Cause & Effect, когда нужно понять, почему именно обновляются представления. Инструкция Apple по профилированию объясняет этот рабочий процесс.

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

Похожий урок я получил во время исправления переключения оформления SwiftUI в macOS: даже убедительное первое объяснение всё равно нужно подтверждать данными из реальной иерархии представлений.

Где hora находится сейчас

В результате этой работы мы получили лучше разделённые пути SQL и SwiftData, меньше повторяющейся работы по подготовке представления и календарь, который в наших текущих ручных тестах ощущается намного плавнее, включая сценарий со 100 000 событий.

В повседневном использовании мы больше не чувствуем, что hora уступает Fantastical или BusyCal по отзывчивости. Это наша субъективная оценка при использовании приложений, а не сравнительный benchmark.

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

Если вы пользуетесь Google Calendar и хотите попробовать результат, скачайте hora Calendar для Mac.

Источник

Exit mobile version