Search Unity

(SOLVED) NativeDisableParallelForRestriction not working

Discussion in 'Entity Component System' started by CodeMonkeyYT, Jun 5, 2019.

  1. CodeMonkeyYT

    CodeMonkeyYT

    Joined:
    Dec 22, 2014
    Posts:
    125
    SOLVED: The correct attribute to use in this case is [NativeDisableContainerSafetyRestriction] which is inside Unity.Collections.LowLevel.Unsafe

    --------------------


    I'm coming across a error related to scheduling jobs to the same NativeArray and from what I understand adding the attribute [NativeDisableParallelForRestriction] is meant to solve it but nothing changes.

    InvalidOperationException: The previously scheduled job TestParallelRestrictionJob writes to the NativeArray TestParallelRestrictionJob.matrixArray. You are trying to schedule a new job TestParallelRestrictionJob, which writes to the same NativeArray (via TestParallelRestrictionJob.matrixArray). To guarantee safety, you must include TestParallelRestrictionJob as a dependency of the newly scheduled job.


    Here's the simplified code:
    Code (CSharp):
    1.  
    2.     private struct TestParallelRestrictionJob : IJobParallelFor {
    3.  
    4.         [NativeDisableParallelForRestriction] public NativeArray<Matrix4x4> matrixArray;
    5.  
    6.         public void Execute(int index) {
    7.         }
    8.     }
    9.  
    10.  
    11.     private void Testing() {
    12.         NativeArray<Matrix4x4> matrixArray = new NativeArray<Matrix4x4>(10, Allocator.TempJob);
    13.  
    14.         TestParallelRestrictionJob testParallelRestrictionJob_1 = new TestParallelRestrictionJob {
    15.             matrixArray = matrixArray
    16.         };
    17.         JobHandle jobHandle_1 = testParallelRestrictionJob_1.Schedule(10, 1);
    18.  
    19.         TestParallelRestrictionJob testParallelRestrictionJob_2 = new TestParallelRestrictionJob {
    20.             matrixArray = matrixArray
    21.         };
    22.         JobHandle jobHandle_2 = testParallelRestrictionJob_2.Schedule(10, 1);
    23.  
    24.         JobHandle.CompleteAll(ref jobHandle_1, ref jobHandle_2);
    25.  
    26.         matrixArray .Dispose();
    27.     }
    I'm trying to create a job to essentially merge arrays into one.
    So Job_1 puts BaseArrayA[0-10] into FinalArray[0-10] and Job_2 puts BaseArrayB[0-10] into FinalArray[10-20]
     
    Last edited: Jun 5, 2019
    harryblack likes this.
  2. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Try nativeslice
     
  3. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    First of all [NativeDisableParallelForRestriction] not solves safety restriction, it's only allow you to write to any index of passed array (if you sure in safety) in same job - solves parallel for restriction.
    Second - your sample code is obvious race condition - 2 parallel jobs uses same range of array.
    Third - what purpose of runnig two IJobParallel for different ranges of same array if you just can run one IJPF for whole range?
    Edit: About third - read text, below code snippet, now it's more clearly (cos your code snippet not relative vwith case which you described)
     
    Last edited: Jun 5, 2019
    Neiist and CodeMonkeyYT like this.
  4. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I think it’s just a bad choice of simplified system and it would have been better, if he showed his real code.

    That said, I think there are use cases to write from different parallel jobs to a result array (ie mesh) and if you can ensure it’s different ranges you can split up the array with native slice
     
    Tony_Max and CodeMonkeyYT like this.
  5. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    Here working sample for you, now you can rewrite it for your DrawMeshInstanced matrices purposes:)
    Code (CSharp):
    1. public class ParallelTestSystem : JobComponentSystem
    2. {
    3.     private struct TestParallelRestrictionJob : IJobParallelFor
    4.     {
    5.         public            int              startIndex;
    6.         [ReadOnly] public NativeArray<int> sourceArray;
    7.         [NativeDisableContainerSafetyRestriction]
    8.         public NativeArray<int> resultArray;
    9.  
    10.         public void Execute(int index)
    11.         {
    12.             var i = startIndex + index;
    13.             resultArray[i] = sourceArray[index];
    14.         }
    15.     }
    16.  
    17.  
    18.     protected override void OnCreateManager()
    19.     {
    20.         NativeArray<int> sourceA = new NativeArray<int>(5, Allocator.TempJob);
    21.         NativeArray<int> sourceB = new NativeArray<int>(5, Allocator.TempJob);
    22.         for (int i = 0; i < 5; i++)
    23.         {
    24.             sourceA[i] = i;
    25.             sourceB[i] = i + 5;
    26.         }
    27.  
    28.         NativeArray<int> resultArray = new NativeArray<int>(10, Allocator.TempJob);
    29.  
    30.         TestParallelRestrictionJob testParallelRestrictionJob_1 = new TestParallelRestrictionJob
    31.         {
    32.             startIndex  = 0,
    33.             resultArray = resultArray,
    34.             sourceArray = sourceA
    35.         };
    36.         JobHandle jobHandle_1 = testParallelRestrictionJob_1.Schedule(5, 1);
    37.  
    38.         TestParallelRestrictionJob testParallelRestrictionJob_2 = new TestParallelRestrictionJob
    39.         {
    40.             startIndex  = 5,
    41.             resultArray = resultArray,
    42.             sourceArray = sourceB
    43.         };
    44.         JobHandle jobHandle_2 = testParallelRestrictionJob_2.Schedule(5, 1);
    45.  
    46.         JobHandle.CompleteAll(ref jobHandle_1, ref jobHandle_2);
    47.  
    48.         for (int i = 0; i < resultArray.Length; i++)
    49.         {
    50.             Debug.Log(resultArray[i]);
    51.         }
    52.  
    53.         sourceA.Dispose();
    54.         sourceB.Dispose();
    55.         resultArray.Dispose();
    56.     }
    57.  
    58.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    59.     {
    60.         return inputDeps;
    61.     }
    62. }
    upload_2019-6-5_10-23-8.png
     
    Neiist, PrimalCoder and CodeMonkeyYT like this.
  6. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I am on mobile, if you want I can post an example with native slice later, it’s essentially the same thing, but I prefer it (as an alternative)
     
  7. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    NativeSlice not solves safety restriction cos it's just wrapper over real native array and use his pointer, it's still require
    [NativeDisableContainerSafetyRestriction] 
    in this case. Only difference is - we replacing
    startIndex 
    related logic to slices.
     
  8. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    - i use the code below for debugging (it creates a line mesh) - i added some comments for readability
    - if you need something dynamic that scales use @eizenhorn with startindex

    edit: @eizenhorn I agree with you that you still have the same container safety issue, but you do not need to disable the restriction, maybe something to alert Unity of?

    Code (CSharp):
    1.     public class SphereTargetSystem : JobComponentSystem
    2.     {
    3.         public Vector3[] Vertices;
    4.      
    5.         private EntityQuery prefabAGroup;
    6.         private EntityQuery prefabBGroup;
    7.      
    8.         [BurstCompile]
    9.         struct SphereTargetJob : IJobParallelFor
    10.         {
    11.             [ReadOnly] public NativeArray<Translation> TranslationsStart;
    12.             [ReadOnly] public NativeArray<Translation> TranslationsEnd;
    13.             [WriteOnly, NativeDisableParallelForRestriction] public NativeSlice<Vector3> Results;
    14.          
    15.             public void Execute(int i)
    16.             {
    17.                 var minDistance = float.MaxValue;
    18.                 int minPos = 0;
    19.                 for (int j = 0; j < TranslationsEnd.Length; j++)
    20.                 {
    21.                     var distance = math.distancesq(TranslationsStart[i].Value, TranslationsEnd[j].Value);
    22.                     // math.select and if compiles to the same machine code in burst
    23.                     if (distance < minDistance)
    24.                     {
    25.                         minPos = j;
    26.                         minDistance = distance;
    27.                     }
    28.                   }
    29.                 Results[i*2] = TranslationsStart[i].Value;
    30.                 Results[i*2+1] = TranslationsEnd[minPos].Value;
    31.             }
    32.         }
    33.  
    34.      
    35.         protected override void OnCreateManager()
    36.         {
    37.             prefabAGroup = GetEntityQuery(ComponentType.ReadOnly<PrefabATag>(), ComponentType.ReadOnly<Translation>());
    38.             prefabBGroup = GetEntityQuery(ComponentType.ReadOnly<PrefabBTag>(), ComponentType.ReadOnly<Translation>());
    39.         }
    40.  
    41.  
    42.         protected override JobHandle OnUpdate(JobHandle inputDependencies)
    43.         {
    44.             // prepare 2 parallel job handles
    45.             JobHandle handleA, handleB;
    46.          
    47.             // setup results array
    48.             var lengthA = prefabAGroup.CalculateLength();
    49.             var lengthB = prefabBGroup.CalculateLength();
    50.          
    51.             var vertices = new NativeArray<Vector3>(lengthA * 2 + lengthB * 2, Allocator.TempJob);
    52.             NativeSlice<Vector3> verticesA = new NativeSlice<Vector3>(vertices, 0, lengthA * 2);
    53.             NativeSlice<Vector3> verticesB = new NativeSlice<Vector3>(vertices, lengthA * 2, lengthB * 2);
    54.          
    55.             // get translation array of prefab A and prefab B
    56.             var translationsA = prefabAGroup.ToComponentDataArray<Translation>(Allocator.TempJob, out handleA);
    57.             var translationsB = prefabBGroup.ToComponentDataArray<Translation>(Allocator.TempJob, out handleB);
    58.             inputDependencies = JobHandle.CombineDependencies(handleA, handleB, inputDependencies);
    59.  
    60.             // schedule jobs
    61.             handleA = new SphereTargetJob
    62.             {
    63.                 TranslationsStart = translationsA,
    64.                 TranslationsEnd = translationsB,
    65.                 Results = verticesA
    66.             }.Schedule(translationsA.Length, 16, inputDependencies);
    67.          
    68.             handleB = new SphereTargetJob
    69.             {
    70.                 TranslationsStart = translationsB,
    71.                 TranslationsEnd = translationsA,
    72.                 Results = verticesB
    73.             }.Schedule(translationsB.Length, 16, inputDependencies);
    74.          
    75.             // combine the parallel dependencies
    76.             inputDependencies = JobHandle.CombineDependencies(handleA, handleB);
    77.          
    78.             inputDependencies.Complete();
    79.          
    80.             // copy results to managed array - drawn in late update on a MB with meshrenderer
    81.             Vertices = vertices.ToArray();
    82.  
    83.             // cleanup native containers
    84.             translationsA.Dispose();
    85.             translationsB.Dispose();
    86.             vertices.Dispose();
    87.  
    88.             return inputDependencies;
    89.         }
    90.     }  
    91.  
     
    Last edited: Jun 5, 2019
  9. CodeMonkeyYT

    CodeMonkeyYT

    Joined:
    Dec 22, 2014
    Posts:
    125
    Yeah sorry I simplified my example code a bit too much, I'm trying to write to different indexes in the same array on different jobs so there's no race condition.

    It appears the solution is to use [NativeDisableContainerSafetyRestriction] which is inside Unity.Collections.LowLevel.Unsafe
    I saw that attribute when I was searching for a solution but since I didn't have that namespace I just assumed it was something that was deprecated in favor of [NativeDisableParallelForRestriction]

    Everything is working perfectly now.
    I guess my question is exactly what does [NativeDisableParallelForRestriction] do?
    The documentation doesn't say anything https://docs.unity3d.com/ScriptRefe...veDisableParallelForRestrictionAttribute.html

    Thanks everyone!
     
    adammpolak likes this.
  10. andrewgotow

    andrewgotow

    Joined:
    Dec 28, 2013
    Posts:
    18
    NativeCollections which can be accessed in parallel all contain three fields,
    m_Length
    ,
    m_MinIndex
    , and
    m_MaxIndex
    . When accessing an element, the collection checks that the index being read or written to is between the min and max values.

    This normally doesn't do much - the min and max index are initialized to fit the entire length of the collection, however there are special rules for
    IJobParallelFor
    . When an
    IJobParallelFor
    is scheduled, the Job system will copy the native collection (just the struct, not the actual data since that would kill performance), and will change the values of
    m_MinIndex
    and
    m_MaxIndex
    for each parallel batch.

    For example, scheduling an
    IJobParallelFor
    with a batch size of 16 on an array of 40 elements will create three job instances. The first will receive a copy of the native collection with an index range of [0, 15], the second [16, 31] and the third [32, 40]. If any of those three jobs tries to access a value outside of their defined range, the collection will throw an exception, since that has been deemed unsafe. Those values may be changed by one of the other parallel batches as you're trying to read them!

    ---

    There are scenarios where you as the programmer can guarantee that each parallel job batch will only access values that aren't touched by any other batch algorithmically, but the values aren't in a linear order. In this case, dividing the data up into chunks and protecting against unordered access doesn't actually help, and the restriction will only get in your way.

    In this case, you can use the
    NativeDisableParallelForRestriction
    attribute to turn off this range check, allowing your batches to access elements of the collection randomly. This isn't the default behavior though, since most of the time you probably just want to iterate through an array and parallel write errors are a major pain.
     
    Totenko and SenseEater like this.
  11. JackalLiu

    JackalLiu

    Joined:
    Jan 3, 2018
    Posts:
    1
    hello and welcome~ You are my code monkey~ And this topic solved my problem~ ok, lets begin!
     
    Neiist likes this.
  12. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,428
    Good to show appreciation but please use the Like button so as not to necro threads.

    Thanks.