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

Введение в NativeContainer

NativeContainer является безопасным для потока C# упаковщик для собственной памяти. NativeContainer объекты также позволяют заданию получать доступ к данным, совместно используемым главная нить а не работать с копией.

Типы NativeContainers

Пространство имен Unity.Collections содержит следующие встроенные объекты NativeContainer:

  • NativeArray: Неуправляемый массив, который открывает буфер собственной памяти для управляемого кода
  • NativeSlice: Получает подмножество NativeArray от определенной позиции до определенной длины.

Примечание: Пакет Коллекции содержит дополнительные типы NativeContainer. Полный список дополнительных типов см. в разделе Типы коллекций документации Collections.

Доступ для чтения и записи

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

Однако, если задание не требует записи в экземпляр NativeContainer, вы можете пометить NativeContainer атрибутом [ReadOnly], например:

[ReadOnly]
public NativeArray<int> input;

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

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

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

Существует три типа Allocator для выделения и высвобождения памяти NativeContainer. Вы должны указать соответствующий тип при создании экземпляра NativeContainer:

  • Allocator.Temp: самое быстрое распределение. Используйте его для распределений с жизненным циклом одного кадра или меньше. Вы не можете использовать Temp для передачи распределений экземплярам NativeContainer, хранящимся в поле члена задания.
  • Allocator.TempJob: Более медленное распределение, чем Temp, но более быстрое, чем Persistent. Используйте его для потоковых распределений в течение четырех кадров. Важно: Вы должны использовать Dispose этого типа распределения в течение четырех кадров, иначе консоль выведет предупреждение, генерируемое из собственного кода. Большинство небольших заданий используют этот тип распределения.
  • Allocator.Persistent: Самый медленный тип выделения, но может длиться столько, сколько вам нужно, и, при необходимости, на протяжении всего срока службы приложения. Это обёртка для прямого вызова malloc. Длительные задания могут использовать этот тип выделения NativeContainer. Не используйте Persistent, когда производительность является существенной.

Например:

NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);

Примечание: Число 1 в вышеприведенном примере указывает на размер NativeArray. В этом случае он имеет только один элемент массива, поскольку он хранит только одну часть данных в своем результате.

NativeContainer система безопасности

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

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

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

Система безопасности позволяет параллельно выполнять несколько заданий по считыванию одних и тех же данных.

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

Также, поскольку NativeContainers не реализует ref return, вы не можете напрямую изменить содержимое NativeContainer. Например, nativeArray[0]++; то же самое, что написать var temp = nativeArray[0]; temp++;, который не обновляет значение в nativeArray.

Вместо этого необходимо скопировать данные из индекса в локальную временную копию, изменить эту копию и сохранить ее обратно. Например:

MyStruct temp = myNativeArray[i];
temp.memberVariable = 0;
myNativeArray[i] = temp;

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