Hero background image
Советы по профилированию производительности для разработчиков игр

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

На этой странице описывается общий процесс профилирования для разработчиков игр. Это выдержка из электронной книги " Ultimate guide to profiling Unity games ", которую можно скачать бесплатно. Электронная книга была создана как внешними, так и внутренними экспертами Unity в области разработки, профилирования и оптимизации игр.

Читайте далее, чтобы узнать о полезных целях, которые можно поставить при профилировании, о распространенных узких местах производительности, таких как привязка к CPU или GPU, а также о том, как выявить и исследовать эти ситуации более подробно.

График частоты кадров в шутере от первого лица
Определите бюджет на рамы

Измерение частоты кадров в игре в кадрах в секунду (fps) не является идеальным для обеспечения стабильных впечатлений игроков. Рассмотрим следующий упрощенный сценарий:

Во время выполнения ваша игра делает 59 кадров за 0,75 секунды. Однако на рендеринг следующего кадра уходит 0,25 секунды. Средняя частота смены кадров в 60 fps звучит неплохо, но на деле игроки заметят эффект заикания, поскольку на рендеринг последнего кадра уходит четверть секунды.

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

Каждый кадр будет иметь временной бюджет, основанный на целевом fps. Приложение, нацеленное на 30 кадров в секунду, всегда должно занимать менее 33,33 мс на кадр (1000 мс / 30 кадров в секунду). Аналогично, при скорости 60 кадров в секунду на каждый кадр приходится 16,66 мс (1000 мс / 60 кадров в секунду).

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

Примечание: Постоянно высокая частота кадров в VR-играх необходима для того, чтобы не вызывать у игроков тошноту или дискомфорт. Без него вы рискуете быть отвергнутым платформодержателем во время сертификации вашей игры.

Кадры в секунду: Обманчивая метрика

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

Рассмотрим эти цифры:

1000 мс/с / 900 кадров в секунду = 1,111 мс на кадр
1000 мс/сек / 450 кадров в секунду = 2,222 мс на кадр

1000 мс/сек / 60 кадров в секунду = 16,666 мс на кадр
1000 мс/сек / 56,25 кадров в секунду = 17,777 мс на кадр

Если ваше приложение работает со скоростью 900 кадров в секунду, это означает, что время кадра составляет 1,111 миллисекунды на кадр. При скорости 450 кадров в секунду это составляет 2,222 миллисекунды на кадр. Разница составляет всего 1,111 миллисекунды на кадр, хотя частота кадров, казалось бы, упала в два раза.

Если посмотреть на разницу между 60 к/с и 56,25 к/с, то это означает 16,666 миллисекунды на кадр и 17,777 миллисекунды на кадр, соответственно. Это также составляет 1,111 миллисекунды на кадр, но здесь падение частоты кадров в процентном соотношении не столь ощутимо.

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

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

Подробнее об этом читайте в оригинальной статье "Robert Dunlop's fps versus frame time".

Задачи для мобильных разработчиков
Задачи для мобильных разработчиков

Тепловой контроль - одна из самых важных областей, которую необходимо оптимизировать при разработке приложений для мобильных устройств. Если CPU или GPU слишком долго работают на полную мощность из-за неэффективной конструкции, эти чипы будут нагреваться. Чтобы избежать повреждения микросхем (и потенциального ожога рук игрока!), операционная система снижает тактовую частоту устройства, чтобы дать ему остыть, что приводит к замиранию кадров и ухудшению пользовательского опыта. Такое снижение производительности известно как тепловое дросселирование.

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

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

Боритесь с тепловым дросселированием и разрядом батареи, используя технику раннего профилирования, чтобы оптимизировать игру с самого начала. Настройте параметры проекта для аппаратного обеспечения целевой платформы, чтобы бороться с проблемами тепловыделения и разряда батареи.

Настройте бюджеты кадров на мобильных устройствах

Для борьбы с тепловыми проблемами устройства при длительной игре обычно рекомендуется оставлять время простоя кадра около 35 %. Это дает мобильным чипам время остыть и помогает предотвратить чрезмерный разряд батареи. При целевом времени кадра 33,33 мс на кадр (для 30 кадров в секунду) типичный бюджет кадра для мобильных устройств составит примерно 22 мс на кадр.

