Профилирование CPU и GPU ресурсов игр
Когда игра «тупит», первый инстинкт — открыть Stats в Game View и смотреть на fps. Это бесполезно. Stats показывает усреднённое значение, не видит спайков, не разделяет CPU от GPU, не показывает, какой именно код съедает время. Для реальной диагностики нужен Profiler в режиме Standalone на целевом железе.
Разница между «42 fps в среднем» и «42 fps с просадками до 18 на каждом третьем кадре» — это разница между комфортной игрой и ощущением, что игра сломана. И это видно только через frame time graph, не через fps-счётчик.
CPU bottleneck vs GPU bottleneck — как различить
Первый вопрос при любой оптимизации: где узкое место. Если CPU тормозит → GPU ждёт. Если GPU тормозит → CPU ждёт. Смешивать методы оптимизации без понимания этого — трата времени.
Диагностика в Unity Profiler: открываем CPU Usage модуль, смотрим на Gfx.WaitForPresent или Graphics.PresentAndSync. Если эти маркеры занимают 8+ мс из 16.6 мс бюджета кадра — вы GPU-bound. CPU уже отдал всё GPU и просто ждёт.
Если же PlayerLoop, Physics.Processing или ваши скрипты занимают большую часть frame time, а Gfx.WaitForPresent минимален — вы CPU-bound.
Это принципиально разные пути оптимизации. GPU-bound: уменьшаем сложность шейдеров, overdraw, fill rate. CPU-bound: оптимизируем скрипты, используем Job System, сокращаем количество Update()-вызовов.
Глубокое профилирование CPU
Deep Profile в Unity — мощный инструмент, но с overhead'ом: он инструментирует каждый вызов метода, и сам по себе замедляет игру. Используем только для точечной диагностики конкретной подсистемы, не как постоянный режим.
Что ищем в CPU профиле:
Managed heap allocations в Update(). Coloured Marker в Profiler — GC.Alloc. Любая аллокация в горячем пути (Update, FixedUpdate, OnCollisionEnter) потенциально вызовет GC.Collect в будущем. GC.Collect на мобильных устройствах — это 2–20 мс spike. Исправляется через кэширование ссылок, object pools, string interning, замену LINQ на ручные циклы.
Physics.Processing занимает > 4 мс. Слишком сложные Collider'ы (Mesh Collider вместо Capsule), слишком маленький Fixed Timestep, слишком много Rigidbody с ContinuousCollisionDetection. Первым делом — Physics Debugger: visualize sleep state всех Rigidbody, искать те, что не спят без причины.
NavMesh.CalculatePath каждый кадр для 40 агентов. NavMeshAgent обновляется по умолчанию в каждом FixedUpdate. Для большого количества агентов — разбиваем на группы с обновлением через кадр или через N кадров в зависимости от дистанции до игрока.
Профилирование GPU
RenderDoc — обязательный инструмент для любого серьёзного GPU-профилирования. Подключается к Android/PC, делает capture одного кадра, показывает каждый draw call с временем на GPU, Input/Output текстуры, pipeline state. Именно здесь видно, какой шейдер съедает 60% GPU time.
Unity Frame Debugger — легче в использовании, но менее детальный. Показывает порядок отрисовки, почему объекты не батчатся, состояние render targets. Для первичной диагностики вполне достаточен.
На мобильных устройствах — ARM Streamline (Mali) или Snapdragon Profiler (Adreno). Они показывают метрики, недоступные в Unity: bandwidth memory, ALU utilization, texture cache miss rate. Именно texture cache miss (много маленьких текстур вместо атласа) или высокий bandwidth (текстуры без mipmaps) часто является настоящей причиной тормозов там, где Draw Calls казались в норме.
Реальный кейс: мобильный аркадный раннер, 45 fps на Snapdragon 730. CPU профиль — чисто, скрипты занимают < 3 мс. GPU — подозрительно много fill rate по данным Snapdragon Profiler. RenderDoc показал: кастомный distortion-шейдер на воде семплировал GrabPass (Screen Space Texture) на каждом кадре, плюс стоял в Transparent queue поверх трёх других слоёв с блендингом. Замена GrabPass на предзапечённую кубмап-текстуру для фоновых отражений + перенос water mesh ниже по Z-order убрали 11 мс с GPU time. Итог: 58–60 fps стабильные.
Процесс профилирования
Сначала определяем целевые метрики: fps бюджет (30/60/120), допустимый frame time (16.6/8.3 мс), платформа. Без целевых метрик непонятно, что считать «достаточно хорошим».
Профилируем в нескольких сценариях: idle (персонаж стоит), пиковая нагрузка (бой с максимальным количеством эффектов), переход между сценами. Каждый сценарий — отдельный Profiler capture.
Создаём отчёт с конкретными bottleneck'ами, их весом в ms и предложениями по устранению. Приоритизируем по соотношению усилий к приросту производительности.
| Масштаб задачи | Ориентировочные сроки |
|---|---|
| CPU/GPU профилирование + отчёт (1–2 сцены) | 2–4 дня |
| Глубокий аудит + исправление топ-3 bottleneck'ов | 1–2 недели |
| Комплексная оптимизация под конкретную платформу | 3–8 недель |
Стоимость определяется после изучения проекта и целевых платформ.





