Unity Jobsystem

下面为你详细介绍 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通常实现IJobIJobParallelFor等接口。

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只能访问NativeArrayNativeList等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对象。
  • 只能用NativeArrayNativeListNativeSlice等。

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最佳实践

  1. 数据量大时用Job,小数据量用主线程即可
  2. Job只做数据处理,不做逻辑分支、IO、网络等操作
  3. 合理拆分Job,避免单个Job过大或过小
  4. 用Profiler分析Job瓶颈,调整Batch Size和依赖关系
  5. Job与Burst配合,性能最大化

八、参考资料


下面继续为你补充Unity Job System的更高级内容,包括:


九、Job System进阶:自定义NativeContainer

1. 为什么自定义NativeContainer?

  • 当Unity内置的NativeArrayNativeList等不能满足你的特殊数据结构需求时,可以自定义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编译后的汇编,分析性能瓶颈。

十五、常见问题与解决方案

  1. Job未Dispose导致内存泄漏

    • 解决:所有NativeContainer用完后都要Dispose。
  2. Job访问Unity对象崩溃

    • 解决:Job中只用NativeContainer,不访问GameObject等。
  3. Job结果未同步

    • 解决:主线程访问结果前必须handle.Complete()
  4. Job调度过多反而变慢

    • 解决:合理拆分Job,避免过多小Job,调整Batch Size。

十六、实用资源


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你一身傲骨怎能输

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值