下面为你详细介绍 Unity Job System 的原理、用法、最佳实践和进阶技巧,适合Unity 2020及以上版本,内容涵盖:
- Job System简介
- 基本用法与代码示例
- Burst Compiler简介
- Job System与主线程/多线程的关系
- Job System与ECS(Entities)关系
- Job System的调试与性能分析
- 实践建议与常见问题
一、Job System简介
Unity Job System 是 Unity 提供的多线程任务调度系统,允许开发者用安全、易用的方式将计算密集型任务分发到多个CPU核心,提高性能,减少主线程压力。
它的核心思想是:数据驱动、无锁、避免共享状态。
二、基本用法与代码示例
1. 定义Job
Job通常实现IJob
、IJobParallelFor
等接口。
IJob 示例
using Unity.Jobs;
using UnityEngine;
public struct MyJob : IJob
{
public int a;
public int b;
public NativeArray<int> result;
public void Execute()
{
result[0] = a + b;
}
}
IJobParallelFor 示例(并行处理数组)
using Unity.Jobs;
using Unity.Collections;
public struct MyParallelJob : IJobParallelFor
{
[ReadOnly] public NativeArray<float> input;
public NativeArray<float> output;
public void Execute(int index)
{
output[index] = input[index] * 2f;
}
}
2. 调度与完成
void Start()
{
NativeArray<int> result = new NativeArray<int>(1, Allocator.TempJob);
MyJob job = new MyJob { a = 1, b = 2, result = result };
JobHandle handle = job.Schedule();
handle.Complete();
Debug.Log(result[0]); // 输出3
result.Dispose();
}
并行Job调度
NativeArray<float> input = new NativeArray<float>(100, Allocator.TempJob);
NativeArray<float> output = new NativeArray<float>(100, Allocator.TempJob);
MyParallelJob job = new MyParallelJob { input = input, output = output };
JobHandle handle = job.Schedule(input.Length, 10); // 10为batch size
handle.Complete();
output.Dispose();
input.Dispose();
三、Burst Compiler简介
Burst Compiler 是Unity的高性能编译器,可将Job代码编译为高效的本地机器码,极大提升性能。
使用方法:
- 安装Burst包(Package Manager)
- 在Job代码前加
[BurstCompile]
特性
using Unity.Burst;
[BurstCompile]
public struct MyJob : IJob { ... }
四、Job System与主线程/多线程的关系
- Job System自动管理线程池,开发者无需手动创建线程。
- Job只能访问
NativeContainer
(如NativeArray),不能访问Unity主线程对象(如GameObject、Transform)。 - Job调度和依赖管理由Unity自动完成。
五、Job System与ECS(Entities)关系
- Job System是ECS的底层基础,ECS中的System通常用Job处理数据。
- Job System可独立于ECS使用,也可与ECS结合实现极致性能。
六、Job System的调试与性能分析
- Profiler:Unity Profiler可查看Job调度、执行、等待等信息。
- Job Debugger:可在Editor下检测Job中的数据访问越界、未Dispose等问题。
- Burst Inspector:可查看Burst编译后的汇编代码。
七、实践建议与常见问题
1. 只用NativeContainer
Job只能访问NativeArray
、NativeList
等NativeContainer,不能访问普通C#对象或UnityEngine对象。
2. Job依赖管理
多个Job有依赖时,可用JobHandle
串联,或用JobHandle.CombineDependencies
。
3. Job生命周期
Job执行完后,必须手动Dispose所有NativeContainer,否则会内存泄漏。
4. Job与主线程同步
handle.Complete()
会阻塞主线程直到Job完成。- 可用
handle.IsCompleted
判断是否完成。
5. Job嵌套与调度
- Job不能在Job内部调度新Job。
- Job调度应在主线程进行。
6. Job与Burst兼容
- Job代码应避免使用不支持的C#特性(如异常、虚函数、GC分配等)。
八、进阶:Job System常用接口
IJob
:单任务IJobParallelFor
:并行数组IJobParallelForTransform
:并行Transform(需TransformAccessArray)IJobChunk
:ECS专用IJobForEach
:ECS专用
九、参考资料
下面继续深入讲解 Unity Job System 的进阶内容,包括:
- Job依赖与组合
- Job与主线程数据交互
- Job调度优化与批量处理
- Job System常见陷阱与调试技巧
- Job System与Burst的协同优化
- Job System与ECS的结合(简单案例)
一、Job依赖与组合
1. Job依赖(JobHandle)
如果你有多个Job,且它们之间有先后依赖关系,可以通过JobHandle
来管理。
示例:A Job完成后再执行B Job
MyJobA jobA = new MyJobA { ... };
JobHandle handleA = jobA.Schedule();
MyJobB jobB = new MyJobB { ... };
JobHandle handleB = jobB.Schedule(handleA); // 依赖handleA
handleB.Complete(); // 等待B完成(A也会自动完成)
2. 多Job依赖合并
JobHandle handleA = jobA.Schedule();
JobHandle handleB = jobB.Schedule();
JobHandle combined = JobHandle.CombineDependencies(handleA, handleB);
MyJobC jobC = new MyJobC { ... };
JobHandle handleC = jobC.Schedule(combined);
二、Job与主线程数据交互
1. Job只能访问NativeContainer
- 不能在Job中直接访问GameObject、Transform、MonoBehaviour等Unity对象。
- 只能用
NativeArray
、NativeList
、NativeSlice
等。
2. Job结果回传主线程
- Job执行完后,主线程读取NativeArray等结果。
- 典型流程:主线程写入数据 → Job处理 → 主线程读取结果 → Dispose
示例
NativeArray<int> result = new NativeArray<int>(1, Allocator.TempJob);
MyJob job = new MyJob { result = result };
JobHandle handle = job.Schedule();
handle.Complete();
int value = result[0]; // 读取结果
result.Dispose();
三、Job调度优化与批量处理
1. IJobParallelFor的Batch Size
job.Schedule(length, batchSize)
,batchSize越大,线程切换开销越小,但并行度降低。- 推荐:数据量大时用较小batchSize(如32、64),数据量小时用较大batchSize。
2. Job调度时机
- Job调度应在Update、LateUpdate等主线程生命周期内。
- Job调度后,只有在
Complete()
时才会阻塞主线程。
3. Job嵌套与递归
- Job不能在Job内部再调度Job。
- Job只能在主线程调度。
四、Job System常见陷阱与调试技巧
1. NativeContainer未Dispose
- Job用完后,必须手动Dispose,否则会内存泄漏。
- Editor下会有警告,Release下不会。
2. 数据越界/未初始化
- Job访问NativeArray时,越界会崩溃。
- Editor下开启Job Debugger可检测。
3. Job未Complete导致数据未同步
- 如果主线程提前访问Job结果,数据可能未写入。
- 必须在访问前
handle.Complete()
。
4. Job与Burst调试
- Burst编译后,调试信息有限。可临时关闭Burst(菜单 Jobs > Burst > Enable Compilation)。
- 用Burst Inspector查看汇编。
五、Job System与Burst的协同优化
- Job代码加
[BurstCompile]
,可极大提升性能。 - 尽量避免在Job中分配内存、使用托管对象、异常、虚函数等。
- Job中可用
math
库(Unity.Mathematics)替代Mathf
,性能更高。
示例
using Unity.Burst;
using Unity.Mathematics;
[BurstCompile]
public struct MyJob : IJobParallelFor
{
public NativeArray<float3> positions;
public void Execute(int i)
{
positions[i] = math.normalize(positions[i]);
}
}
六、Job System与ECS的结合(简单案例)
ECS(Entities)系统的System通常用Job处理数据,极致提升性能。
示例:ECS System中调度Job
using Unity.Entities;
using Unity.Jobs;
using Unity.Burst;
using Unity.Collections;
[BurstCompile]
public struct MoveJob : IJobChunk
{
public float deltaTime;
public ArchetypeChunkComponentType<Translation> translationType;
public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
{
var translations = chunk.GetNativeArray(translationType);
for (int i = 0; i < chunk.Count; i++)
{
translations[i] = new Translation { Value = translations[i].Value + new float3(0, 1, 0) * deltaTime };
}
}
}
public class MoveSystem : JobComponentSystem
{
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var job = new MoveJob
{
deltaTime = Time.DeltaTime,
translationType = GetArchetypeChunkComponentType<Translation>()
};
return job.Schedule(m_Group, inputDeps);
}
}
七、Job System最佳实践
- 数据量大时用Job,小数据量用主线程即可。
- Job只做数据处理,不做逻辑分支、IO、网络等操作。
- 合理拆分Job,避免单个Job过大或过小。
- 用Profiler分析Job瓶颈,调整Batch Size和依赖关系。
- Job与Burst配合,性能最大化。
八、参考资料
下面继续为你补充Unity Job System的更高级内容,包括:
九、Job System进阶:自定义NativeContainer
1. 为什么自定义NativeContainer?
- 当Unity内置的
NativeArray
、NativeList
等不能满足你的特殊数据结构需求时,可以自定义NativeContainer。 - 例如:需要线程安全的队列、栈、哈希表等。
2. 自定义NativeContainer的基本步骤
- 继承
System.IDisposable
,实现Dispose。 - 用
[NativeContainer]
、[NativeContainerSupportsDeallocateOnJobCompletion]
等属性标记。 - 内部用
UnsafeUtility
分配/释放内存。 - 必须实现安全检查(如
AtomicSafetyHandle
)。
示例(简化版,实际需更多安全检查):
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Burst;
using System;
[NativeContainer]
public struct NativeSimpleInt : IDisposable
{
[NativeDisableUnsafePtrRestriction]
private IntPtr m_Buffer;
private Allocator m_Allocator;
public NativeSimpleInt(Allocator allocator)
{
m_Allocator = allocator;
m_Buffer = (IntPtr)UnsafeUtility.Malloc(sizeof(int), 4, allocator);
UnsafeUtility.WriteArrayElement<int>(m_Buffer.ToPointer(), 0, 0);
}
public int Value
{
get => UnsafeUtility.ReadArrayElement<int>(m_Buffer.ToPointer(), 0);
set => UnsafeUtility.WriteArrayElement<int>(m_Buffer.ToPointer(), 0, value);
}
public void Dispose()
{
UnsafeUtility.Free(m_Buffer.ToPointer(), m_Allocator);
m_Buffer = IntPtr.Zero;
}
}
十、Job System进阶:复杂依赖链与多阶段流水线
1. 多阶段流水线(Pipeline)
有时需要将大任务拆分为多个阶段,每个阶段用Job处理,阶段之间有依赖。
示例:三阶段流水线
JobHandle handleA = jobA.Schedule();
JobHandle handleB = jobB.Schedule(handleA);
JobHandle handleC = jobC.Schedule(handleB);
handleC.Complete();
2. 多Job并行+合并
多个Job并行处理,最后一个Job收集所有结果。
JobHandle[] handles = new JobHandle[3];
handles[0] = jobA.Schedule();
handles[1] = jobB.Schedule();
handles[2] = jobC.Schedule();
JobHandle combined = JobHandle.CombineDependencies(handles);
finalJob.Schedule(combined).Complete();
十一、Job System进阶:Job调度器自定义与嵌套
1. Job调度器自定义
你可以自己封装Job调度器,自动管理Job依赖、调度和回收。
示例:简单Job调度管理器
public class JobManager
{
private List<JobHandle> handles = new List<JobHandle>();
public void Schedule(JobHandle handle)
{
handles.Add(handle);
}
public void CompleteAll()
{
foreach (var handle in handles)
handle.Complete();
handles.Clear();
}
}
十二、Job System进阶:性能对比与优化建议
1. 何时用Job System?
- 数据量大、计算密集型任务(如物理、AI、路径、批量数学运算)。
- 任务可拆分为无依赖的子任务。
- 任务不涉及Unity主线程对象。
2. Job System vs 传统C#多线程
特性 | Job System | 传统C#多线程 |
---|---|---|
线程管理 | Unity自动 | 手动 |
数据安全 | NativeContainer | 需自己加锁 |
性能 | 高(无锁、无GC) | 取决于实现 |
Unity对象访问 | 不可 | 不可 |
调试 | 较易 | 较难 |
3. 性能优化建议
- 尽量用
IJobParallelFor
等并行接口。 - 合理设置Batch Size。
- Job中只做数据处理,避免分支和复杂逻辑。
- 用Burst编译器。
- 用Profiler分析瓶颈。
十三、Job System进阶:与ECS深度结合
1. ECS System中调度Job
ECS的System可以直接调度Job,处理Entity数据。
示例:ECS + Job + Burst
[BurstCompile]
public struct MoveJob : IJobChunk
{
public float deltaTime;
public ArchetypeChunkComponentType<Translation> translationType;
public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
{
var translations = chunk.GetNativeArray(translationType);
for (int i = 0; i < chunk.Count; i++)
{
translations[i] = new Translation { Value = translations[i].Value + new float3(0, 1, 0) * deltaTime };
}
}
}
public class MoveSystem : JobComponentSystem
{
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var job = new MoveJob
{
deltaTime = Time.DeltaTime,
translationType = GetArchetypeChunkComponentType<Translation>()
};
return job.Schedule(m_Group, inputDeps);
}
}
十四、Job System进阶:调试与分析
1. Profiler分析
- 查看Job Timeline,分析Job调度、执行、等待时间。
- 检查是否有主线程阻塞。
2. Job Debugger
- Editor下可开启Job Debugger,检测数据越界、未Dispose等问题。
3. Burst Inspector
- 检查Burst编译后的汇编,分析性能瓶颈。
十五、常见问题与解决方案
-
Job未Dispose导致内存泄漏
- 解决:所有NativeContainer用完后都要Dispose。
-
Job访问Unity对象崩溃
- 解决:Job中只用NativeContainer,不访问GameObject等。
-
Job结果未同步
- 解决:主线程访问结果前必须
handle.Complete()
。
- 解决:主线程访问结果前必须
-
Job调度过多反而变慢
- 解决:合理拆分Job,避免过多小Job,调整Batch Size。
十六、实用资源
- Unity官方Job System文档
- Unity Burst Compiler文档
- Unity DOTS/ECS官方文档
- Unity C# Job System and Burst Compiler | 官方教程