Unity Term Book
Advanced Techniques

Job System & Burst

Unity C# Job System cho phép viết code multithreaded an toàn — kết hợp Burst Compiler để sinh mã native SIMD cực nhanh cho các phép tính nặng (heavy compute).

Hãy tưởng tượng...

Main Thread giống như một đầu bếp duy nhất làm tất cả công việc. Job System là dây chuyền nhà bếp công nghiệp: mỗi worker thread làm một nhiệm vụ độc lập (thái rau, nấu cơm, chiên thịt) song song nhau. Đầu bếp chính chỉ cần "lên lịch" và "thu kết quả". Burst Compiler là như thay tất cả dao thủ công bằng máy CNC — tự động tối ưu code C# thành SIMD assembly instructions, thực thi 10-100× nhanh hơn.

Khái niệm chi tiết

C# Job System: Unity không cho phép truy cập UnityEngine objects (Transform, MonoBehaviour) từ thread khác vì thread-safety. Job System giải quyết bằng cách: data phải nằm trong NativeArray<T> (bộ nhớ unmanaged, shared giữa threads). Job struct implement interface IJob, IJobParallelFor, hoặc IJobParallelForTransform. Job được schedule → chạy trên worker threads → complete trước khi đọc kết quả.

Burst Compiler là LLVM-based compiler nhận IL code và output high-performance native code với SIMD (Single Instruction Multiple Data) instructions — xử lý nhiều float cùng lúc (SSE2, AVX2, NEON). Chỉ cần thêm attribute [BurstCompile] lên job struct. Burst không hỗ trợ managed objects, exceptions, hay reference types — chỉ value types và pointers.

NativeArray<T> là container unmanaged memory: không có GC overhead, có thể đọc/ghi từ nhiều thread (với constraints). Phải Dispose() khi xong để tránh memory leak. NativeContainer safety checks trong Editor phát hiện race conditions tự động.

Workflow: Schedule() → job được enqueue vào Unity job queue; Complete() → chờ job xong trước khi đọc kết quả (blocking). Dùng dependency chains (truyền JobHandle vào Schedule() tiếp theo) để chain nhiều job mà không cần Complete() sớm. ECS + DOTS được xây hoàn toàn trên Job System

  • Burst — đó là lý do hiệu năng ECS vượt trội.

Threading Model: Single Thread vs Job System

❌ Không dùng Jobs (Single Thread)

Main
Physics → AI → Render → Input → ...
Worker 1
idle idle idle idle...
Worker 2
idle idle idle idle...
Worker 3
idle idle idle idle...

Frame time: 16.7ms → Main thread bottleneck

✅ Với Job System (Multithreaded)

Main
Input+Render
Complete+Apply
Worker 1
Physics Batch Job
Worker 2
AI Pathfinding Job
Worker 3
Procedural Mesh Job

Frame time giảm xuống ~5ms — tận dụng tất cả CPU cores

Hướng dẫn thực hành

1

Cài đặt Burst package

Package Manager → tìm "Burst" → Install. Job System đã built-in Unity 2018+. Thêm using Unity.Jobs, Unity.Collections.

2

Tạo Job struct với IJob

struct MyJob : IJob { public NativeArray data; public void Execute() { ... } } — không có MonoBehaviour, không có reference types.

3

Allocate NativeArray và Schedule

new NativeArray(count, Allocator.TempJob) → new MyJob { data = array } → .Schedule() → lưu JobHandle.

4

Complete và Dispose

handle.Complete() trong LateUpdate. Đọc kết quả từ NativeArray. array.Dispose() để giải phóng unmanaged memory.

5

Thêm [BurstCompile]

Thêm [BurstCompile] attribute lên struct — Jobs menu → Open Inspector để xem disassembly. Burst hiển thị màu xanh trong Profiler khi active.

Trình mô phỏng tương tác

So sánh tốc độ tính toán: Main Thread vs Job System vs Job + Burst.

Main Thread
--
Job System
--
Job + Burst
--

Nhấn Run để benchmark. (Mô phỏng dựa trên thực đo Unity, không phải đo thật)

Ví dụ Code

Cơ bản

IJobParallelFor — xử lý N element song song trên nhiều thread.

using Unity.Jobs;
using Unity.Collections;
using Unity.Burst;
using Unity.Mathematics;
using UnityEngine;

// [BurstCompile] → compile sang SIMD native code
[BurstCompile]
struct CalculatePositionsJob : IJobParallelFor
{
  [ReadOnly] public NativeArray<float3> velocities;
  public NativeArray<float3> positions; // Ghi kết quả vào đây
  public float deltaTime;

  // Execute chạy song song cho từng index
  public void Execute(int index)
  {
      positions[index] += velocities[index] * deltaTime;
  }
}

public class FlockSimulation : MonoBehaviour
{
  const int COUNT = 10000;
  NativeArray<float3> positions, velocities;
  JobHandle jobHandle;

  void Awake()
  {
      positions  = new NativeArray<float3>(COUNT, Allocator.Persistent);
      velocities = new NativeArray<float3>(COUNT, Allocator.Persistent);
  }

  void Update()
  {
      jobHandle.Complete(); // Chờ job frame trước xong
      jobHandle = new CalculatePositionsJob
      {
          velocities = velocities,
          positions  = positions,
          deltaTime  = Time.deltaTime
      }.Schedule(COUNT, 64); // 64 = batch size per thread
  }

  void OnDestroy()
  {
      jobHandle.Complete();
      positions.Dispose();
      velocities.Dispose();
  }
}

Ví dụ Code

Nâng cao

Job dependency chain — nhiều job nối tiếp nhau không cần Complete() giữa chừng.

using Unity.Jobs;
using Unity.Collections;
using Unity.Burst;

// Hai job chạy nối tiếp qua dependency — không block main thread giữa chừng
void ScheduleChain(NativeArray<float> data)
{
  // Job 1: tính bình phương
  var squareJob = new SquareJob { data = data };
  JobHandle squareHandle = squareJob.Schedule(data.Length, 32);

  // Job 2: cần job 1 xong mới chạy → truyền handle làm dependency
  var sumJob = new SumJob { data = data };
  JobHandle sumHandle = sumJob.Schedule(squareHandle); // dependency!

  // Main thread không bị block. Cả chain chạy parallel với render...
  // ...đến LateUpdate mới cần kết quả:
  sumHandle.Complete();
  Debug.Log($"Sum of squares: {sumJob.result[0]}");
}

// Nhiều job song song rồi merge:
void ScheduleParallel()
{
  var jobA = new JobA().Schedule();
  var jobB = new JobB().Schedule();
  // CombineDependencies: chờ CẢ HAI xong mới chạy jobC
  var combined = JobHandle.CombineDependencies(jobA, jobB);
  var jobC = new JobC().Schedule(combined);
  jobC.Complete();
}

📌 Ghi nhớ nhanh

  • Job = struct, không dùng class/managed types
  • NativeArray: unmanaged, phải Dispose() khi xong
  • [BurstCompile] → SIMD native code, 10-100× faster
  • Dependency chain: không Complete() giữa chừng
  • Allocator.TempJob: tối đa 4 frames; Persistent: dài hạn

⚠️ Lỗi thường gặp

  • ❌ NativeArray not disposed warning

    Quên Dispose() → native memory leak không được GC thu hồi

    ✅ Dispose trong OnDestroy, hoặc dùng Allocator.TempJob với using

  • ❌ InvalidOperationException: The previously scheduled job ... writes...

    Đọc NativeArray từ main thread trong khi job chưa Complete()

    ✅ Gọi handle.Complete() trước khi truy cập NativeArray từ main thread