Расчет выглядит следующим образом: (1000 мс / 30) * 0,65 = 21,66 мс

Для достижения 60 кадров в секунду на мобильном телефоне с помощью того же расчета потребуется целевое время кадра (1000 мс / 60) * 0,65 = 10,83 мс. Этого трудно добиться на многих мобильных устройствах, и батарея будет разряжаться в два раза быстрее, чем при съемке с частотой 30 кадров в секунду. По этим причинам большинство мобильных игр ориентируются на 30 кадров в секунду, а не на 60. Для управления этим параметром используйте Application.targetFrameRate, а для получения более подробной информации о времени кадра обратитесь к разделу "Установка бюджета кадра" в электронной книге.

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

Если вы не выходите за рамки общего бюджета времени, отведенного на съемку кадра (33,33 мс для 30 кадров в секунду), и видите, что ваше устройство работает меньше или регистрирует более низкие температуры для поддержания этой частоты кадров, значит, вы на правильном пути.

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

Сократите количество операций по доступу к памяти
Сократите количество операций по доступу к памяти

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

Сократите количество операций доступа к памяти на кадр:

  • Снижение частоты кадров
  • По возможности уменьшите разрешение дисплея
  • Использование более простых сеток с уменьшенным количеством вершин и точностью атрибутов
  • Использование сжатия текстур и mipmapping

Если вам нужно сосредоточиться на устройствах, использующих аппаратное обеспечение Arm или Arm Mali, инструментарий Arm Mobile Studio (в частности, Streamline Performance Analyzer) включает в себя несколько отличных счетчиков производительности для выявления проблем с пропускной способностью памяти. Счетчики перечислены и объяснены для каждого поколения GPU Arm, например Mali-G78. Обратите внимание, что для профилирования GPU в Mobile Studio требуется Arm Mali.

Установите уровни аппаратного обеспечения для бенчмаркинга

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

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

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

От высокоуровневого к низкоуровневому профилированию

При профилировании хорошо работает подход "сверху вниз", начиная с отключения функции Deep Profiling. Используйте этот высокоуровневый подход для сбора данных и заметок о том, какие сценарии вызывают нежелательное распределение управляемых ресурсов или слишком много процессорного времени в основных областях игрового цикла.

Сначала вам нужно собрать стеки вызовов для маркеров GC.Alloc. Если вы не знакомы с этим процессом, найдите несколько советов и подсказок в разделе "Определение повторяющихся выделений памяти в течение жизни приложения" в Ultimate guide to profiling Unity games.

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

Собирая заметки о "нарушителях" времени кадра, не забудьте отметить, как они соотносятся с остальной частью кадра. На это относительное влияние повлияет включение функции Deep Profiling.

Подробнее о глубоком профилировании в Окончательное руководство по профилированию игр Unity.

Профиль ранний

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

Занимайтесь профилированием часто и рано, чтобы вы и ваша команда поняли и запомнили "подпись производительности" для проекта. Если производительность снизится, вы сможете легко определить, когда что-то пошло не так, и устранить проблему.

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

Схема «Профилирование»
Выявляйте проблемы с производительностью

Скачать PDF-версию этой таблицы можно здесь.

На некоторых платформах определить, привязано ли ваше приложение к CPU или GPU, очень просто. Например, при запуске iOS-игры из Xcode на панели fps отображается гистограмма с общим временем работы CPU и GPU, чтобы вы могли понять, какое из них самое высокое. Время процессора включает в себя время ожидания VSync, которое всегда включено на мобильных устройствах.

Однако на некоторых платформах получить данные о синхронизации GPU может быть проблематично. К счастью, Unity Profiler показывает достаточно информации, чтобы определить местоположение узких мест в производительности. Приведенная выше блок-схема иллюстрирует процесс начального профилирования, а следующие за ней разделы содержат подробную информацию о каждом этапе. Они также представляют снимки Profiler из реальных проектов Unity, чтобы проиллюстрировать, на что следует обратить внимание.

