当您将作业系统与 Burst 编译器 一起使用时,作业系统的工作效果最佳。由于 Burst 不支持托管对象,因此您需要使用非托管类型来访问作业中的数据。您可以使用 可移植类型,或使用 Unity 内置的 NativeContainer
对象,它们是用于原生内存的线程安全 C# 包装器。NativeContainer
对象还允许作业访问与 主线程 共享的数据,而不是使用副本。
Unity.Collections
命名空间包含以下内置 NativeContainer
对象
NativeArray
:非托管数组,它向托管代码公开原生内存的缓冲区NativeSlice
:从特定位置到特定长度获取 NativeArray
的子集。
注意:Collections 包 包含其他 NativeContainer
。有关其他类型的完整列表,请参阅 Collections 文档 中的“集合类型”。
默认情况下,当作业访问 NativeContainer
实例时,它同时具有读写访问权限。此配置可能会降低性能。这是因为作业系统不允许您同时调度访问 NativeContainer
实例的写访问权的作业以及写入该实例的另一个作业。
但是,如果作业不需要写入 NativeContainer
实例,您可以使用 [ReadOnly]
属性标记 NativeContainer
,如下所示
[ReadOnly]
public NativeArray<int> input;
在上面的示例中,您可以同时执行作业以及其他对第一个 NativeArray
具有只读访问权限的作业。
创建 NativeContainer
实例时,您必须指定所需的内存分配类型。您使用的分配类型取决于您希望将原生容器保留多长时间。这样,您可以根据情况调整分配以获得最佳性能。
NativeContainer
内存分配和释放有三种 Allocator 类型。您必须在实例化 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
使用的某些规则,使它们在多个作业和线程之间以确定性方式运行。
例如,如果两个独立的已调度作业写入同一个 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;