私は現在、非常に CPU 負荷の高いシミュレーションに取り組んでおり、ジョブ システム + バースト コンパイルに移行し、パフォーマンスが大幅に向上する可能性があります。とはいえ、ジョブシステムへの切り替えは新たなボトルネックを生み出しました。これを回避する方法があるかどうかを尋ねたかったのです。
これが私のシミュレーションの仕組みです。基本的に、ワールド空間で 2 次元の四角形を移動し、パス上のグリッド座標をキャプチャします。ジョブがなければ、最大のボトルネックは でしSampleLine
た。ジョブ システムでは、ボトルネックはtrajectory.Add
/.AddNative
とoccupied.IndexOf
です。
NativeList<JobHandle> jobHandleList = new NativeList<JobHandle>(Allocator.Temp);
List<NativeArray<float>> timestampList = new List<NativeArray<float>>();
List<NativeList<int2>> resultList = new List<NativeList<int2>>();
while (simulate)
{
move(dummy);
time += timestep;
NativeArray<float> timestampArr = new NativeArray<float>(1, Allocator.TempJob);
NativeList<int2> result = new NativeList<int2>(Allocator.TempJob);
JobHandle jobHandle = GetOccupiedTilesJob(timestep, dummy, grid, ref timestampArr, ref result);
jobHandleList.Add(jobHandle);
timestampList.Add(timestampArr);
resultList.Add(result);
}
// get data from jobs and clean up
JobHandle.CompleteAll(jobHandleList);
for(int i = 0; i < timestampList.Count; i++)
{
// BOTTLENECK HERE
// TODO: once unity supports it, use resultList[i].ToList()
/*List<int2> coordinates = new List<int2>();
for(int j = 0; j < resultList[i].Length; j++)
{
coordinates.Add(resultList[i][j]);
}
trajectory.Add(timestampList[i][0], coordinates);*/
trajectory.AddNative(timestampList[i][0], resultList[i]);
timestampList[i].Dispose();
resultList[i].Dispose();
}
jobHandleList.Dispose();
return trajectory;
trajectory.Add
を使用しDictionary<int2, float2>
、.AddNative はNativeHashMap<int2, float2>
値を格納するために を使用します。最初に NativeList のすべての値を役に立たないリストにコピーする必要がありますが、AddNative のtrajectory.Add
ほぼ2 倍高速です。配列をバッファーとして使用することで、おそらくさらに高速化できます。
使用したジョブは次のとおりです。
[BurstCompile]
public struct GetOccupiedTiles : IJob
{
public float2 A;
public float2 B;
public float2 C;
public float2 D;
public float timestamp;
public NativeArray<float> timestampArr; // only for storage
public NativeList<int2> result;
[ReadOnly] public float2 gridCenter;
[ReadOnly] public float cellSize;
public void Execute()
{
float rate = cellSize / 4f;
result.Add(WorldToCell(A, gridCenter, cellSize));
result.Add(WorldToCell(B, gridCenter, cellSize));
result.Add(WorldToCell(C, gridCenter, cellSize));
result.Add(WorldToCell(D, gridCenter, cellSize));
SampleLine(A, B, rate, ref result, gridCenter, cellSize);
SampleLine(B, C, rate, ref result, gridCenter, cellSize);
SampleLine(C, D, rate, ref result, gridCenter, cellSize);
SampleLine(D, A, rate, ref result, gridCenter, cellSize);
float2 AB = (B - A);
float2 AB_normalized = math.normalize(AB);
float2 DC_normalized = math.normalize(C - D);
float length = math.sqrt(AB.x * AB.x + AB.y * AB.y);
float current = rate;
while (current < length)
{
float2 X = A + AB_normalized * current;
float2 Y = D + DC_normalized * current;
SampleLine(X, Y, rate, ref result, gridCenter, cellSize);
current += rate;
}
timestampArr[0] = timestamp;
}
private static void SampleLine(float2 A, float2 B, float sampleRate, ref NativeList<int2> occupied, float2 gridCenter, float cellSize)
{
float2 AB = (B - A);
float2 AB_scaled = math.normalize(AB) * sampleRate;
float2 test = A + AB_scaled;
float length = math.sqrt(AB.x * AB.x + AB.y * AB.y);
float current = sampleRate;
while (current < length)
{
int2 cell = WorldToCell(test, gridCenter, cellSize);
// optimally i want to use a hashset here because .IndexOf is very slow
if (occupied.IndexOf(cell) == -1) occupied.Add(cell);
test += AB_scaled;
current += sampleRate;
}
}
public static int2 WorldToCell(float2 vector, float2 gridCenter, float cellSize)
{
return (int2)math.floor(-(gridCenter - vector) / cellSize);
}
}
今私の問題は、どうすればコストのかかるNativeList
対List
変換を防ぐことができますか? a を a に変換してそのリストを a に追加するよりも、 aを aNativeList
に追加する方が遅いのはなぜですか?NativeHashMap
NativeList
List
Dictionary
また、並行して呼び出すことSampleLine
もできるので、ジョブを小さなジョブに分割した方がよいでしょうか? WorldToCell
しかし、ジョブが他のジョブを開始できるかどうか、またはジョブを作成WorldToCell
するだけで (最も呼び出されるため)、通常の C# で残りを実行する必要があるかどうかはわかりません。私はジョブシステムにまったく慣れていないので、何がどのように「ジョブ化」されるべきかわかりません。1 つの大きなジョブをそのままにしておくことも、小さなジョブをたくさん作成WorldToCell
することも、3 つの計算すべてを 1 つのジョブにすることもできます。