Unity简单使用Job System

1.定义一个Job结构体

首先,需要定义一个struct继承接口IJob。

注意:Job数据只接受引用类型的数据,无法使用类(calss)的数据。

public struct MyJob : IJob
{
    public float a;
    public float b;
    public NativeArray<float> result;

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

在这个结构体MyJob中进行需要进行的计算并将结果存储于NativeContainer中,Unity附带一个NativeContainer名为NativeArray的程序。

您还可以使用NativeSlice操作一个NativeArray来获取NativeArray从指定位置到指定长度的子集。

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

  • NativeList- 可调整大小的NativeArray。
  • NativeHashMap - 键值对。
  • NativeMultiHashMap - 每个键有多个值。
  • NativeQueue- 先进先出(FIFO)队列。

2.实例化Job并填充数据

// 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;

实例化Job后需要对结构体中的数据赋值,并对要保存结果的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)。

3.调用Schedule方法

// 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();

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

最终的结果需要从结构体的NativeContainer中读取出来。请及时使用Dispose释放内存。

4.Job依赖

如果需要使用多个Job,且需要用到前一个或几个Job的数据,则需要使用到Job依赖。

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

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

如果Job有许多依赖项,则可以使用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 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;
    }
}

 

Main Code:

// 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();

接口:

定义Job的结构体时,有3种不同的接口用于不同计算:

IJob,IJobParallelFor,IJobParallelForTransform

IJob:

允许调度一个与主线程和其他Jobs并行的单独的Job。当Job被列入工作计划时,Job的Execute方法将在一个工作线程上调用。返回的Job句柄可用于确保Job已完成。或者可以将其作为依赖项传递给其他Job,从而确保Job在工作线程上一个接一个地执行。

IJobParallelFor:

允许对本机NativeContainer的每个元素或固定次数的迭代执行相同的独立操作。当计划作业时,Execute(int index)方法将在多个工作线程上并行调用。Execute(int index)将对从0到指定的长度的每个索引执行一次。每个迭代必须独立于其他迭代(安全系统为您执行此规则)。索引没有保证顺序,而是在多个核上并行执行。

IJobParallelForTransform:

Unity的文档中没有详细介绍,只写了名字

在从Package Manager中加载了Jobs包后,Unity增加了三个新的接口IJobParallelForBatch、IJobParallelForFilter和IJobParallelForDefer,不过它们现在是不安全的(Unity说的,等待以后完善)。

IJobParallelForBatch:

它的工作原理类似于IJobParallelFor,但是它不是为每个索引调用一次执行函数,而是每个执行批次调用一次执行函数。如果您需要同时处理多个项目,但仍然希望并行处理,那么这是非常有用的。此Job类型的常见场景是,如果需要创建一个临时数组,并且希望避免一次创建数组中的每个项。通过使用IJobParallelFor,您可以为每个批创建一个临时数组。

IJobParallelForFilter和IJobParallelForDefer还没有具体文档。

 

 

发布了7 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/liquanyi007/article/details/90174601