Unity 6.3
0 онлайн 17 гостей 3 в системе
Вход

Память в Unity Веб

Ограничения памяти в Unity Web могут ограничивать сложность запускаемого контента.

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

  • Используемое устройство
  • Используемая операционная система
  • Используемый браузер и то, работает ли он на 32- или 64-разрядном процессоре
  • Как много памяти требуется JavaScript движку браузера для анализа кода
  • Использует ли браузер отдельные процессы для каждой вкладки, или ваш контент должен делить пространство памяти со всеми другими открытыми вкладками.

Примечание: Сведения о рисках безопасности, связанных с веб-памятью, см. Ресурсы безопасности и памяти.

Использование памяти в Unity Веб

Следующие области веб-контента Unity требуют от браузера выделения значительного количества памяти.

Unity куча

Unity использует кучу памяти для хранения всех объектов времени выполнения движка Unity. Сюда входят управляемые и нативные объекты, загруженные Assets, Scenes и shaders. Это та же память, которую Unity Players используют на любой другой платформе.

Куча Unity представляет собой непрерывный блок выделенной памяти. Unity поддерживает автоматическое изменение размера кучи в соответствии с потребностями приложения. Размер кучи увеличивается во время работы приложения и может достигать 4 ГБ в зависимости от значения Maximum Memory Size, заданного в настройках Player. Unity создаёт эту кучу памяти как объект Memory. Свойство buffer объекта Memory представляет собой ArrayBuffer с изменяемым размером, в котором хранятся необработанные байты памяти, доступные коду WebAssembly.

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

Вы можете управлять начальным размером и ростом кучи, используя параметры Memory Growth Mode в Web Player Settings. Параметры по умолчанию настроены так, чтобы хорошо работать для всех случаев использования на рабочем столе. Однако для мобильных браузеров вам нужно использовать расширенные параметры настройки. Для мобильных браузеров рекомендуется настроить Initial Memory Size на типичное использование кучи приложением.

Данные об ассетах

Когда вы создаете веб-сборку Unity, Unity генерирует .data файл. Он содержит все сцены и ассеты, необходимые приложению для запуска. Поскольку Unity Web не имеет доступа к настоящей файловой системе, она создаёт виртуальную файловую систему в памяти, и браузер распаковывает .data Фреймворк Emscripten (JavaScript) выделяет эту файловую систему памяти в пространстве памяти браузера. Пока ваш контент запускается, память браузера сохраняет несжатые данные. Чтобы сохранить время загрузки и использование памяти низким, постарайтесь сохранить эти несжатые данные как можно меньше.

Чтобы уменьшить использование памяти, вы можете упаковывать данные Asset в AssetBundles. AssetBundles обеспечивает полный контроль за загрузкой ассетов. Вы можете управлять, когда приложение загружает ассет, и когда время выполнения выгружает его. Вы можете выгрузить неиспользуемые ассеты, чтобы высвободить память.

AssetBundles загружаются непосредственно в кучу Unity, поэтому они не приводят к дополнительному выделению браузером.

Включите Кэширование данных для автоматического кэширования данных Asset в вашем контенте на компьютере пользователя. Это означает, что вам не нужно повторно загружать эти данные во время последующих запусков. Веб-загрузчик Unity реализует Data Caching с IndexedDB API и Caching API. Эта опция позволяет кэшировать файлы, которые обычно слишком большие для кэша браузера.

Чтобы включить опцию Кэширование данных перейдите в Файл > Профиль сборки > Настройки плеера > Настройки публикации.

Сборка мусора

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

