Search Unity

Job not optimized or waiting for something... Not Sure..

Discussion in 'Entity Component System' started by Ziboo, Oct 24, 2019.

  1. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Hi,

    I have a JobComponent system. The point is to find Interactables around any InteractableActors, and find the best one the Actor can interact with by calculating a score.

    This is the JobComponentSystem:

    Code (CSharp):
    1.  public class InteractableSystems : JobComponentSystem
    2.     {
    3.         private EntityQuery actorsQuery;
    4.         private EntityQuery playerQuery;
    5.         private EntityQuery interactablesQuery;
    6.  
    7.         protected override void OnCreate()
    8.         {
    9.             this.actorsQuery = this.GetEntityQuery(ComponentType.ReadOnly<InteractableActor>(), ComponentType.ReadOnly<Translation>());
    10.             this.interactablesQuery = this.GetEntityQuery(ComponentType.ReadOnly<Interactable>(), ComponentType.ReadOnly<Translation>(),
    11.                 ComponentType.Exclude<InteractableDisabled>());
    12.             this.RequireForUpdate(this.actorsQuery);
    13.         }
    14.  
    15.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    16.         {
    17.             var handle = new ClearBuffersJob().Schedule(this, inputDeps);
    18.  
    19.             handle = new FindInteractbleAroundDistanceJob
    20.             {
    21.                 Distance = 2,
    22.                 InteractablesEntities = this.interactablesQuery.ToEntityArray(Allocator.TempJob),
    23.                 InteractablesPositions = this.interactablesQuery.ToComponentDataArray<Translation>(Allocator.TempJob)
    24.             }.Schedule(this, handle);
    25.  
    26.  
    27.             handle = new FindInteractableInRangeJob
    28.             {
    29.                 MinDistance = 1f
    30.             }.Schedule(this, handle);
    31.  
    32.             return handle;
    33.         }
    34.  
    35.  
    36.         [BurstCompile]
    37.         private struct ClearBuffersJob : IJobForEachWithEntity_EB<InteractableAroundBuffer>
    38.         {
    39.             public void Execute(Entity entity, int index, DynamicBuffer<InteractableAroundBuffer> b0)
    40.             {
    41.                 b0.Clear();
    42.             }
    43.         }
    44.  
    45.         [BurstCompile]
    46.         [RequireComponentTag(typeof(InteractableActor))]
    47.         private struct FindInteractbleAroundDistanceJob : IJobForEachWithEntity_EBC<InteractableAroundBuffer, Translation>
    48.         {
    49.             public int Distance;
    50.  
    51.             [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<Entity> InteractablesEntities;
    52.  
    53.             [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<Translation> InteractablesPositions;
    54.  
    55.             public void Execute(Entity entity, int index, DynamicBuffer<InteractableAroundBuffer> buffer, ref Translation translation)
    56.             {
    57.                 for (var i = 0; i < this.InteractablesEntities.Length; i++)
    58.                 {
    59.                     if (math.distancesq(translation.Value, this.InteractablesPositions[i].Value) < this.Distance * this.Distance)
    60.                     {
    61.                         buffer.Add(new InteractableAroundBuffer
    62.                         {
    63.                             InteractableEntity = this.InteractablesEntities[i],
    64.                             Position = this.InteractablesPositions[i].Value
    65.                         });
    66.                     }
    67.                 }
    68.             }
    69.         }
    70.  
    71.         [BurstCompile]
    72.         private struct FindInteractableInRangeJob : IJobForEachWithEntity_EBCC<InteractableAroundBuffer, Translation, Rotation>
    73.         {
    74.             public float MinDistance;
    75.  
    76.             public void Execute(Entity entity, int index, DynamicBuffer<InteractableAroundBuffer> b, ref Translation t, ref Rotation r)
    77.             {
    78.                 var closestIndex = -1;
    79.                 var maxScore = float.NegativeInfinity;
    80.  
    81.                 for (var i = 0; i < b.Length; i++)
    82.                 {
    83.                     var interactable = b[i];
    84.  
    85.                     var dir = interactable.Position - t.Value;
    86.  
    87.                     var dist = math.lengthsq(dir);
    88.                     var dot = math.dot(math.forward(r.Value), math.normalize(dir));
    89.  
    90.                     var score = 1f / dist * math.remap(-1, 1, 1, 3, dot);
    91.  
    92.                     if (score > maxScore && dist <= this.MinDistance)
    93.                     {
    94.                         maxScore = score;
    95.                         closestIndex = i;
    96.                     }
    97.                 }
    98.  
    99.                 if (closestIndex != -1)
    100.                 {
    101.                     var e = b[closestIndex];
    102.                     e.IsInRange = true;
    103.                     b[closestIndex] = e;
    104.                 }
    105.             }
    106.         }
    107.  
    108.     }

    I'm not sure what's happening...
    Maybe the code is really not optimized or how exactly I should interpret the Profiler and Entity Debugger
    because the jobs take in average 0.16 ms (In Entity Debugger / Profiler)

    For reference, I only have 1 Actor and 6 Interactables for now.

    This is the Entity Debugger view:
    upload_2019-10-23_20-48-28.png

    The Profiler:
    upload_2019-10-23_20-49-28.png

    And the Timeline has:

    ClearBuffersJob (Burst): 0.003 ms (Worker 3)
    ClearBuffersJob (CleanUp): 0.001 ms (Worker 3)
    FindInteractbleAroundDistanceJob (Burst): 0.003 ms (Worker 5)
    FindInteractableInRangeJob (Burst): 0.002 ms (Worker 5)

    Which makes a total of 0.008 ms...
    Not 0.16 ms...

    So my questions are:
    1. Should I trust the time in the EntityDebugger / Profiler ?
    2. Is it because I don't have much yet so everything has to wait and in fact I'm only taking 0.008 ms to do the job.. (clue from the WaitForJobGroupID)
    3. My code is just garbage.
    4. Why it is generating GC ??

    Thanks !
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    1. I usually don't with this empty of a project. There's not really much point in optimizing until you get a little closer to your frame budget.
    2. There's probably some overhead for transitioning the worker threads out of idle.
    3. For someone diving into DOTS you got a pretty solid grasp of the concepts. There's definitely room for optimization, but I wouldn't worry about it until you need it.
    4. Probably the safety system in the editor from your NativeArrays. That won't be there in a build. Again, nothing to worry about.

    Edit: There's an option that has an out JobHandle for the TCDA and TEA. Combine those dependencies with the ClearBuffers job. Might give you a better picture of what is going on.
     
    Last edited: Oct 24, 2019
  3. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    What if you look into jobs in profiler? I know you will have most likely only one thread running, but data is represented slightly different. I know hierarchically vs timeline times show some discrepancies.

    Other than that, I also suspect some internal safety checks/syncs.
     
  4. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Same thing than Timeline:
    upload_2019-10-23_21-48-1.png

    So based on the feedback you're giving me, I think Unity should maybe do something to have a better Time displayed in the EntityDebugger, because it is super misleading.

    I know you only optimize at the end, but for instance in my case, I started doing this System with Events, and then switch to buffers, to see if it was better (performance and usability in other systems.)

    So if we need to dig into the timeline each time, it's pretty annoying.

    Also, maybe I'm missing something, but finding a job in the Timeline is a pain !
    In Hierarchy we have a search bar but not in Timeline

    I hope they could had it to the Timeline too.
     
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Throw some more Entities at the problem and you will get a better idea. It doesn't matter which approach is better if they are both negligible. On my system, I've uncovered that scheduling a job can take up to 30 microseconds. I had to push up to around 500 entities to get a good enough signal to noise ratio to even make any optimization decisions. And that was for an O(n log n) job I was trying to optimize.
     
  6. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    Yep more entities definitely.
    Or maybe even stress test, while comparing approaches.
     
  7. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    So I added a lot of entities.
    I have 2000 interactables now.

    The Jobs don't seems to grows, I'm still under 0.01 ms for every Burst jobs.
    upload_2019-10-24_13-9-39.png

    But the System Time grows
    upload_2019-10-24_13-11-9.png

    Because you try to choose an architecture for a system, but if you have false numbers to profile It's really hard to know if it's gonna be an issue in the future or not.
     
  8. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    The main thread time increases because in the system you are completing dependencies for Translations and forcing the memcpy into the NativeArrays to complete during the system's OnUpdate(). You can fix that by using the JobHandle variant of ToComponentDataArray.
     
  9. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Thanks for the answer but I'm not sure I get what you mean ...
    Can you give me some direction please ?
    Thanks
     
  10. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Right after you schedule the ClearBuffers job:
    Code (CSharp):
    1. InteractablesEntities = this.interactablesQuery.ToEntityArray(Allocator.TempJob, out JobHandle entityHandle);
    2. InteractablesPositions = this.interactablesQuery.ToComponentDataArray<Translation>(Allocator.TempJob, out JobHandle translationHandle);
    3. handle = JobHandle.CombineDependencies(entityHandle, translationHandle, handle);
    Warning, typed without an IDE from memory. It may not work exactly as is.
     
  11. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Thanks I tried, and it seems to remove the WaitForJob. Progress :)

    But EntityDebugger stills says that the system take ~0.16 ms.. :(
     
  12. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    You do realize how little 0.16ms is right. Have you compiled it and profiled it at runtime?
     
  13. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Yes but just trying to figure out if I'm utilizing the ECS / Jobs correctly.
    I didn't profile at runtime, for sure might be a better test.

    It's just that if you add up the systems (let's say a 100) you have your 16 ms / 60 FPS.

    But mostly it's for education. All the jobs takes 0.008 ms and the system 0.16 ms.
    So just trying to understand what's happening.