잡을 예약할 경우 하나의 잡은 하나의 작업만 수행할 수 있습니다. 게임에서는 수많은 오브젝트에 대해 동일한 작업을 수행하는 경우가 흔합니다. 따라서 이를 위해 IJobParallelFor라고 불리는 별도의 잡 타입이 제공됩니다.
참고: ’ParallelFor’는 IJobParallelFor
인터페이스를 구현하는 구조체에 관한 Unity의 포괄적인 용어입니다.
ParallelFor 잡은 데이터의 NativeArray를 사용하여 데이터 소스로 동작합니다. ParallelFor 잡은 여러 개의 코어에서 동작합니다. 코어당 하나의 잡이 있으며, 각각 일정량의 작업을 처리합니다. IJobParallelFor
는 IJob
과 비슷하게 동작하지만, 단일 Execute가 아니라 데이터 소스의 항목당 하나의 Execute
메서드를 호출합니다. Execute
메서드에는 정수 파라미터가 있습니다. 이 인덱스는 잡 구현 내에서 데이터 소스의 단일 요소에 액세스하여 동작합니다.
struct IncrementByDeltaTimeJob: IJobParallelFor
{
public NativeArray<float> values;
public float deltaTime;
public void Execute (int index)
{
float temp = values[index];
temp += deltaTime;
values[index] = temp;
}
}
ParallelFor 잡을 예약할 때는 분할할 NativeArray
데이터 소스의 길이를 지정해야 합니다. Unity C# 잡 시스템은 구조체에 여러 개의 NativeArray
가 있으면 사용자가 어느 것을 데이터 소스로 사용할지 알 수 없습니다. 또한 데이터 소스의 길이는 C# 잡 시스템에 예상되는 Execute
메서드 개수도 알려줍니다.
보이지 않는 곳에서 ParallelFor 잡 예약은 더 복잡하게 동작합니다. ParallelFor 잡을 예약할 때 C# 작업 시스템은 작업을 배치로 나누어 코어 간에 배포합니다. 각 배치에는 Execute
메서드의 하위 집합이 포함됩니다. 그러면 C# 잡 시스템은 Unity의 네이티브 잡 시스템에서 CPU 코어당 하나의 잡을 예약한 후 해당 네이티브 잡에 완료할 일부 배치를 전달합니다.
한 네이티브 잡이 다른 네이티브 잡보다 배치를 먼저 완료하면 다른 네이티브 잡의 남은 배치를 가져옵니다. 한 번에 네이티브 잡의 남은 배치의 절반만 가져오므로 캐시 집약성이 보장됩니다.
프로세스를 최적화하려면 배치 수를 지정해야 합니다. 배치 수는 몇 개의 잡을 가져오고 스레드 간에 작업 재배포를 어떻게 세부 조정할지를 제어합니다. 작은 배치 수(예:1)를 지정하면 스레드 간에 작업을 더 균등하게 배포할 수 있습니다. 성능 소모가 더 발생하더라도 때로는 배치 수를 늘려야 할 때도 있습니다. 1부터 시작하여 성능 향상을 무시할 수 있는 수준까지 개수를 늘리는 것도 좋은 전략입니다.
잡 코드:
// Job adding two floating point values together
public struct MyParallelJob : IJobParallelFor
{
[ReadOnly]
public NativeArray<float> a;
[ReadOnly]
public NativeArray<float> b;
public NativeArray<float> result;
public void Execute(int i)
{
result[i] = a[i] + b[i];
}
}
메인 스레드 코드:
NativeArray<float> a = new NativeArray<float>(2, Allocator.TempJob);
NativeArray<float> b = new NativeArray<float>(2, Allocator.TempJob);
NativeArray<float> result = new NativeArray<float>(2, Allocator.TempJob);
a[0] = 1.1;
b[0] = 2.2;
a[1] = 3.3;
b[1] = 4.4;
MyParallelJob jobData = new MyParallelJob();
jobData.a = a;
jobData.b = b;
jobData.result = result;
// Schedule the job with one Execute per index in the results array and only 1 item per processing batch
JobHandle handle = jobData.Schedule(result.Length, 1);
// Wait for the job to complete
handle.Complete();
// Free the memory allocated by the arrays
a.Dispose();
b.Dispose();
result.Dispose();