Чтобы получить целостную картину всей активности CPU, включая время ожидания GPU, используйте представление Timeline в модуле CPU в Profiler. Ознакомьтесь с общими маркерами Profiler, чтобы правильно интерпретировать захват. Некоторые маркеры Profiler могут отображаться по-разному в зависимости от целевой платформы, поэтому потратьте время на изучение захватов вашей игры на каждой из целевых платформ, чтобы понять, как выглядит "нормальный" захват для вашего проекта.

Производительность проекта зависит от того, какой чип и/или поток занимает больше всего времени. Именно на этом направлении вам следует сосредоточить свои усилия по оптимизации. Например, представьте себе игру с целевым бюджетом времени кадра 33,33 мс и включенной VSync:

  • Если время кадра CPU (без учета VSync) составляет 25 мс, а время GPU - 20 мс, нет проблем! Вы упираетесь в CPU, но все в пределах бюджета, и оптимизация не улучшит частоту кадров (если только вы не опустите частоту CPU и GPU ниже 16,66 мс и не подскочите до 60 кадров в секунду).
  • Если время кадра CPU составляет 40 мс, а GPU - 20 мс, то вы привязаны к CPU и вам нужно оптимизировать производительность CPU. Оптимизация производительности GPU не поможет; более того, вы можете перенести часть работы CPU на GPU, например, используя вычислительные шейдеры вместо кода C# для некоторых вещей, чтобы сбалансировать ситуацию.
  • Если время кадра CPU составляет 20 мс, а GPU - 40 мс, вы привязаны к GPU и должны оптимизировать работу GPU.
  • Если CPU и GPU работают с частотой 40 мс, то вы связаны обеими, и для достижения 30 кадров в секунду вам придется оптимизировать обе частоты до 33,33 мс.

Ознакомьтесь с этими ресурсами, где подробно рассказывается о привязке к CPU или GPU:

Не превышает ли игра время, выделенное на кадр?
Не превышает ли игра время, выделенное на кадр?

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

Выше представлено изображение захвата Profiler из мобильной игры Unity, разработанной командой, которая постоянно проводила профилирование и оптимизацию. Игра поддерживает 60 кадров в секунду на мобильных телефонах с высоким разрешением и 30 кадров в секунду на телефонах со средним/низким разрешением, как, например, на этом снимке.

Обратите внимание, что почти половину времени на выбранном кадре занимает желтый маркер WaitForTargetfps Profiler. Приложение установило Application.targetFrameRate на 30 кадров в секунду, и VSync включен. Собственно обработка данных в главном потоке завершается примерно на отметке 19 мс, а остальное время уходит на ожидание оставшихся 33,33 мс перед началом следующего кадра. Хотя это время отображается маркером Profiler, основной поток CPU в это время по сути простаивает, позволяя процессору охлаждаться и расходуя минимум энергии батареи.

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

Время простоя отображается серыми или желтыми маркерами Profiler. На скриншоте выше видно, что поток рендеринга простаивает в Gfx.WaitForGfxCommandsFromMainThread, что указывает на время, когда он завершил отправку вызовов отрисовки на GPU на одном кадре и ждет новых запросов на отрисовку от CPU на следующем. Аналогично, хотя поток Job Worker 0 проводит некоторое время в Canvas.GeometryJob, большую часть времени он простаивает. Все это признаки приложения, которое вполне укладывается в рамки бюджета.

Если ваша игра в рамках бюджета

Если вы уложились в бюджет кадра, включая все корректировки, сделанные для учета расхода батареи и теплового дросселирования, вы закончили профилирование производительности до следующего раза - поздравляем. Запустите профилировщик памяти, чтобы убедиться, что приложение также не выходит за рамки своего бюджета памяти.

На изображении выше показано, что игра работает в пределах ~22 мс, необходимых для 30 кадров в секунду. Обратите внимание на подстановку WaitForTargetfps в основной поток, время до VSync и серые времена простоя в потоке рендеринга и рабочем потоке. Также обратите внимание, что интервал VBlank можно наблюдать, глядя на время окончания Gfx.Present по кадрам, и что вы можете нарисовать шкалу времени в области Timeline или на линейке Time вверху, чтобы измерить время от одного из них до другого.

