C#Job System如何工作

1、NativeContainer

NativeContainer

安全系统复制数据的过程的缺点是它还隔离了每个副本中Job的结果。要克服此限制,您需要将NativeContainer结果存储在一种名为NativeContainer的共享内存中。

什么是NativeContainer?

NativeContainer是托管值类型,为本机内存提供相对安全的C#包装器。它包含指向非托管分配的指针。与Unity C#作业系统一起使用时,一个 NativeContainer允许Job访问与主线程共享的数据,而不是使用拷贝副本数据。

有哪些类型的NativeContainer?

Unity附带一个NativeContainer名为NativeArray的程序。您还可以使用NativeSlice操作一个NativeArray来获取NativeArray从指定位置到指定长度的子集。

注意:实体组件系统(ECS)包扩展了Unity.Collections命名空间以包括其他类型的NativeContainer:

NativeList- 可调整大小的NativeArray。

NativeHashMap - 键值对。

NativeMultiHashMap - 每个键有多个值。

NativeQueue- 先进先出(FIFO)队列。

NativeContainer和安全系统

安全系统内置于所有NativeContainer类型。它跟踪NativeContainer中正在阅读和写入的内容。

注意:所有NativeContainer类型的安全检查(例如越界检查,重新分配检查和竞争条件检查)仅在Unity Editor和Play模式下可用。

该安全系统的一部分是DisposeSentinel和AtomicSafetyHandle。该DisposeSentinel检测内存泄漏,如果你没有正确地释放你的内存,就会报错。内存泄漏发生后很久就会发生内存泄漏错误。

使用AtomicSafetyHandle转移NativeContainer代码的所有权。例如,如果两个调度Job写入相同NativeArray,则安全系统会抛出一个异常,并显示一条明确的错误消息,说明解决问题的原因和方法。当你调度违规Job时,安全系统会抛出此异常。

在这种情况下,您可以调度具有依赖关系的Job。第一个Job可以写入NativeContainer,一旦完成执行,下一个Job就可以安全地读取和写入上一个Job相同的NativeContainer。从主线程访问数据时,读写限制也适用。安全系统允许多个Job并行读取相同的数据。

默认情况下,当Job有权访问一个NativeContainer时,它具有读写访问权限。此配置可能会降低性能。C#Job System不允许您在一个job正在写入NativeContainer时同时调度另外一个对NativeContainer 有写入权限的Job。

如果作业不需要写入一个 NativeContainer,请使用[ReadOnly]属性标记NativeContainer,如下所示:

[ReadOnly]

public NativeArray<int> input;

在上面的示例中,您可以与其他对第一个也具有只读访问权限的作业同时执行作业NativeArray。

注意:无法防止从作业中访问静态数据。访问静态数据会绕过所有安全系统,并可能导致Unity崩溃。有关更多信息,请参阅C#作业系统提示和故障排除。

NativeContainer分配器

当创建 NativeContainer时,必须指定所需的内存分配类型。分配类型取决于Job运行的时间长度。通过这种方式,您可以定制分配以在每种情况下获得最佳性能。

NativeContainer内存分配和释放有三种分配器类型。在实例化你的NativeContainer时候需要指定合适的一个类型。

Allocator.Temp分配的时候最快。它适用于寿命为一帧或更少的分配。您不应该使用Temp将NativeContainer分配传递给Jobs。您还需要在从方法(例如MonoBehaviour.Update,或从本机代码到托管代码的任何其他回调)调用返回之前调用该方法Dispose()。

Allocator.TempJob是一个比Temp慢的分配,但速度比Persistent快。它适用于四帧生命周期内的分配,并且是线程安全的。如果在四个帧内没有调用Dispose,则控制台会打印一个从本机代码生成的警告。大多数小型Jobs都使用这个NativeContainer分配类型。

Allocator.Persistent是最慢的分配,只要你需要它,就一直存在。并且如果有必要的话,可以持续整个应用程序的生命周期。它是直接调用malloc的包装器。较长的Jobs可以使用此NativeContainer分配类型。你不应该使用Persistent在性能至关重要的地方使用。

例如:

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

注意:上例中的数字1表示NativeArray的大小。在这种情况下,它只有一个数组元素(因为它只存储一个数据result)。

 

2、Job System如何工作-创建Jobs

创建Jobs

要在Unity中创建作业,您需要实现IJob接口。IJob允许您调度可以与其他正在运行的Job并行运行的单个Job。

注意:“Job”是Unity中用于实现IJob接口的任何结构的集合术语。

要创建Jobs,您需要:

  • 创建一个继承自IJob的结构体。
  • 添加Jobs使用的成员变量(blittable类型或NativeContainer类型)。
  • 在结构体中实现一个继承自IJob接口的Execute的方法。

