Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Feedback An overview of how queries, systemAPI and jobs are executed within systems

Discussion in 'Entity Component System' started by rubcc95, May 10, 2023.

  1. rubcc95

    rubcc95

    Joined:
    Dec 27, 2019
    Posts:
    222
    This is a simple code to move entities:
    Code (CSharp):
    1. public struct Movement : IComponentData
    2. {
    3.     public float3 value;
    4. }
    5.  
    6. public struct Position : IComponentData
    7. {
    8.     public float3 value;
    9. }
    10.  
    11. public partial struct MovementSystem : ISystem
    12. {
    13.     [BurstCompile]
    14.     void ISystem.OnCreate(ref SystemState state)
    15.     {
    16.         state.RequireForUpdate<Position>();
    17.         state.RequireForUpdate<Movement>();
    18.     }
    19.  
    20.     [BurstCompile]
    21.     void ISystem.OnUpdate(ref SystemState state)
    22.     {
    23.         var delta = Time.deltaTime;
    24.         foreach (var (transform, moveable) in SystemAPI.Query<RefRW<Position>, RefRO<Movement>>())
    25.             transform.ValueRW.value += moveable.ValueRO.velocity * delta;
    26.     }
    27. }
    About 0.12 ms to iterate over 50,000 entities. Not bad for having no idea what SystemAPI does inside. I just know that using Time.deltaTime saves my iterations around 0.02 ms instead of SystemAPI.Time.DeltaTime, so whenever I'm using the MainThread I'll use it (correct?).

    Code (CSharp):
    1.  
    2. public partial struct JobEntityMovemenSystem : ISystem
    3. {
    4.     [BurstCompile]
    5.     void ISystem.OnCreate(ref SystemState state)
    6.     {
    7.         state.RequireForUpdate<Position>();
    8.         state.RequireForUpdate<Movement>();
    9.     }
    10.  
    11.     [BurstCompile]
    12.     void ISystem.OnUpdate(ref SystemState state) => new Job { delta = Time.deltaTime }.Schedule();
    13.  
    14.  
    15.     [BurstCompile]
    16.     partial struct Job : IJobEntity
    17.     {
    18.         public float delta;
    19.  
    20.         [BurstCompile]
    21.         public void Execute(in Movement moveable, ref Position position) => position.value += moveable.value * delta;    
    22.     }
    23. }
    24.  
    Code (CSharp):
    1.  
    2. public partial struct JobEntityMovemenSystem : ISystem
    3. {
    4.     [BurstCompile]
    5.     void ISystem.OnCreate(ref SystemState state)
    6.     {
    7.         state.RequireForUpdate<Position>();
    8.         state.RequireForUpdate<Movement>();
    9.     }
    10.  
    11.     [BurstCompile]
    12.     void ISystem.OnUpdate(ref SystemState state) => new Job { delta = Time.deltaTime }.Schedule();
    13.  
    14.  
    15.     [BurstCompile]
    16.     partial struct Job : IJobEntity
    17.     {
    18.         public float delta;
    19.  
    20.         [BurstCompile]
    21.         public void Execute(in Movement moveable, ref Position position) => position.value += moveable.value * delta;    
    22.     }
    23. }
    24.  
    Above is my code for IJobEntity. Without parallel execution, the execution slows down to no less than 0.05/0.06 ms and sometimes drops to 0.02x ms momentarily (why?). Let's continue, this last one can be a little more lazy without commenting...

    Code (CSharp):
    1. public partial struct JobChunkMovementSystem : ISystem
    2. {
    3.     EntityQuery _query;
    4.  
    5.     ComponentTypeHandle<Movement> _movHandle;
    6.     ComponentTypeHandle<Position> _posHandle;
    7.  
    8.     [BurstCompile]
    9.     void ISystem.OnCreate(ref SystemState state)
    10.     {
    11.      
    12.         state.RequireForUpdate<Position>();
    13.         state.RequireForUpdate<Movement>();
    14.  
    15.         var builder = new EntityQueryBuilder(Allocator.Temp).WithAll<Movement>().WithAllRW<Position>();
    16.         _query = state.GetEntityQuery(builder);
    17.         builder.Dispose();
    18.  
    19.         _posHandle = state.GetComponentTypeHandle<Position>();
    20.         _movHandle = state.GetComponentTypeHandle<Movement>(true);
    21.     }
    22.  
    23.     [BurstCompile]
    24.     void ISystem.OnUpdate(ref SystemState state)
    25.     {
    26.         _posHandle.Update(ref state);
    27.         _movHandle.Update(ref state);
    28.         state.Dependency = new Job { movHandle = _movHandle, posHandle = _posHandle, delta = Time.deltaTime }.Schedule(_query, state.Dependency);    
    29.     }
    30.  
    31.  
    32.     [BurstCompile]
    33.     struct Job : IJobChunk
    34.     {
    35.         [ReadOnly] public ComponentTypeHandle<Movement> movHandle;
    36.         public ComponentTypeHandle<Position> posHandle;
    37.         public float delta;
    38.  
    39.         [BurstCompile]
    40.         void IJobChunk.Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
    41.         {
    42.             //var delta = Time.deltaTime;
    43.             var positions = chunk.GetNativeArray(ref posHandle);
    44.             var movements = chunk.GetNativeArray(ref movHandle);
    45.             for (int i = 0; i < positions.Length; i++)
    46.                 positions[i] = new Position { value = positions[i].value + movements[i].value * delta };
    47.             positions.Dispose();
    48.             movements.Dispose();
    49.         }
    50.     }
    51. }
    As a newbie to this framework, it took me walking through manuals and hours to get to this implementation of IJobChunk... 0.08ms??? ok, it's less than the initial implementation but 0.02/0.03 ms slower than the IJobEntity. The difference is reduced if we run them with ScheduleParallel but it's still slower... if IjobEntity is supposed to use IJobChunk to do its implementations... what part am I missing? why my latest implementation is not optimal?

    I think I don't understand the bases of how queries work and how entities manage them internally
     
  2. rubcc95

    rubcc95

    Joined:
    Dec 27, 2019
    Posts:
    222
    It seems that JobChunk gets a substantial improvement by using pointers in an unsafe context instead of creating a NativeArray and using dispose afterwards (of course, what a thing :))) Now JobChunk is like 0.005ms faster than JobEntity! !!!!

    Code (CSharp):
    1.         [BurstCompile]
    2.         unsafe void IJobChunk.Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
    3.         {
    4.             //var delta = Time.deltaTime;
    5.             var positions = chunk.GetComponentDataPtrRO(ref posHandle);
    6.             var movements = chunk.GetComponentDataPtrRO(ref movHandle);
    7.             for (int i = 0; i < positions.Length; i++)
    8.                 positions[i] = new Position { value = positions[i].value + movements[i].value * delta };
    9.         }