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

Распределители памяти

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

  • Влияние на производительность создания, доступа и высвобождения памяти.
  • Накладные расходы на отслеживание использования памяти.
  • Безопасность доступа потока.

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

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

Типы выделения памяти

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

Различные типы распределителей являются следующими:

  • Двойной поток: Обвивка, которая объединяет два динамических распределителя стека и распределитель bucket для небольших распределений. Двойной распределитель потока перенаправляет распределения на основе размера и потока ID.
  • Динамическая куча: Главный распределитель кучи, который использует алгоритм двухуровневого сегрегированного соответствия (TLSF).
  • Bucket: Распределитель с фиксированным размером без блокировки для небольших распределений перед тем, как они попадают в главную кучу.
  • Локальное хранилище потока (TLS) стек: А последний входит, первый выходит (LIFO) распределитель стека, для временных распределений на потоках.
  • Потокобезопасный линейный: циклический аллокатор для рабочих потоков по принципу «первым пришёл — первым обслужен» (FIFO).

Использование распределителя

Каждый тип распределителей имеет свой алгоритм для размещения выделений в блоках памяти, и поэтому полезны для различных типов распределений. Важным различием между распределениями, как правило, является постоянство, или жизненный цикл выделения, который определяет, где разместить выделение. Например, долговечное (постоянное) выделение идет в распределители стека и bucket, в то время как кратковременное выделение идет в поток-безопасные линейные и поток-локальные распределители хранения.

Когда выделение происходит где-то в коде, Unity делает следующее:

  • Проверяет, выполняется ли код в настоящее время на главном потоке:
    • Если код запускается на главном потоке, он использует динамическое распределение кучи на главном потоке.
    • Если код не выполняется на главном потоке, блоки распределения получают доступ к потоковому распределителю.
  • Проверяет размер распределителя:
    • Если размер выделения достаточно мал, чтобы поместиться в распределитель bucket, и для него есть место, он выделяет память в распределитель bucket.
    • Если размер выделения слишком велик для распределителя bucket или нет места, используется динамический распределитель стека.

Двойной распределитель потоков

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

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

Unity по умолчанию создает несколько двухпоточностей, включая Main и Gfx. Выделения файлового кэша и Profiler всегда являются динамическими выделениями стека.

Для примера использования двойного распределителя потоков см. Пример двойного распределителя потоков.

Динамический распределитель стека

Динамический распределитель кучи является основным распределителем кучи. Он использует Алгоритм двухуровневого сегрегированного соответствия (TLSF) для управления своей памятью. Он применяется для крупных долгоживущих выделений и для выделений, которые не помещаются в блочный аллокатор. Выделения делятся на блоки памяти заданного размера, запрашиваемые по мере необходимости. Выделение должно быть меньше половины блока: если оно занимает половину блока или больше, для динамического аллокатора кучи это слишком много, и Unity выделяет память через API виртуальной памяти.

Пример использования динамического распределителя стека см. в Пример динамического распределителя стека.

Распределитель ведра

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

Блочный аллокатор резервирует под выделения блоки памяти. Каждый блок делится на подразделы по 16 КБ. Это значение не настраивается и в интерфейсе не отображается. Каждый подраздел делится на выделения. Размер выделения кратен заданному фиксированному размеру, называемому гранулярностью. У блочного аллокатора есть максимальное число блоков заданного размера.

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

Пример использования распределителя bucket см. в Пример распределителя bucket.

Потоковый локальный распределитель стека хранения

Стековый аллокатор локального хранилища потока (TLS) предназначен для кратковременных выделений памяти в отдельных потоках. Это самый быстрый аллокатор. Он состоит из стеков памяти, работающих по принципу «последним пришёл — первым вышел» (LIFO); для очистки памяти указатель каждого стека сбрасывается в 0. Аллокатор TLS чаще всего используется для выделений памяти Allocator.Temp, которые также могут выполняться пользовательским кодом.

Размер блока по умолчанию для распределителя стека локального хранилища потока (TLS) составляет 4 MB для платформ и 16 MB для Unity Editor. Вы можете настроить эти значения. Если использование распределителя превышает настроенный размер блока, Unity увеличивает размер блока. Лимит для этого увеличения в два раза превышает первоначальный размер.

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

Поскольку TLS является распределителем стека, переплетающиеся схемы распределения и выделения (например, allocate a, allocate b, deallocate a, allocate c, deallocate b и т.д.) могут фрагментировать его и помешать распределителю сбросить точку стека вниз по стеку. Это может привести к тому, что он заполнится полностью, хотя теоретически имеется свободное пространство.

Если распределитель стека потока заполнен, распределения возвращаются к threadsafe линейному распределителю.

Пример использования распределителя стека TLS см. в Пример распределителя стека локального хранилища потока.

Линейный распределитель с защитой от потока

Безопасный для потока линейный распределитель является резервным вариантом для TLS выделения стека, и он выделяет до 16 блоков памяти. Он использует первый-в-первый-выход (FIFO) алгоритм для lock-free распределения рабочих буферов для заданий. Когда завершена, задания распоряжаются этими буферами. Это в основном используется для Allocator.TempJob распределения, которые имеют 4-фреймовый предел жизни, и они также могут быть получены из пользовательского кода.

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

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

Для примера использования линейного распределителя безопасного для потока см. Пример линейного распределителя безопасного для потока.

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