Узнайте, как сэкономить память, улучшив способ использования AssetBundles.

Независимо от того, передает ли ваше приложение потоковые данные из сети доставки контента (CDN) или упаковывает их в один большой бинарник, вы наверняка слышали о AssetBundles. AssetBundle - это файл, содержащий один или несколько сериализованных активов (текстуры, меши, аудиоклипы, шейдеры и т. д.) и загружаемый во время выполнения.
AssetBundles можно использовать напрямую или через системы, такие как Unity Addressable Asset System (она же Addressables). Система Addressables - это пакет, который обеспечивает более доступный и поддерживаемый способ управления активами в ваших проектах. Это абстракция поверх AssetBundles. Хотя Addressables минимизирует прямое взаимодействие разработчиков с AssetBundles, полезно понимать, как использование AssetBundles может повлиять на использование памяти. Обзор системы Addressables можно найти в этом блоге и на сессии Unite Copenhagen 2019.
Разработчики, работающие над новыми проектами, должны рассмотреть возможность использования Addressables, а не работать с AssetBundles напрямую. Если вы работаете над проектом, в котором уже используется подход AssetBundles, приведенная здесь информация о том, как AssetBundles влияют на память во время выполнения, поможет вам добиться наилучших результатов.
Когда Unity загружает LZMA AssetBundle с помощью класса WWW (который теперь устарел) или UnityWebRequestAssetBundle (UWR), Unity оптимизирует получение, повторное сжатие и версионирование AssetBundle, используя два кэша: кэш памяти и дисковый кэш.
Пучки активов, загруженные в кэш памяти, занимают большой объем памяти. Если вы не хотите часто и быстро обращаться к содержимому AssetBundle, кэш памяти, вероятно, не стоит того, чтобы тратить на него память. Вместо этого используйте дисковый кэш.
Если в API UnityWebRequestAssetBundle вы указываете аргумент version или hash, Unity сохраняет данные AssetBundle в дисковом кэше. Если вы не укажете эти аргументы, Unity будет использовать кэш памяти. Обратите внимание, что по умолчанию Addressables использует дисковый кэш. Это поведение можно контролировать с помощью поля UseAssetBundleCache.
AssetBundle.LoadFromFile() и AssetBundle.LoadFromFileAsync() всегда используют кэш памяти для LZMA AssetBundles. Поэтому мы рекомендуем использовать API UnityWebRequestAssetBundle. Если использование API UnityWebRequestAssetBundle нецелесообразно, вы можете использовать AssetBundle.RecompressAssetBundleAsync(), чтобы переписать LZMA AssetBundle на диск.
Внутреннее тестирование показывает, что разница в оперативной памяти между использованием дискового кэша и кэша памяти как минимум на порядок больше. Вы должны взвесить компромисс между стоимостью памяти и дополнительными требованиями к дисковому пространству и временем инсталляции Asset для вашего приложения.
Чтобы определить, как кэш памяти AssetBundle может повлиять на использование памяти вашим приложением, воспользуйтесь родным профилировщиком (мы выбрали инструмент Allocations Instrument из Xcode) для изучения выделений класса ArchiveStorageConverter. Если этот класс использует более 10 МБ оперативной памяти, вероятно, вы используете кэш памяти.

При создании AssetBundles для больших проектов не стоит полагать, что Unity по умолчанию минимизирует количество дублирующейся информации в них. Для выявления случаев дублирования данных в сгенерированных AssetBundles можно использовать удобный AssetBundle Analyzer, написанный на языке Python одним из наших коллег из группы консалтинга и разработки. Используя командную строку, инструмент извлекает информацию из сгенерированных AssetBundles, которая затем сохраняется в базе данных SQLite, содержащей несколько полезных представлений. Затем вы можете запросить базу данных с помощью таких инструментов, как DB Browser для SQLite. Этот инструмент поможет вам найти и устранить любые неэффективные моменты в вашем конвейере сборки, независимо от того, создавали ли вы пакеты вручную или с помощью Addressables.

В качестве альтернативы можно воспользоваться инструментом AssetBundle Browser, который можно загрузить и сразу же интегрировать в свой проект. Обратите внимание, что этот инструмент предоставляет схожие функции с Addressables, поэтому если вы используете Addressables, этот инструмент не имеет значения.
Инструмент AssetBundle Browser позволяет просматривать и редактировать конфигурацию AssetBundles в данном проекте Unity и обеспечивает функциональность сборки. Он также предоставляет несколько удобных функций, например, информирует пользователей о дублировании ассетов, которые тянутся из-за зависимостей, например, текстур.

Решая, как организовать активы в AssetBundles, следует внимательно отнестись к зависимостям. Независимо от топологии AssetBundle, Unity делает различие между активами, которые находятся внутри двоичного файла приложения (в папке Resources или с ее участием), и теми, которые нужно загружать из AssetBundles. Можно считать, что эти два типа активов живут в разных мирах. Невозможно создать AssetBundle, имеющий жесткую ссылку на экземпляр Asset в мире папки Resources. Чтобы ссылаться на эти активы, Unity вместо этого создает копию активов, которые использует в мире AssetBundle.


Возьмем, к примеру, логотип игры. Логотип может отображаться в пользовательском интерфейсе сцены загрузки во время запуска игры. Поскольку этот экран загрузки должен отображаться до того, как удаленные активы будут переданы на диск, вы можете включить логотип актива в сборку, чтобы его можно было использовать сразу.
Этот же логотип используется на панели опций в пользовательском интерфейсе, где пользователи могут выбрать язык, звуковые предпочтения и другие настройки. Если эта панель пользовательского интерфейса загружена из AssetBundle, то этот AssetBundle создаст свою собственную копию логотипа Asset.
При одновременной загрузке экрана загрузки и панели опций будут загружены обе копии логотипа Asset, а это дублирование, которое отнимает память.
Решением этой проблемы является разрыв жесткой связи между одним или обоими экранами. Если логотип находится в AssetBundle, то, прежде чем получить ссылку на ассет, необходимо выполнить некоторое количество потоковых операций. Если логотип находится в бинарном файле (например, в папке Resources), то панель пользовательского интерфейса должна иметь слабую ссылку на ассет логотипа и загружаться через API, например Resources.Load.

Пользовательские сценарии должны будут использовать строку для загрузки изображения во время выполнения и назначения его соответствующему компоненту. Оптимальным вариантом может быть включение AssetBundle, содержащего ассет логотипа, в каталог StreamingAssets приложения. Вы по-прежнему будете загружать ассеты из AssetBundle, но поскольку вы размещаете пакет локально, вы не будете платить за время, необходимое для загрузки содержимого.
Использование строк, путей или GUID для ссылок на активы не слишком интуитивно, но вы можете захотеть создать пользовательские инспекторы, которые позволят использовать стандартные функции Unity по перетаскиванию ссылок на поля со слабыми ссылками. И не забудьте использовать пакет MemoryProfiler от Unity, чтобы определить активы, которые дублируются в памяти. Обратите внимание, что система Addressables имеет свой собственный механизм проверки дубликатов в зависимостях (подробнее см. документацию).
Несмотря на то что система Addressables представляет собой абстракцию поверх AssetBundles, знание того, как все работает под капотом, поможет вам избежать дорогостоящих проблем с производительностью, подобных тем, что описаны в этой статье.
Если вы в настоящее время используете Addressables, мы хотим получить от вас информацию в этом коротком опросе.
Мы планируем дорожную карту для будущих частей этой серии. Есть ли какая-то область, на которой вы хотели бы сосредоточить наше внимание? Оставьте комментарий, чтобы сообщить нам об этом!