Из-за ограничения безопасности WebAssembly, пользовательские программы не имеют права изучать собственный стек выполнения, чтобы предотвратить возможные эксплуатации. Это означает, что на веб-платформе сборщик мусора может запускаться только тогда, когда не выполняется управляемый код (который потенциально может ссылаться на живые объекты C#) и запускается только в конце каждого кадра программы. Это расхождение вызывает различия в поведении сборщика мусора на веб-платформе по сравнению с другими платформами.

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

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

string hugeString = "";

for (int i = 0; i < 100000; i++)
{
   hugeString += "foo";
}

В данном примере длина hugeString в конце цикла составляет 3 * 100000 = 300000 символов. Однако код генерирует сто тысяч временных строк перед созданием окончательной строки. Общая выделенная память на протяжении цикла составляет 3 * (1 + 2 + 3 + … + 100000) = 3 * (100000 * 100001 / 2) = 15 гигабайт.

Чтобы избежать этой проблемы, используйте StringBuilder для построения длинных строк. На собственных платформах, garbage collector непрерывно очищает предыдущие временные копии строки, пока цикл выполняется. Таким образом, этот код не требует 15 GB из RAM в общей сложности для выполнения:

using System.Text;


var stringBuilder = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
    stringBuilder.Append("foo");
}
string hugeString = stringBuilder.ToString();

Если вы знаете максимальную длину строки, то лучше всего установить Capacity или StringBuilder при инициализации.

На веб-платформе сборщик мусора не возвращает временные копии строк до конца кадра. В результате код исчерпал память, пытаясь выделить 15 GB из RAM.

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

byte[] data;

for (int i = 0; i < 100000; i++)
{
   data = new byte[i];
   // do something temporary with data[]
}

Здесь код временно выделяет 1 + 2 + 3 + … + 100000 байт = 5 GB байт, хотя сохраняется только последний массив из 100 KB. Это приводит к тому, что у программы, казалось бы, заканчивается память на веб-платформе, хотя в конечном выводе требуется только 100 KB.

Чтобы ограничить эти типы проблем, избегайте кодовых конструкций, которые выполняют квадратично увеличивающиеся количества выделений временной памяти. Вместо этого либо предварительно выделяйте окончательный необходимый размер данных, либо используйте List<T> или аналогичную структуру данных, которая выполняет геометрически увеличивающиеся резервирования емкости, которые уменьшают давление временной памяти.

Например, с контейнером List<T>, рассмотрите возможность использования функции List<T>.ReserveCapacity(), которая позволяет предварительно выделить необходимую емкость, если вы знаете окончательный размер структуры данных. Аналогичным образом, рассмотрите возможность использования функции List<T>.TrimExcess() при уменьшении размера контейнера, который ранее содержал несколько мегабайт памяти.

Примечание: Когда вы используете C# делегатов или события, такие как Delegate, Action, Func, эти классы внутренне используют аналогичные линейные распределения роста, как выше. Избегайте чрезмерного количества регистрации и отмены регистрации делегатов на кадр с этими классами, чтобы минимизировать давление временной памяти для сборщика мусора на веб-платформе.

NativeArray для временных ассигнований

Обращение к некоторым ограничениям Unity Web сбора мусора с помощью NativeArray<T> для временных выделений. NativeArray<T> обходит сборщик мусора и снижает давление памяти на кучу Unity. Дополнительную информацию о NativeArray<T>см. в Безопасные типы потока.

NativeArray<T> имеет следующие ограничения по сравнению с управлением памятью с помощью сборщика мусора:

  • Она поддерживает только базовые типы (например: byte, int, long, float) и структуры как данные.
  • Для предотвращения утечек памяти требуется освободить объект вручную с помощью Dispose() или использовать оператор using.
  • Он не реализует ref return, что означает, что при модификации данных, значения должны быть скопированы во временную переменную и назначены назад. Например:
   // Updating an element in NativeArray<MyStruct> requires a temporary
   // copy
   MyStruct temp = myNativeArray[i];
   temp.memberVariable = 0;
   myNativeArray[i] = temp;

   // Incrementing an element in NativeArray<int> needs to use explicit
   // assignment since intNativeArrayInt[0]++ won't work
   intNativeArrayInt[0] = intNativeArrayInt[0]++;

В следующем примере показано, как использовать NativeArray<T> для временных выделений в Веб:

using Unity.Collections;

for (int = 0; i < 10000; i++)
{
  using(var data = new NativeArray<byte>(i, Allocator.Temp))
  {
    // Do something with data[] here
    // data.Dispose() will be automatically called at the end of the using
    // scope which immediately frees the memory
  }
}

Дополнительные ресурсы