Categories

Tags

Table of Contents

  1. Jobs in ECS
  2. Job extensions
  3. IJob
    1. IJobParallelFor

Jobs in ECS

ECS使用Job系统实施行为-ECS的System部分。具体而言,ECS系统就是一个用来 转换存储在实体组件中的数据 而创建的Job。

例如,以下系统更新positions:

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
using UnityEngine;

public class MovementSpeedSystem : JobComponentSystem
{
    [BurstCompile]
    struct MovementSpeedJob : IJobForEach<Position, MovementSpeed>
    {
        public float dT;

        public void Execute(ref Position Position, [ReadOnly] ref MovementSpeed movementSpeed)
        {
            float3 moveSpeed = movementSpeed.Value * dT;
            Position.Value = Position.Value + moveSpeed;
        }
    }

    // OnUpdate runs on the main thread.
    protected override JobHandle OnUpdate(JobHandle inputDependencies)
    {
        var job = new MovementSpeedJob()
        {
            dT = Time.deltaTime
        };

        return job.Schedule(this, inputDependencies);
    }
}

Job extensions

Unity C# Job System使您可以在多个线程上运行代码。该系统提供调度,并行处理和多线程安全性。Job System是Unity的核心模块,提供用于创建和运行job的通用接口和类(无论您是否使用ECS)。这些接口包括:

  • IJob:创建由Job System调度程序确定的可在任何线程或内核上运行的Job。
  • IJobParallelFor:创建一个可以在多个线程上并行运行的Job,以处理NativeContainer的元素。。
  • IJobExtensions:提供用于运行Jobs的扩展方法。
  • IJobParalllelForExtensions:提供用于运行IJobParallelFor作业的扩展方法。
  • JobHandle:用于访问预定作业的句柄。JobHandle实例还允许您指定Job之间的依赖关系。

有关Jobs系统的概述,请参见Unity手册中的C#Job System

Jobs package扩展了Job System以支持ECS。它包含了:

IJob

IJob允许您安排一个与其他job和主线程并行运行的job。安排作业后,将在工作线程上调用该job的Execute方法。返回的JobHandle可用于确保job已完成。或者可以将它作为依赖项传递给其他job,从而确保在工作线程上一个接一个地执行jobs。

using UnityEngine;
using Unity.Collections;
using Unity.Jobs;

class ApplyVelocitySample : MonoBehaviour
{
    struct VelocityJob : IJob
    {
        // Jobs declare all data that will be accessed in the job
        // By declaring it as read only, multiple jobs are allowed to access the data in parallel
        [ReadOnly]
        public NativeArray<Vector3> velocity;

        // By default containers are assumed to be read & write
        public NativeArray<Vector3> position;

        // Delta time must be copied to the job since jobs generally don't have concept of a frame.
        // The main thread waits for the job on the same frame or the next frame, but the job should
        // perform work in a deterministic and independent way when running on worker threads.
        public float deltaTime;

        // The code actually running on the job
        public void Execute()
        {
            // Move the positions based on delta time and velocity
            for (var i = 0; i < position.Length; i++)
                position[i] = position[i] + velocity[i] * deltaTime;
        }
    }

    public void Update()
    {
        var position = new NativeArray<Vector3>(500, Allocator.Persistent);

        var velocity = new NativeArray<Vector3>(500, Allocator.Persistent);
        for (var i = 0; i < velocity.Length; i++)
            velocity[i] = new Vector3(0, 10, 0);


        // Initialize the job data
        var job = new VelocityJob()
        {
            deltaTime = Time.deltaTime,
            position = position,
            velocity = velocity
        };

        // Schedule the job, returns the JobHandle which can be waited upon later on
        JobHandle jobHandle = job.Schedule();

        // Ensure the job has completed
        // It is not recommended to Complete a job immediately,
        // since that gives you no actual parallelism.
        // You optimally want to schedule a job early in a frame and then wait for it later in the frame.
        jobHandle.Complete();

        Debug.Log(job.position[0]);

        // Native arrays must be disposed manually
        position.Dispose();
        velocity.Dispose();
    }
}

IJobParallelFor

并行job可让您对nattive 容器的每个元素或固定数量的迭代执行相同的独立操作。

调度后,job的Execute(int index)方法将在彼此并行的多个工作线程上调用。

对于从0到提供的长度的每个索引,将执行一次Execute(int index)。每次迭代必须独立于其他迭代(安全系统会为您强制执行此规则)。索引没有保证的顺序,并且在多个内核上并行执行。

Unity自动将job分割成不少于提供的batchSize的块,并根据工作线程的数量,数组的长度和批处理的大小来调度适当数量的job。

通常应根据job中执行的工作量选择batch size。一个简单的job,例如互相添加几个Vector3的批处理大小可能应该在32到128之间。但是,如果执行的job非常昂贵,则最好使用小batch size,对于昂贵的工作,最好使用batch size为1完全可以。IJobParallelFor使用原子操作执行工作窃取。batch size可以很小,但不是免费的。

返回的JobHandle可用于确保job已完成。或者可以将它作为依赖项传递给其他作业,从而确保在工作线程上一个接一个地执行job。

using UnityEngine;
using Unity.Collections;
using Unity.Jobs;

class ApplyVelocityParallelForSample : MonoBehaviour
{
    struct VelocityJob : IJobParallelFor
    {
        // Jobs declare all data that will be accessed in the job
        // By declaring it as read only, multiple jobs are allowed to access the data in parallel
        [ReadOnly]
        public NativeArray<Vector3> velocity;

        // By default containers are assumed to be read & write
        public NativeArray<Vector3> position;

        // Delta time must be copied to the job since jobs generally don't have concept of a frame.
        // The main thread waits for the job same frame or next frame, but the job should do work deterministically
        // independent on when the job happens to run on the worker threads.
        public float deltaTime;

        // The code actually running on the job
        public void Execute(int i)
        {
            // Move the positions based on delta time and velocity
            position[i] = position[i] + velocity[i] * deltaTime;
        }
    }

    public void Update()
    {
        var position = new NativeArray<Vector3>(500, Allocator.Persistent);

        var velocity = new NativeArray<Vector3>(500, Allocator.Persistent);
        for (var i = 0; i < velocity.Length; i++)
            velocity[i] = new Vector3(0, 10, 0);

        // Initialize the job data
        var job = new VelocityJob()
        {
            deltaTime = Time.deltaTime,
            position = position,
            velocity = velocity
        };

        // Schedule a parallel-for job. First parameter is how many for-each iterations to perform.
        // The second parameter is the batch size,
        // essentially the no-overhead innerloop that just invokes Execute(i) in a loop.
        // When there is a lot of work in each iteration then a value of 1 can be sensible.
        // When there is very little work values of 32 or 64 can make sense.
        JobHandle jobHandle = job.Schedule(position.Length, 64);

        // Ensure the job has completed.
        // It is not recommended to Complete a job immediately,
        // since that reduces the chance of having other jobs run in parallel with this one.
        // You optimally want to schedule a job early in a frame and then wait for it later in the frame.
        jobHandle.Complete();

        Debug.Log(job.position[0]);

        // Native arrays must be disposed manually.
        position.Dispose();
        velocity.Dispose();
    }
}