Index-Out-Of-Bounds in iJobParallelFor solved with "NativeDisable Restriction" - better way?

Hell all. So I have an iJobParallelFor that I kept getting index out of bounds, though I didn’t really see any reason why there is a race condition. using [NativeDisableParallelForRestriction] solved this and everything is working OK, but it seems… sloppy. I’m looking to learn to find a proper way.

Context:

I have two teams of 100 agents each. Each agent needs to find the closest enemies to itself. So I have several jobs that basically spit out a struct array of length 10000. This includes each agent in team 1, of which there are 100 of, with a sorted list of the 100 agents in team 2 in terms of distance to itself.

So:

Each agent has 100 elements in the array of 10000. 0-99 are for Agent 1’s results, 100-199 are for agent 2’s results, etc.

I only really want the first ten closest. Adjusting the first job was too much for me, so I just made another job to pick only the first ten results.

    [BurstCompile]
    struct ExtractMaxTargetsFromResultsJob : IJobParallelFor
    {
        //The original array of 10,000 results
        [ReadOnly] public NativeArray<targetDistanceStruct> FullResults;
        
        // Number of agents in the second team, this case 100
        [ReadOnly] public int OtherTeamCount; 
       
        // How many results per agent to take from the full results, this case 10
        [ReadOnly] public int resultsPerAgent;

        //The array that will store only 10 results for each of the 100 agents in team 1.  Size is 1000.
        [NativeDisableParallelForRestriction] public NativeArray<targetDistanceStruct> FilteredResults;


        public void Execute(int agentIndex)
        {
            // Calculate the starting indexes (e.g.: agent #2's values are 100-199 in the full array, but will be 10-19 in the filtered array
            int fullResultsStartIndex = agentIndex * OtherTeamCount;
            int filteredResultsStartIndex = agentIndex * resultsPerAgent;
            
            // Copy the first 10 elements from FullResults to FilteredResults - gives the 10 closest since they're already ordered.
            for (int i = 0; i < resultsPerAgent; i++)
            {
                   FilteredResults[filteredResultsStartIndex + i] = FullResults[fullResultsStartIndex + i];
            }
        }
    }

Doing the math, it all checks out to me, I shouldn’t every be writing to an element past the size of either collection. But I guess since it’s paralleled it doesn’t like you writing to something that isn’t exactly the index you are assigned or something, since one other thread might be writing to it - thought I’m reserving 10 indexes at a time:

JobHandle handle = extractJob.Schedule(teamCount1, 10);

The Index error is this:

“IndexOutOfRangeException: Index 840 is out of restricted IJobParallelFor range [84…84] in ReadWriteBuffer.”.

Besides telling it, “trust me bro” with the [NativeDisableParallelForRestriction], what is the proper way of addressing this? Any help here would be great, thank you! Note that it DOES work, and tested results are fantastic. I just want to learn how to do this properly.

Thank you!

The reason this exception exists is that a parallel job provides you with an index and it expects you to access the collection through that index, and only through that index, because that is the only way the job can make a guarantee that there will not be any race conditions.

But if the array you access will not change any of its values while the job is running, you should first try to mark it as [ReadOnly] respectively [WriteOnly]. This may already make the exception go away but I may recall it incorrectly. Nevertheless, all readonly/writeonly collections should be flagged as such since that enables more Burst optimization opportunities. In your case, it looks like the filtered array is for output only and thus could be marked as WriteOnly but only IF you write to it using only the passed in index.

Otherwise use [NativeDisableParallelForRestriction] when you KNOW this won’t cause any race conditions, or you don’t care about them (rarely a good strategy). But this can be tricky to wrap your head around especially if you’re not familiar with multithreaded programming. Essentially, a race condition can always occur if the array in question is modified from within the job and modified at an index other than the one passed into Execute(int index).

There are collections which have a ParallelWriter extension that allow writing from parallel jobs if none of the above holds true.

1 Like

Hi @CodeSmile! Thanks for your help again!

I did have it as write only, but yeah, makes sense it gives that error. I’ll check out the parralelWriter documentation. I’m still trying to learn multithreading via the job system, so it’s all tricky for me still, though I’m slowly getting there! The trick I guess is to keep things VERY SIMPLE and do ten jobs if needed, than try to cram multiple logics into a single job.

Thanks again! Do you have a "Buy me a coffee? or something? You’ve helped me so much, so it’s the least I can do! :smiley:

This is actually a good way to think about jobs and use them. It’s also more likely to speed up the whole process especially if you can run over a collection to fill another indiscriminately, rather than a job that makes conditional decisions based on the collection because conditionals can cause Burst not to vectorize the loop and that’s 10-100 times the speed going to waste.

1 Like