ジョブのスケジューリング の場合は、1 つのジョブは 1 つのタスクしか実行できません。ゲームでは、同じ操作を多くのオブジェクトに行ないたいのが一般的です。これを処理するために、IJobParallelFor という別のジョブタイプがあります。
注意 ParallelFor ジョブは、 IJobParallelFor
インターフェースを実装する構造体を指す Unity 内での総称です。
ParallelFor ジョブはデータの NativeArray をデータソースとして使用します。ParallelFor ジョブは複数のコアで実行できます。コアごとに 1 つのジョブがあり、それぞれのジョブがワークロードのサブセットを処理します。IJobParallelFor
は IJob
のように動作しますが、Execute メソッドを 1 回呼び出すのではなく、Execute
メソッドをデータソースのアイテムごとに 1 回呼び出します。Execute
メソッドには整数のパラメーターがあります。このインデックスはジョブの実装の際に、データソースの 1 つの要素にアクセスして操作するためのものです。
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# Job System は、どの NativeArray
をデータソースとして使用したいかを知ることができません。しかし、長さからは、C# Job System にいくつの Execute
メソッドがあるかを予測することができます。
見えないところでは、ParallelFor ジョブのスケジューリングはより複雑です。ParallelFor ジョブをスケジュールするとき、C# Job System は作業をバッチに分割してコア間に分散します。各バッチに Execute
メソッドのサブセットが含まれています。C# Job System は、CPU コアごとに Unity のネイティブジョブシステムで最大 1 つのジョブをスケジュールし、そのネイティブジョブをバッチ処理に渡して処理します。
任意のネイティブジョブが他のジョブより早くバッチを完了すると、他のネイティブジョブから残っているバッチを Work Stealing 処理 (もらい受けて処理) します。キャッシュの局所性 を確保するために、1 度にネイティブジョブの残りのバッチの半分しか受け取りません。
プロセスを最適化するには、バッチ数を指定する必要があります。バッチ数は、取得するジョブの数、スレッド間の作業の再配布の仕方を制御します。バッチ数が1であるなど、バッチ数が少ないと、スレッド間でより均等に作業を分散できます。オーバーヘッドがあるので、バッチ数を増やす方がよい場合もあります。 1から始まり、パフォーマンスの向上がごくわずかになるまでバッチ数を増やすことが有効な方法です。
ジョブコードサンプル
// 2 つの浮動小数点の値を加算するジョブ
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;
// 結果の配列の各インデックスに 1 つずつの実行と、各バッチ処理に 1 要素をもつジョブをスケジュールします
JobHandle handle = jobData.Schedule(result.Length, 1);
// ジョブが完了するのを待機します
handle.Complete();
// 配列に割り当てられたメモリを開放します
a.Dispose();
b.Dispose();
result.Dispose();