Hero background image

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

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

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

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

Эта страница описывает общий процесс профилирования для разработчиков игр. Он взят из электронной книги, Ультимативное руководство по профилированию игр на Unity, доступной для бесплатной загрузки (версия руководства для Unity 6 будет доступна скоро). Электронная книга была создана как внешними, так и внутренними экспертами Unity в области разработки игр, профилирования и оптимизации.

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

График частоты кадров в шутере от первого лица

Установите бюджет кадров

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

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

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

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

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

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

Кадры в секунду: Обманчивый показатель

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

Рассмотрите эти числа:

1000 мс/сек / 900 fps = 1,111 мс на кадр

1000 мс/сек / 450 fps = 2.222 мс на кадр

1000 мс/сек / 60 fps = 16.666 мс на кадр

1000 мс/сек / 56.25 fps = 17.777 мс на кадр

Если ваше приложение работает на 900 fps, это соответствует времени кадра 1.111 миллисекунд на кадр. При 450 fps это 2.222 миллисекунды на кадр. Это представляет собой разницу всего в 1.111 миллисекунд на кадр, хотя частота кадров, кажется, падает вдвое.

Если вы посмотрите на разницу между 60 fps и 56.25 fps, это соответствует 16.666 миллисекундам на кадр и 17.777 миллисекундам на кадр соответственно. Это также представляет собой 1.111 миллисекунд дополнительно на кадр, но здесь падение частоты кадров ощущается гораздо менее драматично в процентном соотношении.

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

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

Прочитайте оригинальную статью, "Фпс Роберта Данлопа против времени кадра", для получения дополнительной информации.

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

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

Тепловое управление является одной из самых важных областей для оптимизации при разработке приложений для мобильных устройств. Если ЦП или ГПУ слишком долго работают на полную мощность из-за неэффективного кода, эти чипы будут нагреваться. Чтобы избежать перегрева и потенциального повреждения чипов, операционная система снизит тактовую частоту устройства, чтобы дать ему остыть, что приведет к заиканию кадров и плохому пользовательскому опыту. Это снижение производительности известно как тепловое троттлирование.

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

При решении проблемы тепла учитывайте бюджет, с которым вы работаете, как системный бюджет.

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

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

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

Расчет выглядит так: (1000 мс / 30) * 0.65 = 21.66 мс

Чтобы достичь 60 fps на мобильных устройствах, используя тот же расчет, потребуется целевое время кадра (1000 мс / 60) * 0.65 = 10.83 мс. Это трудно достичь на многих мобильных устройствах и будет разряжать батарею в два раза быстрее, чем при целевом значении 30 fps. По этим причинам многие мобильные игры нацелены на 30 fps, а не на 60. Используйте Application.targetFrameRate, чтобы контролировать эту настройку, и обратитесь к разделу "Установить бюджет кадров" в электронной книге по профилированию для получения дополнительных сведений о времени кадра.

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

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

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

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

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

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

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

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

Когда вам нужно сосредоточиться на устройствах, использующих аппаратное обеспечение Arm CPU или GPU, инструменты Arm Performance Studio (в частности, Streamline Performance Analyzer) включают отличные счетчики производительности для выявления проблем с пропускной способностью памяти. Доступные счетчики перечислены и объяснены для каждого поколения Arm GPU в соответствующем руководстве пользователя, например, Mali-G710 Performance Counter Reference Guide . Обратите внимание, что профилирование GPU в Arm Performance Studio требует наличия GPU Arm Immortalis или Mali.

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

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

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

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

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

При профилировании вы хотите убедиться, что сосредоточили свое время и усилия на областях, где вы можете создать наибольшее влияние. Поэтому рекомендуется начинать с подхода «сверху вниз» при профилировании, что означает, что вы начинаете с общего обзора категорий, таких как рендеринг, скрипты, физика и распределения сборки мусора (GC). Как только вы определили проблемные области, вы можете углубиться в более детальные детали. Используйте этот общий проход для сбора данных и записи наиболее критических проблем с производительностью, включая сценарии, которые вызывают нежелательные управляемые распределения или чрезмерное использование CPU в вашем основном игровом цикле.

