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

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

 public class InteractableSystems : JobComponentSystem
    {
        private EntityQuery actorsQuery;
        private EntityQuery playerQuery;
        private EntityQuery interactablesQuery;

        protected override void OnCreate()
        {
            this.actorsQuery = this.GetEntityQuery(ComponentType.ReadOnly<InteractableActor>(), ComponentType.ReadOnly<Translation>());
            this.interactablesQuery = this.GetEntityQuery(ComponentType.ReadOnly<Interactable>(), ComponentType.ReadOnly<Translation>(),
                ComponentType.Exclude<InteractableDisabled>());
            this.RequireForUpdate(this.actorsQuery);
        }

        protected override JobHandle OnUpdate(JobHandle inputDeps)
        {
            var handle = new ClearBuffersJob().Schedule(this, inputDeps);

            handle = new FindInteractbleAroundDistanceJob
            {
                Distance = 2,
                InteractablesEntities = this.interactablesQuery.ToEntityArray(Allocator.TempJob),
                InteractablesPositions = this.interactablesQuery.ToComponentDataArray<Translation>(Allocator.TempJob)
            }.Schedule(this, handle);


            handle = new FindInteractableInRangeJob
            {
                MinDistance = 1f
            }.Schedule(this, handle);

            return handle;
        }


        [BurstCompile]
        private struct ClearBuffersJob : IJobForEachWithEntity_EB<InteractableAroundBuffer>
        {
            public void Execute(Entity entity, int index, DynamicBuffer<InteractableAroundBuffer> b0)
            {
                b0.Clear();
            }
        }

        [BurstCompile]
        [RequireComponentTag(typeof(InteractableActor))]
        private struct FindInteractbleAroundDistanceJob : IJobForEachWithEntity_EBC<InteractableAroundBuffer, Translation>
        {
            public int Distance;

            [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<Entity> InteractablesEntities;

            [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<Translation> InteractablesPositions;

            public void Execute(Entity entity, int index, DynamicBuffer<InteractableAroundBuffer> buffer, ref Translation translation)
            {
                for (var i = 0; i < this.InteractablesEntities.Length; i++)
                {
                    if (math.distancesq(translation.Value, this.InteractablesPositions[i].Value) < this.Distance * this.Distance)
                    {
                        buffer.Add(new InteractableAroundBuffer
                        {
                            InteractableEntity = this.InteractablesEntities[i],
                            Position = this.InteractablesPositions[i].Value
                        });
                    }
                }
            }
        }

        [BurstCompile]
        private struct FindInteractableInRangeJob : IJobForEachWithEntity_EBCC<InteractableAroundBuffer, Translation, Rotation>
        {
            public float MinDistance;

            public void Execute(Entity entity, int index, DynamicBuffer<InteractableAroundBuffer> b, ref Translation t, ref Rotation r)
            {
                var closestIndex = -1;
                var maxScore = float.NegativeInfinity;

                for (var i = 0; i < b.Length; i++)
                {
                    var interactable = b[i];

                    var dir = interactable.Position - t.Value;

                    var dist = math.lengthsq(dir);
                    var dot = math.dot(math.forward(r.Value), math.normalize(dir));

                    var score = 1f / dist * math.remap(-1, 1, 1, 3, dot);

                    if (score > maxScore && dist <= this.MinDistance)
                    {
                        maxScore = score;
                        closestIndex = i;
                    }
                }

                if (closestIndex != -1)
                {
                    var e = b[closestIndex];
                    e.IsInRange = true;
                    b[closestIndex] = e;
                }
            }
        }

    }

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:

The Profiler:

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:

  • Should I trust the time in the EntityDebugger / Profiler ?
  • 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)
  • My code is just garbage.
  • Why it is generating GC ??

Thanks !

  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.

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.

Same thing than Timeline:

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.

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.

Yep more entities definitely.
Or maybe even stress test, while comparing approaches.

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.

But the System Time grows

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.

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.

Thanks for the answer but I’m not sure I get what you mean …
Can you give me some direction please ?
Thanks

Right after you schedule the ClearBuffers job:

InteractablesEntities = this.interactablesQuery.ToEntityArray(Allocator.TempJob, out JobHandle entityHandle);
InteractablesPositions = this.interactablesQuery.ToComponentDataArray<Translation>(Allocator.TempJob, out JobHandle translationHandle);
handle = JobHandle.CombineDependencies(entityHandle, translationHandle, handle);

Warning, typed without an IDE from memory. It may not work exactly as is.

Thanks I tried, and it seems to remove the WaitForJob. Progress :slight_smile:

But EntityDebugger stills says that the system take ~0.16 ms… :frowning:

You do realize how little 0.16ms is right. Have you compiled it and profiled it at runtime?

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.