Technology

Рассказы из окопов оптимизации: Экономия памяти с помощью Addressables

PATRICK DEVARNEY / UNITY TECHNOLOGIESContributor
Mar 31, 2021|12 Мин
Рассказы из окопов оптимизации: Экономия памяти с помощью Addressables
Эта веб-страница была переведена с помощью машинного перевода для вашего удобства. Мы не можем гарантировать точность или надежность переведенного контента. Если у вас есть вопросы о точности переведенного контента, обращайтесь к официальной английской версии веб-страницы.

Эффективная передача данных в память и из памяти - ключевой элемент любой качественной игры. Будучи консультантом в нашей команде профессиональных услуг, я стремился повысить эффективность многих проектов клиентов. Именно поэтому я хочу поделиться некоторыми советами о том, как использовать систему Addressables Asset System от Unity для улучшения вашей стратегии загрузки контента.

Память - это ограниченный ресурс, которым необходимо тщательно управлять, особенно при переносе проекта на новую платформу. Использование Addressables может улучшить память во время выполнения за счет внедрения слабых ссылок для предотвращения загрузки ненужных активов. Слабые ссылки означают, что вы контролируете, когда ссылаемый актив загружается в память и выводится из нее; Addressables System сама найдет все необходимые зависимости и загрузит их тоже. В этом блоге мы рассмотрим ряд сценариев и проблем, с которыми вы можете столкнуться при настройке проекта на использование Unity Addressable Asset System, а также объясним, как их распознать и оперативно устранить.

Пример инвентаризации
Использование Addressables

В этой серии рекомендаций мы будем работать с простым примером, который устроен следующим образом:

  • У нас есть скрипт InventoryManager в сцене со ссылками на наши три инвентарных актива: Префабы меча, меча босса, щита.
  • Эти средства не нужны постоянно во время игры.

Вы можете загрузить файлы проекта для этого примера на моем GitHub. Мы используем пакет предварительного просмотра Memory Profiler для просмотра памяти во время выполнения. В Unity 2020 LTS перед установкой этого пакета из менеджера пакетов необходимо включить предварительный просмотр пакетов в Настройках проекта.

Если вы используете Unity 2021.1, выберите опцию Добавить пакет по имени из дополнительного меню (+) в окне Менеджера пакетов. Используйте имя "com.unity.memoryprofiler".

Этап 1: Жесткие ссылки, без Addressables

Давайте начнем с самой простой реализации, а затем перейдем к оптимальному подходу к настройке содержимого Addressables. Мы просто применим жесткие ссылки (прямое назначение в инспекторе, отслеживаемое по GUID) к нашим префабам в MonoBehaviour, который существует в нашей сцене.

Система инвентаризации

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

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

Текстуры для наших предметов хранятся в памяти, даже если они еще не инстанцированы.
Текстуры для наших предметов хранятся в памяти, даже если они еще не инстанцированы.

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

Этап 2: Внедрить Addressables

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

Меню активов с опциями "меч", "меч босса" и "щит

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

В памяти не отображаются текстуры предметов инвентаря; только текстуры TextMeshPro.
В памяти не отображаются текстуры предметов инвентаря; только текстуры TextMeshPro.

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

Использование Addressables

Проблема: Если мы инстанцируем все наши предметы и депаундируем меч босса, мы все равно увидим текстуру меча босса "BossSword_E " в памяти, даже если она не используется. Причина в том, что, хотя вы можете частично загружать пакеты активов, невозможно автоматически частично выгружать их. Такое поведение может стать особенно проблематичным для бандлов с большим количеством активов, например, для одного AssetBundle, который включает в себя все наши префабы инвентаря. Ни один из активов в пакете не будет выгружен до тех пор, пока весь AssetBundle не перестанет быть нужным, или пока мы не вызовем дорогостоящую процессорную операцию Resources.UnloadUnusedAssets().

Текстура BossSword_E остается в памяти даже после того, как меч босса был выпущен.
Текстура BossSword_E остается в памяти даже после того, как меч босса был выпущен.
Макет данных
Этап 3: Небольшие пакеты

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

Сделать это очень просто. Выберите Addressables Group, затем Content Packaging & Loading > Advanced Options > Bundle Mode и перейдите в Inspector, чтобы изменить режим упаковки с Pack Together на Pack Separately.

Используя Pack Separately для создания Addressable Group, вы можете создать AssetBundle для каждого актива в Addressables Group.

Меню группы активов Adressables

Активы и пакеты будут выглядеть следующим образом:

Активы и пакеты активов

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

Проблема: Если мы спауним все три предмета и сделаем захват памяти, в памяти появятся дубликаты активов. Точнее, это приведет к появлению нескольких копий текстур "Sword_N" и "Sword_D". Как это может произойти, если мы меняем только количество пакетов?

Текстуры с дубликатами
Этап 4: Исправьте дубликаты активов

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

Пакеты ассетов для меча и BossSword содержат некоторые из тех же зависимостей.
Наши пакеты ассетов для меча и BossSword содержат некоторые из тех же зависимостей.

