[Unity ECS] 使用非托管内存

63 篇文章 11 订阅

  这个包中的 Native- 和 Unsafe- 集合是从非托管内存分配的,这意味着垃圾收集器不知道它们的存在。您负责释放不再需要的任何非托管内存。未能释放大量或大量的分配可能会导致浪费越来越多的内存,这最终可能会减慢甚至崩溃您的程序。

分配器

  分配器管理一些非托管内存,您可以从中进行分配。不同的分配器以不同的方式组织和跟踪它们的内存。三个标准提供的分配器是:

Allocator.Temp

最快的分配器。对于非常短暂的分配。临时分配不能传递给Job。

  每一帧,主线程都会创建一个临时分配器,该分配器在帧结束时完全释放。每个作业还为每个线程创建一个临时分配器,这些分配器会在作业结束时整体释放。因为 Temp 分配器作为一个整体被丢弃,您实际上不需要手动取消分配您的 Temp 分配(实际上,这样做是无操作的)。

  临时分配只能在分配它们的线程中安全使用。因此,虽然可以在Job中进行临时分配,但主线程的临时分配不能传递到Job中。例如,在主线程中分配的 Temp 的 NativeArray 不能传递到Job中。

Allocator.TempJob

  下一个最快的分配器。对于短期分配。 TempJob 分配可以传递到Job中。

  您应该在创建 TempJob 的 4 帧内取消分配您的 TempJob 分配。选择数字 4 是因为通常需要持续几帧的分配:4 的限制以舒适的额外余量满足了这种需求。

  对于 Native-collection 类型,如果 TempJob 分配的生存时间超过 4 帧,则处置安全检查将抛出异常。对于 Unsafe- 集合类型,您仍然需要在 4 帧内释放它们,但不会执行安全检查来确保您这样做。

Allocator.Persistent

  最慢的分配器。对于无限期的生命周期分配。持久分配可以传递到Job中。

  因为允许持久分配无限期地存在,所以没有安全检查可以检测到持久分配是否已经超过其预期的生命周期。因此,当您不再需要持久分配时,您应该格外小心地释放它。

Disposal(解除分配)

  每个集合都保留对分配其内存的分配器的引用,因为释放需要指定分配器。

  • Unsafe-collection 的 Dispose 方法释放它的内存。
  • Native-collection 的 Dispose 方法释放其内存并释放安全检查所需的句柄。
  • enumerator的 Dispose 方法是无操作的。包含该方法仅用于实现 IEnumerator 接口。

  我们经常想在需要它的Job运行后处理一个集合。 Dispose(JobHandle) 方法创建并调度一个将处理集合的Job,这个新Job将输入句柄作为它的依赖项。实际上,该方法在依赖关系运行后才进行处置:

NativeArray<int> nums = new NativeArray<int>(10, Allocator.TempJob);

// 创建和调度使用array的Job。
ExampleJob job = new ExampleJob { Nums = nums };
JobHandle handle = job.Schedule();

// 创建并安排一个作业,该作业将在 ExampleJob 运行后处置Array。
// 返回新作业的句柄
handle = nums.Dispose(handle);

IsCreated 属性

  集合的 IsCreated 属性仅在两种情况下为 false:

  1. 在使用其默认构造函数创建集合后,立即。
  2. 在集合上调用 Dispose 之后。

  但是,请理解您不打算使用集合的默认构造函数。它之所以可用是因为 C# 要求所有结构都具有公共默认构造函数。

  另请注意,在集合上调用 Dispose 会将 IsCreated 设置为 false 仅在该结构中,而不是在该结构的任何副本中。因此,即使在集合的底层内存被释放后,IsCreated 可能仍然为真,如果…

  • 在结构的不同副本上调用了 Dispose。
  • 或者底层内存是通过别名释放的。

