Система.Вводится исключение с использованием IJobParallelFor

#c# #unity3d #parallel-processing #jobs

#c# #unity3d #параллельная обработка #Вакансии

Вопрос:

Я пытаюсь оптимизировать работу с использованием IJobParallelFor в коде Unity: к сожалению, я столкнулся с ошибкой, подобной этой:

Система.Исключение IndexOutOfRangeException: индекс {0} находится вне ограниченного диапазона IJobParallelFor [{1} …{2}] в 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 amp;amp; neighboursArray[neigbourIndex] >= 0 amp;amp;
                        colorsArray[neighboursArray[neigbourIndex]].b == 255)
                    {
                        voxelToColor[listIndex] = index;
                        listIndex  ;
                    }
                }
            }
        }
}
 

}

Ответ №1:

Afaik это исключение вызвано тем фактом, что IJobParallelFor , как то же самое говорит, выполняется параллельно

Execute(int index) будет выполняться один раз для каждого индекса от 0 до указанной длины. Каждая итерация должна быть независимой от других итераций (система безопасности применяет это правило для вас). Индексы не имеют гарантированного порядка и выполняются на нескольких ядрах параллельно.

Unity автоматически разбивает работу на куски размером не менее предоставленного размера пакета и планирует соответствующее количество заданий на основе количества рабочих потоков, длины массива и размера пакета.

Это означает, что, допустим, у вас есть массив длины 10 , и вы говорите, что размер пакета может быть до 4 , тогда вы, вероятно, получите 3 параллельных блока заданий для индексов [0, 1, 2, 3] , [4, 5, 6, 7] и [8, 9] . Поскольку каждый из этих 3 блоков, возможно, находится на другом ядре, они получают доступ только к соответствующему фрагменту NativeArray (-ам). (Подробнее об этом здесь)

Вероятно, происходит то, что несколько ваших параллельных заданий пытаются получить доступ и записать в один и тот же индекс ваших выходных NativeArray данных voxelToColor и neighboursArray . В частности, без выполнения остальных вычислений вы, безусловно, попытаетесь выполнить запись voxelToColor[0] в каждом из ваших параллельных заданий, что не разрешено и не имеет для меня большого смысла.

В течение одного Execute вызова вы можете выполнять запись только в указанный индекс.

Afaik сообщение должно дополнительно читать

ReadWriteBuffers ограничены только чтением и записью элемента в индексе задания. Вы можете использовать стратегии двойной буферизации, чтобы избежать условий гонки из-за параллельного чтения и записи одних и тех же элементов из задания.

[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] , вы получите исключение, которое получаете.