Addressables включает окно анализа, помогающее диагностировать расположение пучков. Откройте Window > Asset Management > Addressables > Analyze и запустите правило Bundle Layout Preview. Здесь мы видим, что комплект меча явно включает в себя sword.prefab, но в него также включено множество неявных зависимостей.

Меню правил

В том же окне выполните команду Check Duplicate Bundle Dependencies. Это правило выделяет активы, включенные в несколько пакетов активов на основе нашего текущего макета Addressables.

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

Мы можем предотвратить дублирование этих активов двумя способами:

1. Поместите префабы Sword, BossSword и Shield в один пакет, чтобы у них были общие зависимости, или

2. Явно включите дублированные активы в Addressables

Мы хотим избежать размещения нескольких префабов инвентаря в одном пакете, чтобы предотвратить сохранение ненужных активов в памяти. Поэтому мы добавим дублированные активы в их собственные комплекты (Bundle 4 и Bundle 5).

Addressables, размещенные в собственных пакетах
Дубликаты текстур явно помещаются в свои собственные пачки.

В дополнение к анализу наших пакетов правила анализа могут автоматически исправлять неработающие активы с помощью правил Fix Selected. Нажмите эту кнопку, чтобы создать новую адресную группу под названием "Duplicate Asset Isolation", в которую войдут четыре дублированных актива. Установите для этой группы режим Bundle Mode на Pack Separately, чтобы предотвратить сохранение в памяти других активов, которые больше не нужны.

Скриншот редактора - Addressables Groups
Этап 5: Уменьшение размера метаданных пакета активов в крупных проектах

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

Просмотрите текущий расход памяти метаданных AssetBundle в профилировщике Unity. Перейдите к модулю памяти и сделайте снимок памяти. Загляните в категорию Другие > SerializedFile.

Архив
В настоящее время в память SerializedFile загружено 1 819 пакетов, общий размер которых составляет 263 МБ.

Для каждого загруженного AssetBundle в памяти имеется запись SerializedFile. В этой памяти хранятся метаданные AssetBundle, а не реальные активы в пакетах. Эти метаданные включают в себя:

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

Из этих трех элементов больше всего места занимают буферы чтения файлов. Эти буферы имеют размер 64 КБ каждый на PS4, Switch и Windows RT и 7 КБ на всех остальных платформах. В приведенном выше примере 1 819 пакетов * 64 КБ * 2 буфера = 227 МБ только для буферов.

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

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

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

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

Встроенное правило анализа дедупликации, которое мы запустили, обнаруживает все активы, находящиеся в нескольких пакетах, и эффективно перемещает их в одну Addressables Group. Установив для этой группы значение " Упаковать отдельно", мы получим один актив в каждом пакете. Однако есть некоторые дублирующиеся активы, которые можно смело упаковывать вместе, не создавая проблем с памятью. Рассмотрите приведенную ниже диаграмму:

Один актив в пачке

Мы знаем, что текстуры "Sword_N" и "Sword_D" являются зависимостями одних и тех же пакетов (Bundle 1 и Bundle 2). Поскольку у этих текстур одни и те же родители, мы можем смело упаковывать их вместе, не создавая проблем с памятью. Обе текстуры меча должны быть всегда загружены или разгружены. Никогда не возникает опасений, что одна из текстур может остаться в памяти, поскольку не бывает случаев, когда мы специально используем одну текстуру, а не другую.

Мы можем реализовать эту улучшенную логику дедупликации в нашем собственном правиле Addressables Analyze Rule. Мы будем работать с существующим правилом CheckForDupeDependencies.cs. Полный код реализации можно увидеть в примере Inventory System. В этом простом проекте мы всего лишь сократили общее количество пучков с семи до пяти. Но представьте себе сценарий, в котором ваше приложение имеет сотни, тысячи или даже больше дубликатов активов в Addressables. В ходе работы с компанией Unknown Worlds Entertainment по оказанию профессиональных услуг для ее игры Subnautica в проекте изначально было 8 718 пакетов после использования встроенного правила анализа дедупликации. Мы сократили это число до 5 199 пучков после применения пользовательского правила для группировки дедуплицированных активов на основе их родительских пучков. Подробнее о нашей работе с командой вы можете узнать из этого кейса.

Таким образом, количество пакетов сократилось на 40 %, при этом в них осталось то же содержимое и сохранился тот же уровень детализации. Сокращение количества пакетов на 40 % аналогично уменьшило размер SerializedFile во время выполнения на 40 % (с 311 МБ до 184 МБ).

Заключение

Использование Addressables позволяет значительно сократить потребление памяти. Вы можете еще больше сократить объем памяти, организовав AssetBundles в соответствии с вашим сценарием использования. В конце концов, встроенные правила анализа консервативны, чтобы подходить для всех приложений. Написание собственных правил анализа позволяет автоматизировать компоновку пакетов и оптимизировать ее для вашего приложения. Чтобы выявить проблемы с памятью, продолжайте часто создавать профили и проверяйте окно Analyze, чтобы узнать, какие активы явно и неявно включены в ваши пакеты. Ознакомьтесь с документацией по Addressables Asset System, чтобы узнать о лучших практиках, руководстве по началу работы и расширенной документации по API.

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