Сначала вам нужно собрать стеки вызовов для маркеров GC.Alloc. Если вы не знакомы с этим процессом, найдите несколько советов и приемов в разделе под названием "Поиск повторяющихся распределений памяти на протяжении жизненного цикла приложения" в электронной книге.

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

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

Профилируйте рано

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

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

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

Схема «Профилирование»

Выявляйте проблемы с производительностью

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

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

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

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

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

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

Смотрите эти ресурсы, которые более подробно исследуют, ограничены ли вы по CPU или GPU:

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

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

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

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

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

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

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

Если ваша игра укладывается в бюджет кадров

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

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

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

Зависимость от ЦП

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

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

  • Основной поток: Здесь по умолчанию выполняется большая часть логики/скриптов игры. Большинство систем Unity, таких как физика, анимация, пользовательский интерфейс и начальные этапы рендеринга, выполняются здесь.
  • Поток рендеринга: Это обрабатывает подготовительную работу (например, какие объекты в сцене видимы для камеры и какие исключены/невидимы, потому что они находятся за пределами фрустрации обзора, закрыты или отсеяны по другим критериям), которая должна происходить перед отправкой инструкций рендеринга на GPU.
  • Во время процесса рендеринга основной поток проверяет сцену и выполняет отсечение камеры, сортировку по глубине и пакетирование вызовов отрисовки, в результате чего получается список объектов для рендеринга. Этот список передается в поток рендеринга, который переводит его из внутреннего платформонезависимого представления Unity в конкретные вызовы графического API, необходимые для управления GPU на конкретной платформе.
  • Потоки рабочих задач: Разработчики могут использовать систему задач для планирования определенных видов работы, выполняемой на рабочих потоках, что снижает нагрузку на основной поток. Некоторые системы и функции Unity также используют систему задач, такие как физика, анимация и рендеринг.

Реальный пример оптимизации основного потока

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

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

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

BehaviourUpdate, маркер профайлера, который охватывает все выполнения метода MonoBehaviour.Update(), занимает 7,27 миллисекунд в этом кадре.

В представлении Timeline секции магентового цвета указывают на точки, где скрипты выделяют управляемую кучу памяти. Переключение на представление Hierarchy и фильтрация, вводя GC.Alloc в строку поиска, показывает, что выделение этой памяти занимает около 0.33 мс в этом кадре. Тем не менее, это неточное измерение влияния выделений памяти на производительность вашего ЦП.

Маркеры GC.Alloc не временные, как типичные образцы профилировщика, записывая начальную и конечную точки. Чтобы минимизировать их накладные расходы, Unity записывает только временную метку выделения и выделенный размер.

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

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

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

В начале кадра четыре экземпляра Physics.FixedUpdate составляют 4.57 мс. Позже LateBehaviourUpdate (вызовы MonoBehaviour.LateUpdate()) занимают 4 мс, а Animators составляют около 1 мс. Чтобы гарантировать, что этот проект достигает желаемого бюджета и скорости кадров, все эти проблемы основного потока необходимо исследовать для поиска подходящих оптимизаций.

Общие ловушки для узких мест основного потока

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

  • Физические расчеты
  • Обновления скриптов MonoBehaviour
  • Выделение и/или сборка мусора
  • Кулинг камеры и рендеринг в основном потоке
  • Неэффективная пакетная отрисовка вызовов
  • Обновления UI, макеты и перестройки
  • Анимация

Прочитайте наши руководства по оптимизации, которые предлагают длинный список практических советов по оптимизации некоторых из самых распространенных проблем:

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

  • Для скриптов MonoBehaviour, которые занимают много времени, но не показывают, почему это так, добавьте Маркировки профилировщика в код или попробуйте глубокую профилировку, чтобы увидеть полный стек вызовов.
  • Для скриптов, которые выделяют управляемую память, включите Стек вызовов выделения, чтобы увидеть, откуда именно происходят выделения. В качестве альтернативы, включите глубокую профилировку или используйте Аудитор проекта, который показывает проблемы с кодом, отфильтрованные по памяти, чтобы вы могли определить все строки кода, которые приводят к управляемым выделениям.
  • Используйте Отладчик кадров, чтобы исследовать причины плохой пакетной отрисовки вызовов.