Привязка к процессору
Привязка к процессору

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

Редко когда вся нагрузка на процессор оказывается узким местом. Современные процессоры имеют несколько различных ядер, способных выполнять работу независимо и одновременно. На каждом ядре процессора могут работать разные потоки. Полноценное приложение Unity использует множество потоков для различных целей, но чаще всего проблемы с производительностью возникают из-за потоков:

  • Главная нить: Именно здесь по умолчанию работает вся игровая логика/скрипты, и именно здесь проводится большая часть времени для таких функций и систем, как физика, анимация, пользовательский интерфейс и рендеринг.
  • Нить визуализации: В процессе рендеринга главный поток просматривает сцену и выполняет сортировку камеры, сортировку глубины и пакетную обработку вызовов рисования, в результате чего формируется список объектов для рендеринга. Этот список передается потоку рендеринга, который переводит его из внутреннего представления Unity, не зависящего от платформы, в конкретные вызовы графического API, необходимые для работы с GPU на конкретной платформе.
  • Потоки рабочих заданий: Разработчики могут использовать систему заданий C# для планирования выполнения определенных видов работ в рабочих потоках, что снижает нагрузку на основной поток. Некоторые системы и функции Unity также используют систему заданий, например, физика, анимация и рендеринг.

Основная нить

На рисунке выше показано, как все может выглядеть в проекте, который связан с главным потоком. Этот проект работает на Meta Quest 2, который обычно нацелен на частоту кадров 13,88 мс (72 к/с) или даже 8,33 мс (120 к/с), поскольку высокая частота кадров важна для предотвращения укачивания в VR-устройствах. Однако даже если бы игра была нацелена на 30 fps, очевидно, что проект находится в затруднительном положении.

Хотя поток рендеринга и рабочие потоки выглядят так же, как в примере, который находится в пределах бюджета кадра, основной поток явно занят работой на протяжении всего кадра. Даже с учетом небольшого количества накладных расходов Profiler в конце кадра, основной поток занят более 45 мс, что означает, что этот проект достигает частоты кадров менее 22 fps. Нет никаких маркеров, показывающих, что главный поток бездействует в ожидании VSync; он занят в течение всего кадра.

Следующий этап исследования - определить части кадра, которые занимают больше всего времени, и понять, почему так происходит. На этом кадре PostLateUpdate.FinishFrameRendering занимает 16,23 мс, что больше, чем весь бюджет кадра. При ближайшем рассмотрении обнаруживается, что существует пять экземпляров маркера под названием Inl_RenderCameraStack, что указывает на наличие пяти камер, активных для рендеринга сцены. Поскольку каждая камера в Unity задействует весь конвейер рендеринга, включая выборку, сортировку и пакетную обработку, наиболее приоритетной задачей для этого проекта является сокращение числа активных камер, в идеале - до одной.

BehaviourUpdate, маркер, который охватывает все методы MonoBehaviour Update(), занимает 7,27 мс, а пурпурные участки временной шкалы показывают, где скрипты выделяют управляемую кучу памяти. Переключение в режим просмотра иерархии и фильтрация по вводу GC.Alloc в строке поиска показывает, что выделение этой памяти занимает около 0,33 мс в этом кадре. Однако это неточная оценка того, как распределение памяти влияет на производительность вашего процессора.

Маркеры GC.Alloc на самом деле не таймируются путем измерения времени от точки начала до точки конца. Для того чтобы их накладные расходы были небольшими, они записываются только как метка времени начала работы плюс размер выделенных средств. Профилировщик отводит на них минимальное количество времени, чтобы они были заметны. Само выделение может занять больше времени, особенно если нужно запросить у системы новый диапазон памяти. Чтобы увидеть влияние более наглядно, поместите маркеры Profiler вокруг кода, выполняющего выделение, а при глубоком профилировании промежутки между пурпурными образцами GC.Alloc в представлении Timeline дают некоторое представление о том, сколько времени они могли занять.