Aliasing(别名)

  别名是一个集合,它没有自己的分配,而是共享另一个集合的分配,全部或部分。例如,可以创建一个UnsafeList,它不分配自己的内存,而是使用NativeList的分配。通过UnsafeList写到这个共享内存会影响到NativeList的内容,反之亦然。

  您不需要 dispose aliases,实际上,对别名调用 Dispose 没有任何作用。一旦原件被处理,该原件的别名就不能再使用:

NativeList<int> nums = new NativeList<int>(10, Allocator.TempJob);
nums.Length = 5;

// 创建一个包含 5 个整数的数组,作为列表内容的alias。
NativeArray<int> aliasedNums = nums.AsArray();

// 修改数组和列表的第一个元素。
aliasedNums[0] = 99;

// 只需要处理原件。
nums.Dispose();

// 抛出 ObjectDisposedException 
// 因为处置原始释放别名内存。
aliasedNums[0] = 99;

Aliasing在以下几种情况下很有用:

  • 在不复制数据的情况下以另一种集合类型的形式获取集合的数据。例如,您可以创建一个别名为 NativeArray 的 UnsafeList。
  • 在不复制数据的情况下获取集合数据的子范围。例如,您可以创建一个 UnsafeList,为另一个列表或数组的子范围设置别名。
  • Array reinterpretation(数组重新解释)。

  也许令人惊讶的是,允许 Unsafe- 集合作为 Native- 集合的别名,即使这种情况会破坏安全检查。例如,如果 UnsafeList 为 NativeList 设置别名,则调度访问其中一个的Job同时调度另一个访问另一个的Job是不安全的,但安全检查不会捕获这些情况。您有责任避免此类错误。

Array reinterpretation(数组重新解释)

  数组的重新解释是数组的别名,它将内容作为不同的元素类型进行读取和写入。例如,一个 NativeArray 重新解释了一个 NativeArray 共享相同的字节,但它读取和写入字节为 ints 而不是 ushorts;因为每个int是4个字节,每个ushort都是2个字节,所以每个int对应两个ushort,重新解释的长度是原来的一半。

NativeArray<int> ints = new NativeArray<int>(10, Allocator.Temp);

// 重新解释的数组的长度为 20
//(因为它每一个原始int有两个short)。
NativeArray<short> shorts = ints.Reinterpret<int, short>();

// 修改数组的前 4 个字节。
shorts[0] = 1;
shorts[1] = 1;

int val = ints[0];   // val is 65537 (2^16 + 2^0)

// 与其他别名集合一样,只有原始集合需要销毁。
ints.Dispose();

// Throws an ObjectDisposedException because disposing
// the original deallocates the aliased memory.
shorts[0] = 1;

已知的问题

  在同一线程上使用 Allocator.Temp 分配的所有容器都使用共享的 AtomicSafetyHandle 实例,而不是每个容器都有自己的实例。一方面,这很好,因为 Temp 分配的集合无法传递到Job中。另一方面,在使用 NativeHashMap、NativeMultiHashMap、NativeHashSet 和 NativeList 的情况下同时使用它们的 secondary 安全句柄时,这是有问题的。 (secondary 安全句柄确保在 NativeList 由于调整大小而重新分配时,作为 NativeList 别名的 NativeArray 会失效。)

  使这些集合类型的枚举器无效(或使 NativeList.AsArray 返回的 NativeArray 无效)的操作也会使之前获取的所有其他枚举器无效。例如,当启用安全检查时,这将抛出:

var list = new NativeList<int>(Allocator.Temp);
list.Add(1);

// 此数组使用列表的secondary安全句柄,即
// 在所有 Allocator.Temp 分配之间共享。
var array = list.AsArray();

var list2 = new NativeHashSet<int>(Allocator.Temp);

// 这会使辅助安全手柄失效,该手柄也用于
// 通过上面的列表。
list2.TryAdd(1);

// 这将抛出一个InvalidOperationException,因为共享安全的
//句柄无效。
var x = array[0];
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值