Аудит продуктивності AR-застосунку: рендеринг і thermal
AR-застосунок на ARKit гріє iPhone до 45°C за 8 хвилин та розряджає батарею на 1% щохвилини. Frame rate — 45–50 FPS замість стабільних 60. Це не «трохи гальмує» — це продукт, яким неможливо користуватися. Оптимізація продуктивності AR-застосунку потребує системного підходу — не просто профілювання, а усунення першопричин теплового дроселювання та GPU overload. Ми спеціалізуємося на цьому: за 5 років роботи і 50+ проектів з ARKit та ARCore ми знаємо, де саме втрачається продуктивність і як це виправити. Наш аудит займає 2–3 дні й показує конкретні вузькі місця — без загальних рекомендацій.
Оптимізація AR — особлива дисципліна: не можна пожертвувати точністю трекінгу задля FPS, не можна вимкнути освітлення і втратити реалізм. Потрібно одночасно працювати з CPU (ARKit/ARCore-обробка), GPU (рендеринг) та Neural Engine (ML-моделі, де є). Правильний баланс між ними дає стабільні 60 FPS та температуру нижче 40°C навіть після 20 хвилин сесії.
Що входить в оптимізацію AR-продуктивності
- Профілювання сесії в Instruments та Android GPU Inspector — виявлення CPU, GPU та thermal-дроселювання.
- Аудит конфігурації ARKit/ARCore — відключення невикористовуваних фіч трекінгу та освітлення.
- Оптимізація 3D-активів: LOD, decimation, конвертація текстур у ASTC/ETC2.
- Перехід з SceneKit/ARSCNView на Metal (ARKit) або Filament (ARCore) там, де потрібен повний контроль рендерингу.
- Налаштування frustum culling та distance-based LOD для AR-сцен із кількома об'єктами.
- Тестування на фізичних пристроях API 26–34 (Android) та iOS 14–17.
- Гарантований вимірюваний результат або продовжуємо роботу безоплатно.
Де виникають проблеми: типові вузькі місця
| Проблема |
Причина |
Вплив |
| FPS 24–40 при 2+ об'єктах |
3D-моделі 800K–1.2M полігонів без LOD |
GPU overload |
| Нагрів до 45–50°C |
Thermal-дроселювання CPU+GPU |
Примусове зниження FPS |
| Батарея -1%/хв |
LightEstimationMode.ENVIRONMENTAL_HDR |
Фонова обробка HDR |
| Лаг при розміщенні |
Завантаження текстур 4096×4096 у main thread |
Пропуск кадрів |
Вузькі місця AR-застосунку рідко бувають одним — зазвичай 3–5 проблем одночасно. Профілювання виявляє всі відразу, і ми усуваємо їх у порядку пріоритету.
Як правильно налаштувати ARKit-сесію?
Перший крок — відключити функції, які не використовуються. ARWorldTrackingConfiguration з isAutoFocusEnabled = true та environmentTexturing = .automatic без реальної необхідності дають постійне навантаження на систему. Для більшості AR-застосунків достатньо planeDetection = [.horizontal] та isAutoFocusEnabled = false.
ARSCNView зручний, але для складних сцен MTKView + кастомний Metal-рендерер дає повний контроль над draw calls. Metal pipeline — faster than ARSCNView на сценах з 10+ AR-об'єктами: приріст FPS складає 20–30% за рахунок прямого управління draw calls та виключення SceneKit overhead.
SCNNode.isHidden = true для об'єктів поза полем зору — SceneKit не рендерить приховані ноди, але виконує physics та update. Правильніше — видаляти об'єкти зі сцени через node.removeFromParentNode().
Оптимізація ARCore (Android): конфігурація та Filament
LightEstimationMode.ENVIRONMENTAL_HDR — найдорожчий режим. На пристроях без Depth API (більшість mid-range до API 30) використовувати тільки якщо це ключова фіча. Відключення дає +15% ресурс батареї та знижує температуру на 3–5°C.
ARCore-застосунки з Filament рендерять PBR-матеріали через Vulkan на підтримуваних пристроях — помітно швидше, ніж через OpenGL ES. На пристроях з Vulkan (API 24+) різниця у frame time складає 8–12 мс.
planeFindingMode = Config.PlaneFindingMode.HORIZONTAL_ONLY замість HORIZONTAL_AND_VERTICAL дає 10–15% зниження навантаження на CPU для задач, де вертикальні поверхні не потрібні. Архітектурна особливість ARCore полягає у відокремленні підсистем комп'ютерного зору від координатного відстеження, що дозволяє незалежно масштабувати обчислювальне навантаження кожного компонента відповідно до поточних теплових обмежень апаратного забезпечення.
З нашої практики: AR-меблевий каталог
Наш клієнт — застосунок для перегляду меблів у AR. Дивани та столи — 3D-моделі від дизайнерів, по 800K–1.2M полігонів кожна. На iPhone 13 — 24 FPS при розміщенні 2 об'єктів. Проблема очевидна.
Що ми зробили: експорт моделей через Blender з decimation до 50K полігонів для AR-версії (втрата деталізації невидима з дистанції 1–2 метри на екрані телефону). Конвертація текстур з PNG 4096×4096 у ASTC 2048×2048. Додавання LOD: висока деталізація для об'єктів ближче 1.5 метра, середня — далі.
Результат: стабільні 58–60 FPS, температура нормалізувалась до 38°C, тривалість сесії без дроселювання зросла з 4 до 18 хвилин.
Наш процес оптимізації AR: покроково
- Профілювання сесії в Instruments (iOS) або Android GPU Inspector — виявляємо bottle neck: CPU, GPU, або thermal.
- Аналіз конфігурації ARKit/ARCore — відключаємо все невикористовуване.
- Аудит 3D-активів — decimation, LOD, ASTC/ETC2 текстури.
- Рефакторинг рендерингу — Metal або Filament замість SceneKit/ARSCNView де потрібно.
- Впровадження frustum culling та distance-based LOD.
- Тестування на пристроях різних класів: low-end (Snapdragon 660, A12) та flagship (S24, iPhone 15).
- Вимірювання результату в Instruments — порівнюємо frame time та thermal state до і після.
Які результати ви отримаєте після оптимізації?
Після нашої роботи застосунок показує результати краще ніж до оптимізації: стабільні 58–60 FPS замість 30–45, температура нижче 40°C навіть після 20 хвилин активної AR-сесії. Відсоток примусових пауз через thermal throttling падає з 40–60% до нуля на переважній більшості пристроїв. Наші клієнти відзначають зростання тривалості сесій на 30–50% після усунення thermal-проблем.
Моніторинг продуктивності AR у production
Після завершення оптимізації критично важливо налагодити безперервний моніторинг продуктивності у production середовищі, оскільки подальше додавання 3D-активів або оновлення бібліотек можуть повернути проблеми дроселювання. На iOS платформі ARSession надає frame.rawFeaturePoints для оцінки інтенсивності навантаженості підсистеми трекінгу, а MetricKit (iOS 13+) автоматично агрегує статистику середнього значення frame rate та градаційні стани теплового режиму процесора протягом усієї тривалості сесії.
На Android: Frame.getTimestamp() різниця між послідовними кадрами — якщо перевищує 33 мс, кадр пропущено і система перебуває у режимі примусового дроселювання продуктивності. Рекомендуємо встановити автоматичні сповіщення: якщо середній час рендерингу кадру перевищує 30 мс протягом більш ніж 10% загальної кількості кадрів у середині сесії — необхідне повторне профілювання та комплексна діагностика продуктивності. Зазвичай це сигналізує про появу нових 3D-активів без попередньої оптимізації або несанкціоновану зміну конфігурації AR-сесії.
Вартість оптимізації AR-продуктивності
| Обсяг роботи |
Вартість |
Строки |
| Аудит продуктивності (звіт + рекомендації) |
від 300 USD |
2–3 дні |
| Оптимізація однієї платформи (iOS або Android) |
від 700 USD |
5–10 днів |
| Обидві платформи під ключ |
від 1200 USD |
10–15 днів |
| Оптимізація 3D-активів (окремо) |
від 200 USD |
1–3 дні |
Apple Developer Documentation: "Use the Instruments app to identify performance bottlenecks in your AR experience." (developer.apple.com/documentation/arkit/)
Наші результати стабільніші, ніж у команд без досвіду AR-оптимізації, бо ми враховуємо thermal management, а не лише GPU metrics. Зв'яжіться з нами для безкоштовної оцінки застосунку — надішліть ІРА або APK, і ми скажемо де проблеми за 1 день.
Оптимізація мобільних додатків: cold start, пам'ять, батарея, FPS, профілювання
Ми стикаємося з додатками, де cold start триває 4+ секунди — користувачі йдуть до конкурентів ще до першого екрану. Android Vitals у Google Play Console безпосередньо впливають на ранжування, Apple аналогічно моніторить crash rate та час запуску через MetricKit. Оптимізація — це не «зробити швидше», а знайти конкретне місце втрати часу та усунути його. Наш досвід 7+ років та понад 50 оптимізованих застосунків дозволяє гарантувати результат: зменшення часу запуску на 30–60% при помірному навантаженні на команду.
Чому cold start критичний для бізнесу?
Cold start — запуск додатку, коли процес не існує в пам'яті. На Android це час від натискання на іконку до Activity.onWindowFocusChanged(hasFocus = true). На iOS — від tap до viewDidAppear першого екрану. При cold start більше 2 секунд 60% користувачів закривають додаток — це прямі втрати конверсії. Наприклад, у фінансовому додатку, який ми оптимізували, cold start зменшився з 4.3 с до 1.2 с, що дало +15% до добової активності.
Як виміряти час холодного старту?
Інструмент для діагностики: Android Studio Profiler → App Startup. Показує граф ініціалізації з часом кожного компонента. Альтернатива — Tracing.beginSection("MyInitTag") в коді + systrace.
Рішення: App Startup Library (Jetpack) з явним графом залежностей ініціалізаторів. Компоненти, потрібні лише в конкретних сценаріях, ініціалізуються ліниво — by lazy {} або initializer з флагом lazyInit. Firebase Analytics, наприклад, не потрібен до першої дії користувача — його ініціалізацію можна відкласти.
ContentProvider-и, що автоматично додаються SDK через AndroidManifest merge, теж запускаються при старті. tools:node="remove" в маніфесті дозволяє вимкнути конкретний провайдер та ініціалізувати SDK вручну в потрібний момент.
Ще одна грабля: Room.databaseBuilder().build() на main thread. Це синхронна операція створення/відкриття файлу БД — на повільних пристроях займає 50–300 мс. Переносимо в coroutine з Dispatchers.IO, в ViewModel через viewModelScope.launch.
На iOS cold start ділиться на pre-main (до виклику main()) та post-main. Pre-main — час завантаження dylib, rebase/binding, Objective-C runtime initialization та виконання +load методів.
Xcode Instruments → App Launch template показує час pre-main та post-main окремо. DYLD_PRINT_STATISTICS=1 в схемі запуску виводить детальний час завантаження в консоль.
Що вбиває pre-main:
- Багато динамічних бібліотек (кожна dylib — накладні витрати на лінковку). CocoaPods додає окрему dylib на кожен pod. Рішення: Swift Package Manager зі статичною лінковкою (
type: .static) або use_frameworks! :linkage => :static в CocoaPods — це зменшує pre-main час на 40% порівняно з динамічним лінкуванням.
-
+load методи в Objective-C — виконуються синхронно при завантаженні класу, до main(). Сторонні SDK можуть зловживати цим. +initialize — лінивий аналог, викликається при першому зверненні до класу.
Post-main — application(_:didFinishLaunchingWithOptions:). Та ж історія що на Android: синхронна ініціалізація всього. lazy var для сервісів, які не потрібні негайно. SwiftUI @StateObject ініціалізує об'єкт тільки коли View з'являється — це вже вбудована лінивість.
Цільові метрики (App Store рекомендації): cold start < 400 мс для простих додатків, < 2 секунди для складних. Warm start (процес в пам'яті, але Activity/Scene перестворюється) — < 1 секунда.
Пам'ять: витоки, OOM, excessive pressure
Витік пам'яті в iOS — retention cycle: об'єкт A тримає посилання на B, B тримає на A, жоден не звільняється. Класика: Timer з self в замиканні без [weak self]. Timer утримує замикання, замикання утримує self (ViewController), ViewController не звільняється при закритті. Instruments → Leaks або Memory Graph Debugger в Xcode — знаходить живі об'єкти, яких не повинно бути.
Що робити при витоках пам'яті?
На Android garbage collector керує пам'яттю, але витоки все одно трапляються. Activity або Fragment, що утримуються через статичне посилання, singleton, або Handler/Runnable після onDestroy — класика. LeakCanary — обов'язковий інструмент в debug-збірці. Додається однією залежністю debugImplementation "com.squareup.leakcanary:leakcanary-android" та автоматично детектує витоки з повним стектрейсом.
OutOfMemoryError найчастіше виникає через завантаження зображень. Bitmap в пам'яті займає ширина × висота × 4 байти. Зображення 4000×3000 px — 48 МБ в пам'яті, незалежно від розміру файлу на диску. Glide / Coil правильно обробляють це: завантажують з даунсемплінгом під розмір View, кешують в LRU-кеш. Завантажувати в ImageView через BitmapFactory.decodeFile — шлях до OOM на пристроях з 2 ГБ RAM. Використання Glide замість прямого декодування зменшує споживання пам'яті в 4 рази.
На Flutter Dart VM має свій GC, але нативні ресурси (зображення, текстури) не керуються Dart GC. Image.network кешує зображення в пам'яті без автоматичного звільнення при виході з дерева віджетів — при довгих списках з картинками використовуємо cached_network_image з правильним memCacheWidth/memCacheHeight.
FPS та UI Performance
60 FPS — 16.67 мс на кадр. 120 FPS (ProMotion) — 8.33 мс. Все що займає більше на main thread — джанк.
Типові причини просадок FPS:
На iOS: синхронне декодування зображень в cellForRowAt. Коли комірка таблиці з'являється, UIImage(contentsOfFile:) декодує JPEG/PNG на main thread — видно як загальмований скролл на довгих списках. Рішення: UIImage.preparingForDisplay() (iOS 15+) або ImageIO з kCGImageSourceCreateThumbnailWithTransform в background queue, результат через DispatchQueue.main.async.
На Android: RecyclerView.Adapter.onBindViewHolder з синхронними операціями. Бази даних, файлова система, синхронні мережеві запити на main thread — StrictMode.ThreadPolicy з detectAll().penaltyLog() в debug-збірці покаже всі порушення.
На Flutter: build() метод викликається часто, він має бути дешевим. setState() на верхньому віджеті перезбирає все дерево. const конструктори, RepaintBoundary, розбиття на дрібні віджети з локальним стейтом — основні інструменти. Flutter DevTools → Performance показує janky frames (червоні) з причинами.
Профілювання Compose: Recomposition Highlighter та трасування через Trace.beginSection в @Composable. remember для дорогих обчислень, derivedStateOf для computed values, LazyColumn замість Column + forEach для довгих списків.
Батарея: Wake locks, WorkManager, мережеві запити
Додаток у топі по витраті батареї — користувач бачить це в налаштуваннях і видаляє. Android Battery Historian (з ADB bug report) показує детальний timeline: wake locks, wakeups, network activity, sensor usage.
Основні споживачі енергії:
- Постійний GPS (розбираємо в maps-geo)
- Polling мережі кожні N секунд за代push
- Утримання wake lock довше необхідного
- Excessive
AlarmManager wakeups
WorkManager з Constraints — правильний спосіб планувати фонові задачі: setRequiredNetworkType, setRequiresBatteryNotLow, setRequiresCharging. ОС батчить задачі та виконує в зручний час.
На iOS BGTaskScheduler з BGProcessingTaskRequest (для важких задач при зарядці) та BGAppRefreshTaskRequest (для легких оновлень) — система вирішує коли виконувати, розробник тільки реєструє та реалізує логіку.
Батчинг мережевих запитів: замість 10 окремих запитів протягом хвилини — один батч запит. Менше радіо-активностей (LTE radio споживає багато при ініціалізації з'єднання), менше wakeups.
Інструменти профілювання
| Платформа |
Інструмент |
Що показує |
| iOS |
Xcode Instruments (Time Profiler) |
CPU, call stack, гарячі методи |
| iOS |
Allocations |
Живі об'єкти, піки пам'яті |
| iOS |
Leaks |
Retention cycles |
| iOS |
MetricKit |
Виробничі метрики (crash rate, hang rate, launch time) |
| Android |
Android Profiler |
CPU, Memory, Network, Energy |
| Android |
Systrace / Perfetto |
System-level трейси |
| Android |
LeakCanary |
Витоки пам'яті |
| Android |
Battery Historian |
Енергоспоживання |
| Flutter |
Flutter DevTools |
Recomposition, frame rendering, memory |
| Flutter |
Dart Observatory |
Dart VM profiling |
MetricKit на iOS — особливо цінний: реальні дані з пристроїв користувачів, а не симулятора. MXMetricManager отримує агреговані метрики раз на добу: MXAppLaunchMetric, MXHangDiagnostic, MXCPUExceptionDiagnostic. Діагностики по hang та CPU-exceptions містять стектрейс з реального пристрою — золото для діагностики production-проблем.
Типові проблеми та рішення
| Проблема |
Інструмент діагностики |
Рішення |
| Cold start > 3 с |
App Startup / DYLD_PRINT_STATISTICS |
Лінива ініціалізація, статичне лінкування |
| OOM на зображеннях |
LeakCanary / Allocations |
Glide/Coil з даунсемплінгом |
| FPS просадки при скролі |
Graphics (Xcode) / Profile GPU (Android) |
Фонове декодування, preparingForDisplay |
| Висока витрата батареї |
Battery Historian / Energy Log |
WorkManager, батч запитів, push замість polling |
Процес оптимізації
Починаємо з вимірювання, не з припущень. Інструменти вище дають цифри: конкретний час cold start, конкретний об'єм пам'яті, конкретні кадри з просіданням. Потім — пріоритизація по impact: що найбільше впливає на користувацький досвід саме в цьому додатку.
Аудит продуктивності існуючого додатку: 3–5 робочих днів. Реалізація оптимізацій — від тижня до двох місяців залежно від запущеності проблем та архітектури коду.
Що входить в роботу
- Детальний аудит продуктивності з профілюванням на реальних пристроях
- Звіт з виявленими проблемами та рекомендаціями (PDF/Notion)
- Код-рев'ю вузьких місць з пропозиціями змін
- Реалізація оптимізацій (cold start, пам'ять, UI, батарея)
- Повторне тестування для підтвердження покращень
- Документація змін та рекомендації щодо подальшої підтримки
Ми оптимізували 50+ мобільних застосунків, серед яких фінансові та соціальні мережі з аудиторією понад 10 млн користувачів. Гарантуємо зменшення часу запуску на 30–60% та підвищення FPS до стабільних 60.
Зв'яжіться з нами для безкоштовної оцінки вашого проекту — пропишемо точний план та терміни. Замовте аудит продуктивності сьогодні та отримайте перші результати вже через 3 дні.