๐ English Abstract
This article provides an in-depth troubleshooting guide for unmanaged memory leaks and Job Safety System exceptions encountered when using Unity's C# Job System and Burst Compiler. It covers the lifecycle management of NativeArray, proper usage of different Allocator types, and techniques to resolve concurrent access conflicts. By implementing dependency chaining with JobHandle and leveraging automated memory leak detection tools, developers can build robust, highly optimized multi-threaded data pipelines in Unity.
์ ๋ํฐ(Unity)์์ ๋ฉํฐ์ฐ๋ ๋ฉ ๊ธฐ๋ฐ์ ๊ณ ์ฑ๋ฅ ์ฐ์ฐ์ ๊ตฌํํ ๋ C# Job System๊ณผ Burst Compiler๋ ์ ํ์ด ์๋ ํ์ ๊ธฐ์ ๋ก ์๋ฆฌ ์ก์์ต๋๋ค. ๋ฐ์ดํฐ ์งํฅ ๊ธฐ์ ์คํ(Data-Oriented Technology Stack, DOTS)์ ํต์ฌ ์ถ์ ์ด๋ฃจ๋ ์ด ๊ธฐ์ ๋ค์ C#์ ๊ฐ๋น์ง ์ปฌ๋ ์ (Garbage Collection, GC) ๋ถํ๋ฅผ ์์ ํ ์ฐํํ์ฌ C++ ์์ค์ ์๋์ ์ธ ์คํ ์๋๋ฅผ ์ ๊ณตํฉ๋๋ค.
ํ์ง๋ง ์ด๋ฌํ ์ฑ๋ฅ ์ด์ ์ ์ด๋ฉด์๋ ์๊ฒฉํ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ์ฑ
์์ ๊ฐ๋ฐ์๊ฐ ์ง์ ์ ์ ์ผ๋ก ์ง์ด์ ธ์ผ ํ๋ค๋ ์ํ ์์๊ฐ ์กด์ฌํฉ๋๋ค. ํนํ ๋น๊ด๋ฆฌํ ๋ฉ๋ชจ๋ฆฌ(Unmanaged Memory) ๊ตฌ์กฐ์ฒด์ธ NativeArray<T>์ ์๋ช
์ฃผ๊ธฐ(Lifecycle)๋ฅผ ์๋ชป ๊ด๋ฆฌํ๋ฉด ์์ง ๋ ์ด์ด์์์ ๋ฉ๋ชจ๋ฆฌ ๋์(Memory Leak)๊ฐ ๋ฐ์ํ๋ฉฐ, ์ค๋ ๋ ๊ฐ ๋ฐ์ดํฐ ์ ๊ทผ ๊ฒฝ๋ ์กฐ๊ฑด(Race Condition)์ด ์ผ์ด๋ ๊ฒฝ์ฐ Unity์ ์ก ์ธ์ดํํฐ ์์คํ
(Job Safety System)์ด ์น๋ช
์ ์ธ ์์ธ(Exception)๋ฅผ ๋ฐ์์ํต๋๋ค.
๋ณธ ํฌ์คํ
์์๋ ํ์
ํ๋ก์ ํธ์์ ์์ฃผ ์ง๋ฉดํ๋ NativeArray ๊ด๋ จ ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฉ์ปค๋์ฆ๊ณผ Job Safety System ์์ธ์ ์์ธ์ ๊น์ด ์๊ฒ ๋ถ์ํ๊ณ , ์ด๋ฅผ ์๋ฒฝํ ํด๊ฒฐํ ์ ์๋ ์ค๋ฌด ํธ๋ฌ๋ธ์ํ
๊ฐ์ด๋๋ฅผ ๊ณต์ ํฉ๋๋ค.
1. Unmanaged ๋ฉ๋ชจ๋ฆฌ ๋์์ ์์ธ๊ณผ NativeArray ์๋ช ์ฃผ๊ธฐ
C#์ ์ผ๋ฐ์ ์ธ ๋ฐฐ์ด(Array)์ด๋ List<T>๋ Managed Heap์ ํ ๋น๋์ด Mono/IL2CPP์ GC๊ฐ ์๋์ผ๋ก ์๊ฑฐํฉ๋๋ค. ๊ทธ๋ฌ๋ C# Job System๊ณผ Burst Compiler๊ฐ ์ ๊ทผํ๋ NativeContainer ๊ณ์ด(NativeArray, NativeList, NativeHashMap ๋ฑ)์ Unmanaged C++ Heap ์์ญ์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ง์ ํ ๋นํฉ๋๋ค.
1.1 ํ ๋น์(Allocator) ์ ํ๊ณผ ํน์ง
NativeArray ์์ฑ ์ ์ ๋ฌํ๋ Allocator ์ ํ์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ํ ๋น๋๋ ์์น์ ๊ธฐ๋ ์๋ช
์ ๊ฒฐ์ ํฉ๋๋ค.
- Allocator.Temp: ๊ฐ์ฅ ๋น ๋ฅธ ํ ๋น ๋ฐฉ์์ ์ฌ์ฉํ๋ฉฐ, 1ํ๋ ์ ์ดํ์ ์งง์ ์๋ช ์ ๊ฐ์ง๋๋ค. ๋ฉ์๋ ์ข ๋ฃ ์ ๋ฐ๋์ ํด์ ๋์ด์ผ ํ๋ฉฐ, Job์ผ๋ก ์ ๋ฌํ ์ ์์ต๋๋ค.
- Allocator.TempJob: ํ๋ ์ ๋จ์(์ต๋ 4ํ๋ ์)๋ก ์ ์ง๋๋ ํ ๋น ๋ฐฉ์์ ๋๋ค. Job์ ์ธ์๋ก ์ ๋ฌํ๊ธฐ์ ์ ํฉํ๋ฉฐ, ์์ ์๋ฃ ํ ์๋์ผ๋ก ํด์ ํ์ง ์์ผ๋ฉด ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํฉ๋๋ค.
- Allocator.Persistent: C++์
malloc๊ณผ ๋์ผํ๊ฒ ์ฅ๊ธฐ ๋ณด๊ด์ฉ ํ ๋ฉ๋ชจ๋ฆฌ์ ํ ๋น๋ฉ๋๋ค. ๊ฐ๋ฐ์๊ฐ ์ง์ Dispose()๋ฅผ ํธ์ถํ๊ธฐ ์ ๊น์ง ์๊ตฌํ ์ ์ง๋ฉ๋๋ค.
1.2 ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐ์ ์ฃ์ง ์ผ์ด์ค (Edge Cases)
์ค๋ฌด์์ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ๋ ๋ํ์ ์ธ ์ํฉ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ์์ธ ๋ฐ์์ผ๋ก ์ธํ ๊ตฌ๋ฌธ ๊ฑด๋๋: ์ก์ ์ค์ผ์ค๋งํ๊ฑฐ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ๋ ๋์ค C# ์์ธ๊ฐ ๋ฐ์ํ์ฌ ํ๋จ์
.Dispose()์ฝ๋๊ฐ ์คํ๋์ง ์๋ ๊ฒฝ์ฐ. - ์กฐ๊ธฐ ๋ฐํ(Early Return): ์กฐ๊ฑด๋ฌธ์ ์ํด ๋ฉ์๋๊ฐ ์ค๊ฐ์
return๋ ๋, ๋ฉ๋ชจ๋ฆฌ ํด์ ๋ก์ง์ ๋๋ฝํ๋ ๊ฒฝ์ฐ. - ๋น๋๊ธฐ ํ๋ฆ ํํธํ: Job์ ์คํ ์๋ฃ ์์ (JobHandle)๊ณผ ๋ฉ์ธ ์ค๋ ๋์ ํด์ ์์ ์ด ๋๊ธฐํ๋์ง ์์ ์ฐธ์กฐ๊ฐ ์์ค(Dangling Reference)๋๋ ๊ฒฝ์ฐ.
1.3 ๋์ ํ์ง: Native Leak Detection ํ์ฑํ
๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์ ๋ฐ ์ถ์ ํ๊ธฐ ์ํด ์๋ํฐ ๋ฐ ๊ฐ๋ฐ ๋น๋์์ ์ถ์ ๋ชจ๋๋ฅผ ๊ฐํํ ์ ์์ต๋๋ค. ์คํฌ๋ฆฝํธ ์ง์ ์ ์ด๋ ์ด๊ธฐํ ๋ก์ง์ ์๋ ์ฝ๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.
// ์คํ ํธ๋ ์ด์ค(Stack Trace)๋ฅผ ํฌํจํ ๊ฐ๋ ฅํ ๋ฉ๋ชจ๋ฆฌ ๋์ ๊ฐ์ง ํ์ฑํ
UnsafeUtility.SetLeakDetectionMode(NativeLeakDetectionMode.EnabledWithStackTrace);
์ด ์ต์
์ด ํ์ฑํ๋๋ฉด ํด์ ๋์ง ์์ NativeArray๊ฐ ์กด์ฌํ ๋ ์ฝ์์ฐฝ์ ํด๋น ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ด๋ ํ์ผ, ๋ช ๋ฒ์งธ ์ค์์ ํ ๋น๋์๋์ง ์ ํํ ์คํ ํธ๋ ์ด์ค๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
2. Job Safety System ์์ธ์ ์์ธ ๋ถ์
Unity์ Job Safety System(JSS)์ ๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์ ๋ฐ์ํ ์ ์๋ ๋ฐ์ดํฐ ๊ฒฝ๋(Data Race)์ ๋ฐฉ์งํ๋ ๋ฐฉํ๋ฒฝ ์ญํ ์ ํฉ๋๋ค. ๋ฉ์ธ ์ค๋ ๋์ ์ก ์ค๋ ๋๊ฐ ๋์ผํ ๋ฉ๋ชจ๋ฆฌ ์์ญ์ ๋์์ ์ฝ๊ณ ์ฐ๋ ค๊ณ ํ ๋, JSS๋ ๋ฐํ์ ์์ธ๋ฅผ ๋ฐ์์์ผ ๋ฐ์ดํฐ ์ค์ผ์ ์ฐจ๋จํฉ๋๋ค.
์ฃผ์ ๋ฐ์ ์์ธ ํจํด
ํจํด A: Unscheduled / Uncompleted Access Exception
InvalidOperationException: The NativeArray is object of a job that is currently scheduled.
You must call JobHandle.Complete() before accessing the buffer.
์ค์ผ์ค๋ง๋ ์ก(Job)์ด ๋ด๋ถ์ ์ผ๋ก NativeArray๋ฅผ ์ฌ์ฉํ๊ณ ์๋ ๋์, ๋ฉ์ธ ์ค๋ ๋์์ ํด๋น ๋ฐฐ์ด์ ์ ๊ทผ(Read/Write)ํ๊ฑฐ๋ Dispose()๋ฅผ ์ฆ์ ํธ์ถํ๋ ค ํ ๋ ๋ฐ์ํฉ๋๋ค.
ํจํด B: Concurrent Write Access Exception
InvalidOperationException: The previously scheduled job 'ProcessJob' writes to the NativeArray...
You must pass this JobHandle as a dependency.
์๋ก ๋ค๋ฅธ ๋ ๊ฐ ์ด์์ ์ก์ด ๋์ผํ NativeArray ๋ฒํผ์ ๋ํด ์ฐ๊ธฐ ๊ถํ์ ๊ฐ์ง๊ณ ๋์์ ์ค์ผ์ค๋ง๋์์ ๋ ๋ฐ์ํฉ๋๋ค. JSS๋ ์ด๋ค ์ก์ด ๋จผ์ ์คํ๋ ์ง ๋ณด์ฅํ ์ ์๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ์๊ฒฉํ ๊ธ์งํฉ๋๋ค.
3. ์ฌํ ํธ๋ฌ๋ธ์ํ ๋ฐ ๋ฒ์คํธ ์๋ฒฝ ํธํ ํจํด
๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์์ ํ ๋ฐฉ์งํ๊ณ Job Safety System๊ณผ์ ์ถฉ๋์ ํด๊ฒฐํ๊ธฐ ์ํ ์ค๋ฌด ํจํด ๋ฐ ์ฝ๋ ์์์ ๋๋ค.
ํจํด 1: DisposeSentinel๊ณผ JobHandle์ ํตํ ์ง์ฐ ํด์ (Dispose Dependency)
๋ฉ์ธ ์ค๋ ๋์์ JobHandle.Complete()๋ฅผ ํธ์ถํ์ฌ ๋ธ๋กํน(Blocking)์ ์ผ์ผํค๋ ๊ฒ์ ํ๋ ์ ๋๋์ ์์ธ์ด ๋ฉ๋๋ค. ์ก์ด ์๋ฃ๋ ์งํ ์ค๋ ๋ ์ธ์ดํํ๊ฒ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํ๋ ค๋ฉด Dispose(JobHandle) ์ค๋ฒ๋ก๋๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
public class SafeNativeArrayPipeline : MonoBehaviour
{
[BurstCompile]
private struct ComputeTaskJob : IJobParallelFor
{
[ReadOnly] public NativeArray<float> inputBuffer;
public NativeArray<float> outputBuffer;
public void Execute(int index)
{
outputBuffer[index] = inputBuffer[index] * 2.5f;
}
}
private void Update()
{
int dataSize = 10000;
// 1. Unmanaged ๋ฉ๋ชจ๋ฆฌ ํ ๋น
var inputData = new NativeArray<float>(dataSize, Allocator.TempJob);
var outputData = new NativeArray<float>(dataSize, Allocator.TempJob);
// ๋ฐ์ดํฐ ์ด๊ธฐํ (์๋ต)
// 2. Job ๊ตฌ์กฐ์ฒด ์์ฑ ๋ฐ ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ
var job = new ComputeTaskJob
{
inputBuffer = inputData,
outputBuffer = outputData
};
// 3. Job ์ค์ผ์ค๋ง (ParallelFor)
JobHandle jobHandle = job.Schedule(dataSize, 64);
// 4. [์ค์] Job์ด ๋๋ ํ ์๋์ผ๋ก NativeArray๊ฐ ํด์ ๋๋๋ก ์์กด์ฑ ์ฃผ์
ํด์ ์ฒ๋ฆฌ
// ๋ฉ์ธ ์ค๋ ๋๋ฅผ ๋ธ๋กํนํ์ง ์๊ณ , ์์ ํ๊ฒ Unmanaged ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํจ
JobHandle inputDisposeHandle = inputData.Dispose(jobHandle);
JobHandle outputDisposeHandle = outputData.Dispose(jobHandle);
// 5. ์์กด์ฑ์ด ๊ฒฐํฉ๋ ์ต์ข
ํธ๋ค ๊ด๋ฆฌ (ํ์ ์)
JobHandle finalHandle = JobHandle.CombineDependencies(inputDisposeHandle, outputDisposeHandle);
// ์ฃผ์: finalHandle์ ์ฐจํ ํ๋ ์์์ ์๋์ผ๋ก Complete() ํด์ฃผ๊ฑฐ๋ ๋ค์ ์ก์ ์์กด์ฑ์ผ๋ก ๋๊ฒจ์ผ ํจ
}
}
ํจํด 2: [ReadOnly] ๋ฐ [WriteOnly] ํน์ฑ(Attribute)์ ์ฌ๋ฐ๋ฅธ ์ง์
Burst Compiler์ Job Safety System์ ๋ฐ์ดํฐ ์ ๊ทผ ์๋๋ฅผ ๋ช ํํ ์๋ฆฌ๋ฉด, ์ธ์ดํํฐ ์ฒดํฌ ์์ธ๋ฅผ ํํผํจ๊ณผ ๋์์ ๊ทน์ ์ธ ์ปดํ์ผ ์ต์ ํ(Auto-vectorization)๋ฅผ ์ด๋์ด๋ผ ์ ์์ต๋๋ค.
- [ReadOnly]: ์ฌ๋ฌ ์ก์ด ๋์ผํ
NativeArray๋ฅผ ๋์์ ์ฝ๋ ๊ฒ์ ์์ ํฉ๋๋ค. ์ด ํน์ฑ์ ๋ถ์ด๋ฉด JSS๋ ๋ค์ค ๋ณ๋ ฌ ์ฝ๊ธฐ ์์ ์ ํ์ฉํฉ๋๋ค. - [WriteOnly]: ๋ฐ์ดํฐ ์ฐ๊ธฐ ์ ์ฉ ๋ฒํผ์์ ๋ช ์ํ์ฌ ๋ถํ์ํ ์บ์ ๋๊ธฐํ ๋น์ฉ์ ์ค์ ๋๋ค.
- [NativeDisableParallelForRestriction]:
IJobParallelFor๋ด์์ ์ธ๋ฑ์ค(Index) ์ด์ธ์ ์์์ ์ ๊ทผํด์ผ ํ ๋ ๋ฐ์ ์์ ๊ฒ์ฌ๋ฅผ ๋นํ์ฑํํฉ๋๋ค. (๋จ, ์ฌ์ฉ ์ ๊ฒฝ๋ ์กฐ๊ฑด์ด ๋ฐ์ํ์ง ์์์ ๊ฐ๋ฐ์๊ฐ ๋ณด์ฅํด์ผ ํฉ๋๋ค.)
ํจํด 3: JobHandle ์ฒด์ด๋(Chaining)์ ์ด์ฉํ Race Condition ํด๊ฒฐ
์ด์ ์ก์ ์ถ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ๋ค์ ์ก์ ์
๋ ฅ ๊ฒฐ๊ณผ๋ก ์ฐ๊ฒฐํ ๋, JobHandle์ ์์กด ๊ด๊ณ(Dependency)๋ก ๋ช
์์ ์ผ๋ก ์ ๋ฌํด์ผ ํฉ๋๋ค.
JobHandle handleDataPrep = prepJob.Schedule();
// handleDataPrep์ด ์๋ฃ๋ ์ดํ์๋ง processJob์ด ์คํ๋๋๋ก ์์กด์ฑ ์ฒด์ด๋
JobHandle handleProcess = processJob.Schedule(handleDataPrep);
// ์๋ฃ ๋ณด์ฅ ํ ์ ๊ทผ
handleProcess.Complete();
4. ์ค๋ฌด ์ฑ๋ฅ ์ต์ ํ ๋ฐ ์ฃ์ง ์ผ์ด์ค ์ฒดํฌ๋ฆฌ์คํธ
์ค์ ํ๋ก๋์ ํ๊ฒฝ์์ Unmanaged ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๋ฐ C# Job System์ ์ ์ฉํ ๋ ๋ฐ๋์ ํ์ธํด์ผ ํ ์ฒดํฌ๋ฆฌ์คํธ์ ๋๋ค.
| ํญ๋ชฉ | ์ ๊ฒ ์ฌํญ | ๊ถ์ฅ ์๋ฃจ์ |
|---|---|---|
| ํ ๋น์ ์ ํ | ํ๋ ์ ๋ด ์ผํ์ฑ ์ฐ์ฐ์ Persistent ํ ๋น ์ฌ์ฉ ์ฌ๋ถ | Allocator.Temp ๋๋ TempJob์ผ๋ก ์ ํํ์ฌ ํ ํํธํ ๋ฐฉ์ง |
| Dispose ๋๋ฝ | ํด๋์ค ๋ฉค๋ฒ ๋ณ์๋ก ์ ์ธ๋ NativeArray์ ํด์ ์ฌ๋ถ | OnDestroy() ๋๋ Dispose() ๊ตฌํ ํจํด(IDisposable) ์ ์ฉ |
| Main Thread Blocking | Update() ๋ง๋ค JobHandle.Complete() ํธ์ถ๋ก ์ค๋ ๋ ๋๊ธฐ ๋ฐ์ |
Complete() ์์ ์ LateUpdate()๋ก ๋ฏธ๋ฃจ๊ฑฐ๋ Dispose(JobHandle) ํ์ฉ |
| Burst ์ปดํ์ผ ์ค๋ฅ | NativeArray ๋ด๋ถ์ ์ฐธ์กฐ ํ์(Reference Type) ํฌํจ ์ฌ๋ถ | ๊ตฌ์กฐ์ฒด(struct) ๋ด์๋ ๊ฐ ํ์(Value Type) ๋๋ NativeContainer๋ง ํฌํจ |
๊ฒฐ๋ก : ์์ ํ๊ณ ๊ฐ๊ฑดํ ๊ณ ์ฑ๋ฅ ๋ฉํฐ์ค๋ ๋ฉ ๋ฐ์ดํฐ ํ์ดํ๋ผ์ธ ๊ตฌ์ถ
Unity์ C# Job System๊ณผ Burst Compiler๋ Garbage Collection ํ๋ ์ ๋๋์์ ๋ฒ์ด๋ ํ๋์จ์ด ํ๊ณ๊น์ง ์ฑ๋ฅ์ ๋์ด์ฌ๋ฆด ์ ์๋ ๊ฐ๋ ฅํ ๋ฌด๊ธฐ์ ๋๋ค. ํ์ง๋ง ์ด๋ฅผ ์์ ์ ์ผ๋ก ๋ค๋ฃจ๊ธฐ ์ํด์๋ Unmanaged ๋ฉ๋ชจ๋ฆฌ์ ๋ช ํํ ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ์ Job Safety System์ ๋ฐ์ดํฐ ์ ๊ทผ ๊ท์ฝ์ ์๊ฒฉํ ์ค์ํด์ผ ํฉ๋๋ค.
์ ํํ Allocator ์ ํ, Dispose(JobHandle)๋ฅผ ํ์ฉํ ๋น๋๊ธฐ ์ง์ฐ ํด์ , ๊ทธ๋ฆฌ๊ณ [ReadOnly] ํน์ฑ์ ํ์ฉํ ๋ฐ์ดํฐ ํต์ ๋ฐฉ์์ ํ์คํํ๋ค๋ฉด, ๋ฉ๋ชจ๋ฆฌ ๋์ ์๋ ๊ณ ์ฑ๋ฅ ๋ฉํฐ์ค๋ ๋ ์์คํ
์ ์ฑ๊ณต์ ์ผ๋ก ๊ตฌํํ ์ ์์ ๊ฒ์
๋๋ค.