Кроме того, выделение новой памяти может оказывать негативное влияние на производительность, которое сложнее измерить и приписать напрямую:

  • Запрос новой памяти от системы может повлиять на бюджет энергопотребления мобильного устройства, что может привести к замедлению работы CPU или GPU.
  • Новая память, скорее всего, должна быть загружена в кэш L1 процессора и тем самым вытеснить существующие строки кэша.
  • Инкрементная или синхронная сборка мусора может быть запущена напрямую или с задержкой, когда свободное пространство в управляемой памяти будет превышено.

В начале кадра четыре экземпляра Physics.FixedUpdate составляют 4,57 мс. В дальнейшем LateBehaviourUpdate (вызов MonoBehaviour.LateUpdate()) занимает 4 мс, а на аниматоры приходится около 1 мс.

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

Следующие области часто являются плодотворными местами для оптимизации в проектах, привязанных к основному потоку:

  • Физика
  • Обновление скрипта MonoBehaviour
  • Распределение и/или сбор мусора
  • Отбраковка и рендеринг камеры
  • Плохая синхронизация вызовов розыгрыша
  • Обновление, компоновка и перестройка пользовательского интерфейса
  • Анимация

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

  • Для скриптов MonoBehaviour, которые работают долго, но не показывают, почему именно так, добавьте в код маркеры профилировщика или попробуйте глубокое профилирование, чтобы увидеть полный стек вызовов.
  • Для сценариев, выделяющих управляемую память, включите функцию Allocation Call Stacks, чтобы видеть, откуда именно происходит выделение. В качестве альтернативы включите Deep Profiling или используйте Project Auditor, который показывает проблемы кода, отфильтрованные по памяти, чтобы вы могли определить все строки кода, которые приводят к управляемым выделениям.
  • Используйте отладчик кадров, чтобы выяснить причины плохой синхронизации вызовов рисования.

Чтобы получить исчерпывающие советы по оптимизации игры, бесплатно скачайте эти руководства для экспертов по Unity:

Привязка к процессору Поток рендеринга
Привязка к процессору Поток рендеринга

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

Захват Profiler показывает, что перед началом рендеринга на текущем кадре основной поток ожидает поток рендеринга, о чем свидетельствует маркер Gfx.WaitForPresentOnGfxThread. Поток рендеринга все еще отправляет команды вызова рисования с предыдущего кадра и не готов принимать новые вызовы рисования от основного потока; поток рендеринга проводит время в Camera.Render.

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

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

Ниже перечислены распространенные причины, которые следует выяснить для проектов, оказывающихся связанными нитью:

  • Плохая синхронизация вызовов рисования, особенно в старых графических API, таких как OpenGL или DirectX 11.
  • Слишком много камер. Если вы не создаете многопользовательскую игру с разделенным экраном, то, скорее всего, у вас будет только одна активная камера.
  • Плохая сортировка, в результате чего нарисовано слишком много вещей. Исследуйте размеры окружности камеры и маски слоев. Рассмотрите возможность включения функции Occlusion Culling. Возможно, даже создайте свою собственную простую систему очистки от окклюзии, основанную на том, что вы знаете о расположении объектов в вашем мире. Посмотрите, сколько в сцене объектов, отбрасывающих тень, - отсеивание теней происходит в отдельном проходе от "обычного" отсеивания.

Модуль Rendering Profiler показывает обзор количества пакетов вызовов рисования и вызовов SetPass в каждом кадре. Лучшим инструментом для изучения того, какие партии вызовов рисования ваш поток рендеринга передает GPU, является Frame Debugger.

Привязка к процессору Рабочие потоки
Привязка к процессору Рабочие потоки

Проекты, привязанные к потокам процессора, отличным от основного или потока рендеринга, встречаются не так часто. Однако она может возникнуть, если в вашем проекте используется стек технологий, ориентированных на работу с данными (DOTS), особенно если работа перемещается из главного потока в рабочие потоки с помощью системы заданий C#.

Захват, показанный выше, сделан в режиме Play в редакторе и показывает проект DOTS, выполняющий симуляцию жидкости с частицами на процессоре.

На первый взгляд, это успех. Рабочие потоки плотно заполнены заданиями с Burst-компиляцией, что указывает на то, что большой объем работы был перенесен с основного потока. Как правило, это разумное решение.

