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) и видите, что ваше устройство работает меньше или фиксирует более низкие температуры для поддержания этой частоты кадров, вы на правильном пути.

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

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

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

Доступ к 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.

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

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

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

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

Сократите количество операций по доступу к памяти
Streamline Performance Analyzer от Arm содержит множество информации счетчиков производительности, которую можно записать во время сеансов профилирования в реальном времени на целевом оборудовании Arm. Это отлично подходит для выявления проблем с производительностью, таких как насыщение пропускной способности памяти, возникающее из-за избыточного рисования.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Смотрите эти ресурсы, которые более подробно исследуют, как быть ограниченным ЦП или ГП:

Следуйте этой блок-схеме и используйте Профайлер, чтобы помочь определить, где сосредоточить усилия по оптимизации.
Следуйте этой блок-схеме и используйте Профайлер, чтобы помочь определить, где сосредоточить усилия по оптимизации.

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

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

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

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

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

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

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

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

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

Не превышает ли игра время, выделенное на кадр?
Это профиль игры, работающей комфортно в пределах ~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, маркер Profiler, который охватывает все выполнения метода MonoBehaviour.Update(), занимает 7.27 миллисекунд в этом кадре.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Привязка к процессору: Рабочие потоки
Проект на базе DOTS, сосредоточенный на симуляциях и привязанный к рабочим потокам.

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

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

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

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

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

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

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

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

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

Привязка к графическому процессору
Скриншот мобильной игры на графическом процессоре
Руководства по лучшим практикам Unity
Больше советов для разработчиков и создателей Unity

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

Рекомендации по профилированию производительности игр | Unity