当执行job时,这个Execute方法在单个核心上运行一次。

注意:在设计job时,请记住它们在数据副本上运行,除非是NativeContainer。因此,从主线程中的作业访问数据的唯一方法是写入NativeContainer。

简单job定义的示例

// Job adding two floating point values together
public struct MyJob : IJob
{
    public float a;
    public float b;
    public NativeArray<float> result;

    public void Execute()
    {
        result[0] = a + b;
    }
}

3、调度Jobs

要在主线程中调度作业,您必须:

  • 实例化作业。
  • 填充作业的数据。
  • 调用Schedule方法。

调用Schedule将Job放入Job队列中以便在适当的时间执行。一旦调度,你就不能打断Job的运行。
注意:您只能在主线程调用Schedule。

调度Jobs的一个例子

// Create a native array of a single float to store the result. 
//This example waits for the job to complete for illustration purposes
NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);

// Set up the job data
MyJob jobData = new MyJob();
jobData.a = 10;
jobData.b = 10;
jobData.result = result;

// Schedule the job
JobHandle handle = jobData.Schedule();

// Wait for the job to complete
handle.Complete();

// All copies of the NativeArray point to the same memory, you can access the result in "your" copy of the NativeArray
float aPlusB = result[0];

// Free the memory allocated by the result array
result.Dispose();

4、JobHandle和依赖关系

JobHandle and dependencies

当您调用作业的Schedule方法时,它将返回JobHandle。您可以在代码中使用JobHandle 作为其他Job的依赖关系。如果Job取决于另一个Job的结果,您可以将第一个作业JobHandle作为参数传递给第二个作业的Schedule方法,如下所示:

JobHandle firstJobHandle = firstJob.Schedule();
secondJob.Schedule(firstJobHandle);

结合依赖关系
如果作业有许多依赖项,则可以使用JobHandle.CombineDependencies方法合并它们。CombineDependencies允许您将它们传递给Schedule方法。

NativeArray<JobHandle> handles = new NativeArray<JobHandle>(numJobs, Allocator.TempJob);

// Populate `handles` with `JobHandles` from multiple scheduled jobs...

JobHandle jh = JobHandle.CombineDependencies(handles);

在主线程中等待Job
在主线程中使用JobHandle强迫让你的代码等待您的Job执行完毕。要做到这一点,调用JobHandle的方法 Complete。此时,您知道主线程可以安全地访问正在使用job 的NativeContainer。

注意:在调度Job时,Job不会开始执行。如果您正在等待主线程中的Job,并且您需要访问正在使用Job的NativeContainer数据,则可以调用该方法JobHandle.Complete。此方法从内存高速缓存中刷新作业并启动执行过程。调用JobHandle的Complete方法将返回NativeContainer的所有权到主线程。您需要再次调用 JobHandle 的Complete方法以便于再次从主线程安全地访问这些NativeContainer类型。也可以通过从Job的依赖中的JobHandle的Complete方法调用返回主线程上的所有权。例如,你可以调用jobA的Complete方法,或者也可以调用依靠JobA的JobB上的Complete方法。两者都会在调用Complete后在主线程上安全访问时使用jobA的NativeContainer类型。

否则,如果您不需要访问数据,则需要明确刷新批处理。为此,请调用静态方法JobHandle.ScheduleBatchedJobs。请注意,调用此方法可能会对性能产生负面影响。

多个Jobs和dependencies的示例

// Job code:
// Job adding two floating point values together
public struct MyJob : IJob
{
    public float a;
    public float b;
    public NativeArray<float> result;

    public void Execute()
    {
        result[0] = a + b;
    }
}

// Job adding one to a value
public struct AddOneJob : IJob
{
    public NativeArray<float> result;
    
    public void Execute()
    {
        result[0] = result[0] + 1;
    }
}

主线程代码:
// Create a native array of a single float to store the result in. 
//This example waits for the job to complete
NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);

// Setup the data for job #1
MyJob jobData = new MyJob();
jobData.a = 10;
jobData.b = 10;
jobData.result = result;

// Schedule job #1
JobHandle firstHandle = jobData.Schedule();

// Setup the data for job #2
AddOneJob incJobData = new AddOneJob();
incJobData.result = result;

// Schedule job #2
JobHandle secondHandle = incJobData.Schedule(firstHandle);

// Wait for job #2 to complete
secondHandle.Complete();

// All copies of the NativeArray point to the same memory, you can access the result in "your" copy of the NativeArray
float aPlusB = result[0];

// Free the memory allocated by the result array
result.Dispose();
发布了97 篇原创文章 · 获赞 21 · 访问量 28万+

猜你喜欢

转载自blog.csdn.net/sun124608666/article/details/100771904