Однако в данном случае время кадра 48,14 мс и серый маркер WaitForJobGroupID 35,57 мс в главном потоке - это признаки того, что не все в порядке. WaitForJobGroupID указывает на то, что главный поток запланировал задания для асинхронного выполнения в рабочих потоках, но ему нужны результаты этих заданий до того, как рабочие потоки завершат их выполнение. Синие маркеры Profiler под WaitForJobGroupID показывают, что главный поток выполняет задания во время ожидания, пытаясь обеспечить более быстрое завершение заданий.

Хотя задания скомпилированы в Burst, они все равно выполняют много работы. Возможно, структуру пространственных запросов, используемую в этом проекте для быстрого поиска частиц, расположенных близко друг к другу, следует оптимизировать или заменить на более эффективную структуру. Или же задания пространственных запросов можно запланировать на конец кадра, а не на его начало, при этом результаты не потребуются до начала следующего кадра. Возможно, этот проект пытается смоделировать слишком много частиц. Чтобы найти решение, требуется дальнейший анализ кода заданий, поэтому добавление более тонких маркеров Profiler поможет выявить самые медленные участки.

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

К распространенным причинам возникновения точек синхронизации и узких мест в рабочих потоках относятся:

  • Задания не компилируются компилятором Burst
  • Длительные задания выполняются в одном рабочем потоке, а не распараллеливаются по нескольким рабочим потокам.
  • Недостаточно времени между моментом, когда работа запланирована, и моментом, когда требуется результат
  • Несколько "точек синхронизации" в кадре, которые требуют немедленного завершения всех заданий

Вы можете использовать функцию Flow Events в представлении Timeline модуля CPU Usage Profiler для изучения того, когда задания запланированы и когда их результаты ожидаются главным потоком. Для получения дополнительной информации о написании эффективного кода DOTS см. Лучшие практики DOTS руководство.

Привязка к графическому процессору
Привязка к графическому процессору

Ваше приложение привязано к GPU, если основной поток проводит много времени в маркерах Profiler, таких как Gfx.WaitForPresentOnGfxThread, а ваш поток рендеринга одновременно отображает такие маркеры, как Gfx.PresentFrame или <GraphicsAPIName>.WaitForLastPresent.

Следующий снимок был сделан на Samsung Galaxy S7 с использованием графического API Vulkan. Хотя часть времени, проведенного в Gfx.PresentFrame в этом примере, может быть связана с ожиданием VSync, экстремальная длина этого маркера Profiler указывает на то, что большая часть этого времени потрачена на ожидание завершения рендеринга предыдущего кадра графическим процессором.

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

  • Дорогие полноэкранные эффекты постобработки, в том числе такие распространенные, как Ambient Occlusion и Bloom
  • Дорогие фрагментные шейдеры, вызванные:
  • Логика ветвления
  • Использование полной, а не половинной точности
  • Чрезмерное использование регистров, влияющих на заполняемость волнового фронта графических процессоров
  • Перерисовка в очереди прозрачного рендеринга, вызванная неэффективным пользовательским интерфейсом, системами частиц или эффектами постобработки
  • Чрезмерно высокое разрешение экрана, как, например, у дисплеев 4K или дисплеев Retina на мобильных устройствах
  • Микротреугольники, вызванные плотной геометрией сетки или отсутствием LOD, что является особой проблемой для мобильных GPU, но может также влиять на PC и консольные GPU
  • Пропуски кэша и нерациональное использование пропускной способности памяти GPU из-за несжатых текстур или текстур высокого разрешения без mipmaps
  • Шейдеры геометрии или тесселяции, которые могут запускаться несколько раз за кадр, если включены динамические тени

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

Лучший способ выяснить причину узких мест в работе GPU - изучить захват GPU с помощью подходящего профилировщика GPU. Какой инструмент вы используете, зависит от целевого оборудования и выбранного графического API.

ключ единства арт 21 11
Хотите узнать больше?

Бесплатно скачайте электронную книгу " Ultimate guide to profiling Unity games", чтобы получить все советы и лучшие практики.

Было ли это содержание полезным?