Unity中 JobSystem使用整理_unity jobsystem
Unity 的JobSystem允许创建多线程代码,以便应用程序可以使用所有可用的 CPU 内核来执行代码,这提供了更高的性能,因为您的应用程序可以更高效地使用运行它的所有 CPU 内核的容量,而不是在一个 CPU 内核上运行所有代码。
可以单独使用JobSystem,为了提高性能,可以和Burst 编译器一块使用,Burst 编译器改进了代码生成,从而提高了移动设备的性能并减少了电池消耗。
还可以将JobSystem与 Unity 的实体组件系统结合使用,以创建高性能的面向数据的代码。
概念:
单线程:一次执行一条指令,并且一次产生一个结果。加载和完成程序的时间取决于 CPU 需要完成的工作量。
多线程:利用了 CPU 在多个核心上同时处理多个线程的能力。这种情况下不是逐个执行任务或指令,而是同时运行任务或指令。
什么是多线程? - Unity 手册
JobSystem:通过创建Job而不是线程来管理多线程代码,将创建的Job放入Job队列中等待执行,工作线程从Job队列中获取并执行Job,用依赖关系来确保Job用适当的顺序执行。
Job:完成一项特定任务的一个小工作单位,会接收参数并对数据进行操作,可以是独立的,也可依赖其他作业完成之后才能运行。
Job依赖关系: 如果jobA依赖于jobB,系统将确保jobA不会在jobB完成之前开始执行。
什么是作业系统? - Unity 手册
优势:
1.多线程并行计算:利用多核CPU提升性能。
2.无GC分配:使用 Native 容器避免托管堆分配。
3.Burst 编译优化:生成高效原生代码(性能提升5-10倍)。
使用:
1.安装
通过Package Manager添加
通过Git URL地址搜索Job System包
com.unity.jobs:Job System 的核心功能包。
搜索到Jobs包后点击右下角Import
2.JobSystem使用入门:
1.NativeContainer:
1.核心容器:NativeArray 、NativeSlice
2.特殊数据结构容器:NativeBitArray
3.动态容器:NativeList、NativeQueue
4.并行安全容器:
NativeParallelHashMap
NativeParallelHashSet
NativeParallelMultiHashMap
5.迭代器:
NativeParallelMultiHashMapIterator
2.第一个Job例子:
1.创建Job:
1.创建实现 IJob 的结构。
2.添加该作业使用的成员变量(为 blittable 类型和 NativeContainer 类型之一)。
3.在结构中创建一个名为 Execute 的方法,并在其中实现该作业。
public struct MyJob : IJob { public float a; public float b; public NativeArray result; public void Execute() { result[0] = new Vector3(a,0,b); } }
2.运行Job:
1.实例化该作业。
2.填充作业的数据。
3.调用 Schedule 方法,会将该作业放入作业队列中,以便在适当的时间执行。
NativeArray result; JobHandle handle; void Update() { result = new NativeArray(1, Allocator.TempJob); MyJob jobData = new MyJob { a = 10, b = 10, result = result }; handle = jobData.Schedule(); } private void LateUpdate() { handle.Complete(); Debug.Log(result[0]); result.Dispose(); }
结果:
3.JobHandle 和依赖项:
调用Job的 Schedule 方法时,将返回 JobHandle。可以在代码中使用 JobHandle 作为其他Job的依赖项,一个Job可以依赖于多个Job,在其所依赖的Job完成之前,JobSystem不会运行此Job。
JobHandle firstJobHandle = firstJob.Schedule();secondJob.Schedule(firstJobHandle);
合并依赖项:使用JobHandle.CombineDependencies合并多个依赖项。
NativeArray handles = new NativeArray(numJobs, Allocator.TempJob);JobHandle jh = JobHandle.CombineDependencies(handles);
多个依赖项执行例子:
1.在定义新的Job:
public struct MyJob : IJob { public float a; public float b; public NativeArray result; public void Execute() { for (int i = 0; i< 100; i++) { a += 1; b += 1; } result[0] = new Vector3(a + 100, 0, b + 100); } } // 将一个值加一的作业 public struct AddOneJob : IJob { public NativeArray result; public void Execute() { result[0] = result[0] + Vector3.one; } }
2.调度Job:
NativeArray result; JobHandle handle; JobHandle firstHandle; void Update() { result = new NativeArray(1, Allocator.TempJob); MyJob jobData = new MyJob { a = 10, b = 10, result = result }; firstHandle = jobData.Schedule(); AddOneJob incJobData = new AddOneJob(); incJobData.result = result; // 调度作业 #2 handle = incJobData.Schedule(firstHandle); } private void LateUpdate() { handle.Complete(); Debug.Log(result[0]); result.Dispose(); }
结果:
4.并行Job:
在调度 ParallelFor 作业时,必须指定要拆分的 NativeArray 数据源的长度(表示有多少个 Execute 方法)。如果结构中有多个 NativeArray,无法确定将哪个用作数据源。
C# JobSystem将Job分成多个批次以便在多个核心之间分配任务,每个批次包含一小部分 Execute 方法,然后,JobSystem在 Unity 的Native Job System中为每个 CPU 内核安排一个Job,并将该Native Job传递给要完成的批次,如下图所示:
1.ParallelFor Job:
定义一个并行Job:
public struct MyParallelJob : IJobParallelFor{ [ReadOnly] public NativeArray a; [ReadOnly] public NativeArray b; public NativeArray result; public void Execute(int i) { result[i] = a[i] + b[i]; }}
调度Job:
public class ParallelJobTest : MonoBehaviour{ NativeArray a; NativeArray b; NativeArray result; JobHandle handle; // Update is called once per frame void Update() { a = new NativeArray(3, Allocator.TempJob) { [0] = 1, [1] = 2, [2] = 3 }; b = new NativeArray(3, Allocator.TempJob) { [0] = 100, [1] = 200, [2] = 111 }; result = new NativeArray(3, Allocator.TempJob); MyParallelJob jobData = new MyParallelJob(); jobData.a = a; jobData.b = b; jobData.result = result; // Schedule the job with one Execute per index in the results array and only 1 item per processing batch handle = jobData.Schedule(result.Length, 1); // Wait for the job to complete handle.Complete(); Debug.Log($\"{result[0]},{result[1]},{result[2]}\"); // Free the memory allocated by the arrays a.Dispose(); b.Dispose(); result.Dispose(); }}
结果:
2.ParallelForTransform Job:
ParallelForTransform Job允许对传递到作业中的所有转换的每个位置、旋转和缩放执行相同的独立作。
定义一个ParallelForTransform Job:
public struct VelocityJob : IJobParallelForTransform{ [ReadOnly] public NativeArray velocity; public float deltaTime; public void Execute(int index, TransformAccess transform) { var pos = transform.position; pos += velocity[index] * deltaTime; transform.position = pos; }}
调度Job:
public class ParallelForTransformTest : MonoBehaviour{ [SerializeField] public Transform[] m_Transforms; TransformAccessArray m_AccessArray; // Start is called before the first frame update void Start() { m_AccessArray = new TransformAccessArray(m_Transforms); } // Update is called once per frame void Update() { var velocity = new NativeArray(m_Transforms.Length, Allocator.Persistent); for (var i = 0; i < velocity.Length; ++i) velocity[i] = new Vector3(0f, 10f, 0f); var job = new VelocityJob() { deltaTime = Time.deltaTime, velocity = velocity }; JobHandle jobHandle = job.Schedule(m_AccessArray); jobHandle.Complete(); Debug.Log(m_Transforms[0].position); velocity.Dispose(); } private void OnDestroy() { m_AccessArray.Dispose(); }}
结果:
3.注意事项:
1.不要从Job访问静态数据。
2.不要更新 NativeContainer 内容。
3.调用 JobHandle.Complete 以重新获得所有权。
4.在适当时间使用 Schedule 和 Complete 方法。
5.将 NativeContainer 类型标记为只读。
6.不要在Job中分配托管内存。
C# 作业系统提示和故障排除 - Unity 手册
参考链接:
什么是作业系统? - Unity 手册
ArtStation - Unity Job System in Practice. How we increased FPS from 15 to 70 in our game