Для комплексных советов по оптимизации вашей игры, скачайте эти руководства от экспертов Unity бесплатно:

Зависимость от ЦП: Поток рендеринга

Зависимость от ЦП: Поток рендеринга

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

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

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

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

Общие ошибки для узких мест потока рендеринга

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

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

Модуль Профайлер рендеринга показывает обзор количества пакетных вызовов отрисовки и вызовов SetPass за каждый кадр. Лучший инструмент для изучения того, какие пакетные вызовы отрисовки ваш поток рендеринга отправляет на GPU, - это Отладчик кадров.

Инструменты для решения выявленных узких мест

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

  • Пакетирование SRP снижает нагрузку на ЦП, постоянно храня данные материалов в памяти GPU. Хотя это не уменьшает фактическое количество вызовов отрисовки, это делает каждый вызов отрисовки дешевле.
  • Инстансирование GPU объединяет несколько экземпляров одной и той же сетки, использующей один и тот же материал, в один вызов отрисовки.
  • Статическое пакетирование объединяет статические (неподвижные) сетки, использующие один и тот же материал, и, таким образом, может дать вам преимущества при работе с дизайном уровней, содержащим множество статических элементов.
  • GPU resident drawer автоматически использует инстансирование GPU для снижения нагрузки на ЦП и вызовов отрисовки, группируя похожие GameObjects вместе.
  • Dynamic Batching объединяет небольшие меши во время выполнения, что может быть преимуществом на старых мобильных устройствах с высокими затратами на вызовы отрисовки. Однако недостатком является то, что преобразование вершин также может быть ресурсоемким.
  • GPU occlusion culling использует вычислительные шейдеры для определения видимости объектов, сравнивая буферы глубины из текущих и предыдущих кадров, уменьшая ненужную отрисовку закрытых объектов без необходимости в предварительно подготовленных данных.

Кроме того, на стороне ЦП можно использовать такие техники, как Camera.layerCullDistances, чтобы уменьшить количество объектов, отправляемых в поток отрисовки, отсекая объекты на основе их расстояния от камеры, что помогает облегчить узкие места ЦП во время отсечения камеры.

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

Зависимость от ЦП: Рабочие потоки

Зависимость от ЦП: Рабочие потоки

Проекты, ограниченные потоками ЦП, отличными от основных или потоков отрисовки, не так уж и распространены. Однако это может возникнуть, если ваш проект использует Data-Oriented Technology Stack (DOTS), особенно если работа перемещается с основного потока в рабочие потоки с использованием job system.

Изображение выше - это захват из режима воспроизведения в редакторе, показывающий проект DOTS, выполняющий симуляцию жидкостных частиц на ЦП.

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

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

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

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

Распространенные ловушки для узких мест рабочих потоков

Распространенные причины синхронных точек и узких мест рабочих потоков включают:

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

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

Для получения дополнительной информации о написании эффективного кода DOTS смотрите руководство Лучшие практики DOTS.

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

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

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

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

API FrameTimingManager может быть полезен в таких случаях, предоставляя низконагруженные, высокоуровневые времена кадров как на ЦП, так и на GPU.

Вышеуказанный захват был сделан на мобильном телефоне Android с использованием графического API Vulkan. Хотя часть времени, проведенного в Gfx.PresentFrame в этом примере, может быть связана с ожиданием VSync, экстремальная длина этого маркера профилировщика указывает на то, что большинство этого времени тратится на ожидание завершения рендеринга предыдущего кадра GPU.

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

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

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

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

Руководства по лучшим практикам Unity
Хотите узнать больше?

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

Лучшие практики для профилирования производительности игры | Unity