[Перевод] Оптимизация игр на Unity: проверенный в деле план

image-loader.svg

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

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

Распространенные ошибки

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

Аврал за несколько дней до дедлайна 

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

Отсутствие плана

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

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

Программисты порой оптимизируют отдельные куски кода — например, оптимизируют UI с помощью цикла foreach (с 10 мс до 3 мс). А художники рассчитывают полигоны. И то, и другое — улучшение, но зачастую эти действия не дают заметных результатов. Лучше сосредоточиться на опыте игрока, ведь в конечном итоге — это единственный важный результат.

Некорректные данные при включении GPU Profiler

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

Определить кто задерживает исполнение программы — GPU или CPU — можно, используя Timeline профайлера CPU:

  • Gfx.WaitForPresent: ограничения GPU, CPU ожидает ответа от GPU;

  • Gfx.WaitForCommands: ограничения CPU, GPU ожидает ответа от CPU.

«Сбор данных профайлера GPU может сильно перегружать систему.  Закройте эту графу, если эти данные вам не нужны», — я солидарен с Unity. «Сбор данных профайлера GPU может сильно перегружать систему. Закройте эту графу, если эти данные вам не нужны», — я солидарен с Unity. 

Некорректные данные при запуске Deep Profiling

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

Использование кастомных маркеров профиля:

UnityEngine.Profiling.Profiler.BeginSample("MyHeavyCode - Top");
..
UnityEngine.Profiling.Profiler.EndSample();

Некорректные данные из-за физики в FixedUpdate

Профилирование само по себе настолько загружает систему, что это может сильно повлиять на некоторые данные. Игра будет идти хуже, а значит, будет выполняться больше физики FixedUpdates. Даже если профайлер покажет, что на физику ушло 33% фреймрейта, по факту в итоговом билде это значение будет ближе к 10%. Так же как в случае с глубоким профилированием, эти некорректные данные могут сподвигнуть разработчиков заниматься оптимизацией не там, где это принесет значимый результат.

Сложности с сетевым решением

Программисты порой полагают, что сетевое решение (например, Photon) сильно снижает производительность. Они видят, что из-за него происходят пики загрузки ЦП, но забывают заглянуть поглубже в стек вызовов. Сетевой инструмент запускает методы, вызываемые из сети (так называемые RPC), а они — часть вашего собственного кода, которая не имеет к сети никакого отношения. В таких ситуациях нужно оптимизировать RPC-методы и/или распределить их рабочую нагрузку.

План оптимизации

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

Подготовка

1. Возьмите за ориентир самую слабую платформу.

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

2. Сделайте так, чтобы вам было удобно.

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

Общие рекомендации

1. Автоматизируйте билды, чтобы они собирались в один клик.

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

2. Ускорьте билды.

Обеспечьте возможность собирать более быстрые и менее объемные билды (например, с одним уровнем и одной машиной в гоночной игре).

3. Отключите обфускацию, если она используется.

Рекомендации по платформам

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

Ускорьте настройку компилятора Il2CPP

Используйте Release или даже Debug, если компиляция кода не занимает слишком много времени.

PlayerSettings.SetIl2CppCompilerConfiguration(group, mode); 
Il2CppCompilerConfiguration.Master //Slow build, Quick performance
Il2CppCompilerConfiguration.Release //Medium build time, Good runtime performance
Il2CppCompilerConfiguration.Debug //Quickest build, slowest runtime performance

Петля оптимизации

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

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

1. Документирование производительности

Фиксируйте производительность вашей игры, желательно на тех этапах, которые легко воспроизвести. Задокументируйте результаты и сохраните данные профилирования. Стоит записать уровень загрузки CPU (и GPU), чтобы оценить прогресс. Я часто также отслеживаю загрузку памяти, чтобы подготавливать эффективные новые билды и при необходимости сокращать объем используемой памяти.

Можно использовать Profile Analyzer: он упрощает сравнение данных в профиле. Это поможет обнаружить пики загрузки или другие отличия между разными билдами/конфигурациями настроек. То есть этот инструмент отмечает все произведенные улучшения.

Window > Analyse > Profile Analyzer» />Window > Analyse > Profile Analyzer</p>

<p>Profile Analyzer экономит много работы: выберите две области, и он автоматически сообщит, в чем разница. </p>

<h4>2. Базовая производительность</h4>

<p>Сначала мы обычно игнорируем пики загрузки и сосредотачиваемся на том, чтобы базовая производительность была в пределах нормы. Здесь главное довести «нормальный» игровой цикл до приемлемого уровня, будь то 30 кадров в секунду (мобильные платформы, Switch), 60 или даже 120 (VR).</p>

<p>Используйте профайлер Unity, чтобы понять, что снижает базовую производительность, и решить только самые важные проблемы. Timeline помогает увидеть, как работает игра с точки зрения производительности.</p>

<p><img src=Отладчик кадров Unity

Прочее

Помимо Occlusion Culling есть еще две настройки, которые стоит проверить перед использованием.

Graphic Jobs 

Нам пришлось отключить их на многих платформах из-за сбоев Unity (Particle jobs содержат ошибки). Как обычно: перед включением проверьте не страдает ли от этого производительность.

Динамический батчинг 

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

Нужны ли другие инструменты

Есть и другие инструменты, которые использовались раньше: от PIX (Xbox) до Intel VTune. Однако современный профайлер Unity предлагает все, что необходимо для внесения наиболее важных изменений. Для некоторых конкретных платформ можно использовать дополнительные инструменты (PIX, XCode, Android Studio и другие), чтобы упростить доступ к информации об устройстве. Но по моему опыту, встроенных инструментов Unity достаточно для выполнения оптимизации.

Отдельно про ограничение со стороны ЦП

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

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

Таск менеджер типичной игры на Unity с задержкой в CPU 3 (основной тред Unity)Таск менеджер типичной игры на Unity с задержкой в CPU 3 (основной тред Unity)

Смежные вопросы

Сокращение используемых объемов памяти и исправление OOM-сбоев

Работая над производительностью игры, вы столкнетесь со сбоями Out Of Memory или медленной загрузкой из-за неоптимизированного использования ресурсов. Хотя память не обязательно напрямую влияет на производительность, она все-таки важна. Оптимизацию использования памяти лучше всего проводить при подготовке оптимизационных билдов. Используйте Memory Profiler, чтобы точно определять занимаемые объемы. Совет: велика вероятность, что шейдеры съедают 50% памяти, тут стоит глубже погрузиться в правильное ограничение ключевых слов шейдера (на эту тему стоит написать отдельную статью).

Window > Analysis > Memory Profiler » />Window > Analysis > Memory Profiler </p>

<h2>Заключение</h2>

<p>Надеюсь, эта статья поможет вам с оптимизацией или хотя бы подскажет, как выбрать более эффективный подход к профилированию. Удачи с разработкой.</p>
    
            <p class=© Habrahabr.ru