Skip to content

Unity Job System & Burst

Unity의 Job System은 멀티스레드 연산을 안전하게 작성할 수 있는 공식 API입니다. 여기에 Burst Compiler를 결합하면 C# 코드를 LLVM 기반 네이티브 코드로 컴파일해 SIMD 수준의 성능을 얻을 수 있습니다.


기존 ThreadTask는 Unity의 주요 API(Transform, Physics 등)에 스레드 안전하게 접근할 수 없습니다. Job System은 다음을 보장합니다.

  • 데이터 경쟁 방지 — 읽기/쓰기 선언을 컴파일 타임에 검사
  • Worker Thread 풀 자동 관리 — 플랫폼별 코어 수에 맞게 자동 분배
  • Burst 친화적 설계NativeContainer 기반 데이터 레이아웃

인터페이스용도
IJob단일 작업 단위
IJobParallelFor인덱스 기반 병렬 반복
IJobFor스케줄 옵션을 세밀하게 제어하는 병렬 반복

IJobParallelFor — 대량 오브젝트 이동

Section titled “IJobParallelFor — 대량 오브젝트 이동”
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
// Job 구조체 — 힙 할당 없이 스택에서 동작
public struct MoveJob : IJobParallelFor
{
public NativeArray<Vector3> positions;
public float deltaTime;
public float speed;
public void Execute(int index)
{
positions[index] += Vector3.forward * speed * deltaTime;
}
}
public class BulletSystem : MonoBehaviour
{
NativeArray<Vector3> _positions;
JobHandle _jobHandle;
void Start()
{
_positions = new NativeArray<Vector3>(10000, Allocator.Persistent);
}
void Update()
{
var job = new MoveJob
{
positions = _positions,
deltaTime = Time.deltaTime,
speed = 20f,
};
// innerloopBatchCount: 한 워커에 한 번에 할당할 인덱스 수
_jobHandle = job.Schedule(_positions.Length, 64);
}
void LateUpdate()
{
// 메인 스레드에서 결과 사용 전 반드시 Complete 호출
_jobHandle.Complete();
for (int i = 0; i < _positions.Length; i++)
transform.GetChild(i).position = _positions[i];
}
void OnDestroy()
{
_jobHandle.Complete();
_positions.Dispose();
}
}

Job System은 관리형 배열 대신 NativeContainer를 사용합니다. 관리형 메모리에 접근하면 Burst가 최적화할 수 없고, 스레드 안전성도 보장되지 않기 때문입니다.

컨테이너특징
NativeArray<T>고정 크기 배열, 가장 기본
NativeList<T>동적 크기 (Unity.Collections 패키지)
NativeHashMap<K,V>해시맵
NativeQueue<T>선입선출 큐
// Temp — 한 프레임 이내 사용 후 Dispose
NativeArray<int> temp = new NativeArray<int>(100, Allocator.Temp);
// TempJob — Job 내부에서 사용, 4프레임 이내 Dispose
NativeArray<int> tempJob = new NativeArray<int>(100, Allocator.TempJob);
// Persistent — 장기 보관, OnDestroy에서 반드시 Dispose
NativeArray<int> persistent = new NativeArray<int>(100, Allocator.Persistent);

[BurstCompile] 어트리뷰트를 붙이면 해당 Job을 Burst가 LLVM IR로 컴파일합니다.

using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
[BurstCompile]
public struct SumJob : IJob
{
[ReadOnly] public NativeArray<float> values;
[WriteOnly] public NativeArray<float> result;
public void Execute()
{
float sum = 0f;
for (int i = 0; i < values.Length; i++)
sum += values[i];
result[0] = sum;
}
}
제약이유
관리형 객체 사용 불가 (class, string)GC 힙 접근 불가
static 변수 읽기 제한공유 상태 → 경쟁 조건
예외(Exception) 불가네이티브 코드에서 CLR 예외 처리 불가
try/catch 불가위와 동일
Debug.Log 제한UnityEngine.Debug는 관리형

Debug.Log 대신 Unity.Burst.Debug 또는 BurstDiscard 어트리뷰트를 사용합니다.

[BurstCompile]
public struct DebugJob : IJob
{
public void Execute()
{
// Burst 빌드 시 이 메서드 호출이 제거됨
LogMessage();
}
[BurstDiscard]
static void LogMessage() => Debug.Log("디버그 전용");
}

Unity Profiler에서 Jobs 탭을 열면 워커 스레드별 Job 실행 시간을 확인할 수 있습니다.

Profiler → Jobs 탭 → 워커별 타임라인 확인
Burst Inspector (Jobs > Burst > Open Inspector) → 생성된 어셈블리 확인
// 너무 작으면 스케줄 오버헤드 증가, 너무 크면 병렬화 효율 감소
// 일반적으로 32~128 사이에서 Profiler로 측정하며 조정
_jobHandle = job.Schedule(count, 64);

// ❌ Complete 전에 NativeArray 접근 → InvalidOperationException
_jobHandle = job.Schedule(...);
Debug.Log(_positions[0]); // 위험!
// ✅ Complete 후 접근
_jobHandle.Complete();
Debug.Log(_positions[0]);
// ❌ Dispose 누락 → 메모리 누수 + 에디터 경고
// ✅ OnDestroy 또는 using 블록에서 반드시 Dispose