System.InedxOutOfRangeException 使用 IJobParallelFor

System.InedxOutOfRangeException using IJobParallelFor

我尝试在 Unity 代码中使用 IJobParallelFor 优化性能: 不幸的是我遇到了这样的错误:

System.IndexOutOfRangeException: Index {0} is out of restricted IJobParallelFor range [{1}...{2}] in ReadWriteBuffer.

我尝试使用

[NativeDisableParallelForRestriction]

[NativeDisableContainerSafetyRestriction]

但没有效果

[BurstCompile(CompileSynchronously = true)]

public struct DilationJob : IJobParallelFor
{
    
[ReadOnly]
public NativeArray<Color32> colorsArray;

public NativeArray<int> voxelToColor;
public int kernelSize;
public NativeArray<int> neighboursArray;
public int cubeNumber;


public void Execute(int index)
{   
    int dimx = 512;
    int  dimy = 512; 
    
    //int[] neighboursArray = new int[kernelSize * kernelSize * kernelSize];
    int listIndex = 0;
    
        for (int i = -1; i < kernelSize - 1; i++)
        {
            for (int j = -1; j < kernelSize - 1; j++)
            {
                for (int k = -1; k < kernelSize - 1; k++)
                {
                    int neigbourIndex = (i + 1) * (j + 1) * kernelSize + (j + 1) * kernelSize + (k + 1);

                    if (neigbourIndex < 0)
                    {
                        neigbourIndex = 0;
                    }

                    neighboursArray[neigbourIndex] =
                        index + k * dimx * dimy + j * dimx + i;

                    if (neighboursArray[neigbourIndex] < colorsArray.Length && neighboursArray[neigbourIndex] >= 0 &&
                        colorsArray[neighboursArray[neigbourIndex]].b == 255)
                    {
                        voxelToColor[listIndex] = index;
                        listIndex++;
                    }
                }
            }
        }
}

}

Afaik 这个异常是由于 IJobParallelFor 是并行执行的

Execute(int index) will be executed once for each index from 0 to the provided length. Each iteration must be independent from other iterations (The safety system enforces this rule for you). The indices have no guaranteed order and are executed on multiple cores in parallel.

Unity automatically splits the work into chunks of no less than the provided batchSize, and schedules an appropriate number of jobs based on the number of worker threads, the length of the array and the batch size.

这意味着假设您有一个长度为 10 的数组,并且您说 batchSize 可以达到 4 那么您最终可能会得到 3 个用于索引的并行作业块[0, 1, 2, 3][4, 5, 6, 7][8, 9]。由于这 3 个块中的每一个都可能位于不同的内核上,因此它们只能访问 NativeArray(s) 中相应的块。 (更多关于 here

可能发生的情况是您的多个并行作业尝试访问和写入输出 NativeArrays voxelToColorneighboursArray 的相同索引。具体来说,如果不进行其余计算,您肯定会尝试在每个并行作业中写入 voxelToColor[0],这对我来说既不允许也不有意义。

在一次 Execute 调用中,您只能写入给定的索引。

Afaik 消息应该进一步阅读

ReadWriteBuffers are restricted to only read & write the element at the job index. You can use double buffering strategies to avoid race conditions due to reading & writing in parallel to the same elements from a job.

[ReadOnly] 标记数组是例外,因为只要您只读取,这里的多个并行访问就不会破坏数据。


然后[NativeDisableContainerSafetyRestriction]如果我理解正确解决了不同作业和主线程之间的竞争条件。

虽然您可能想要的更多 [NativeDisableParallelForRestriction] 据我所知,它禁用了对数组索引的并行访问的安全限制。老实说,Unity API 在这些方面非常省力。

举个小例子

public class Example : MonoBehaviour
{
    public Color32[] colors = new Color32[10];

    private void Awake()
    {
        var job = new DilationJob()
        {
            colorsArray = new NativeArray<Color32>(colors, Allocator.Persistent),
            voxelToColor = new NativeArray<int>(colors.Length, Allocator.Persistent)
        };

        var handle = job.Schedule(colors.Length, 4);

        handle.Complete();

        foreach (var i in job.voxelToColor)
        {
            Debug.Log(i);
        }

        job.colorsArray.Dispose();
        job.voxelToColor.Dispose();
    }
}

[BurstCompile(CompileSynchronously = true)]
public struct DilationJob : IJobParallelFor
{
    [ReadOnly] public NativeArray<Color32> colorsArray;

    [NativeDisableParallelForRestriction]
    public NativeArray<int> voxelToColor;

    public void Execute(int index)
    {
        voxelToColor[index] = colorsArray[index].a;

        if (index + 1 < colorsArray.Length - 1) voxelToColor[index + 1] = 0;
    }
}

不应抛出任何异常。但是如果你注释掉 [NativeDisableParallelForRestriction] 你会得到你得到的异常。