Search Unity

Question Does Enableable components conflict with performance

Discussion in 'Entity Component System' started by LittleBigRex, Nov 30, 2022.

  1. LittleBigRex

    LittleBigRex

    Joined:
    Aug 11, 2021
    Posts:
    11
    I have a question about Enableable components. The advantage of ECS is to concentrate the required data into continuous chunks. But Enableable components causes the data to be queried scattered in different chunks. Doesn't it conflict with data-oriented processing?Are the performance implications of using it smaller than structural changes?
     
    JDespland likes this.
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    Many different components on entities, leads to many archetypes.
    Same applies, if adding and removing components.
    However, enabling/disabling components, that is just a bit mask.
    So if jobs query entities with matching archetype, they check bitmask, if components of particular archetype are enabled.

    This approach doesn't conflict with Data Oriented Programming / Design.
    There is many use cases, when you want to filter entities by components.
    To avoid structural changes, and fragmentation of chunks, plus creating many variants of archetypes, when adding and removing components, you simply toggle state of that components. Meaning, no structural change, no fragmentation, no new archetype, no moving to different chunk..
    Job filtering query does the rest.
     
    CodeSmile and LittleBigRex like this.
  3. LittleBigRex

    LittleBigRex

    Joined:
    Aug 11, 2021
    Posts:
    11
    Thanks for your answer!
    I mean chunks will contain many entities with disabled components. Won't it slow down the query job?
     
  4. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    It is literally a 128 bitmask check.
    I don't know underlining code for the checking, but I do suspect, it is just AND-ing and early exit.
    So in that sense, there is a little overhead, maybe two CPU instructions?
    But that cost is far lower, than having fragmented chunks, doe to structural changes.

    https://github.com/Unity-Technologi...509de/DOTS_Guide/cheatsheet/jobs.md#ijobchunk

    An example IJobChunk.
    Code (CSharp):
    1. [BurstCompile]
    2. public partial struct MyIJobChunk : IJobChunk
    3. {
    4.     // The job needs type handles for each component type
    5.     // it will access from the chunks.
    6.     public ComponentTypeHandle<Foo> FooHandle;
    7.     // Handles for components that will only be read should be
    8.     // marked with [ReadOnly].
    9.     [ReadOnly] public ComponentTypeHandle<Bar> BarHandle;
    10.     // The entity type handle is needed if we
    11.     // want to read the entity ID's.
    12.     public EntityTypeHandle EntityHandle;
    13.     // Jobs should not use an EntityManager to create and modify
    14.     // entities directly. Instead, a job can record commands into
    15.     // an EntityCommandBuffer to be played back later on the
    16.     // main thread at some point after the job has been completed.
    17.     // If the job will be scheduled with ScheduleParallel(),
    18.     // we must use an EntityCommandBuffer.ParallelWriter.
    19.     public EntityCommandBuffer.ParallelWriter Ecb;
    20.     // When this job runs, Execute() will be called once for each
    21.     // chunk matching the query that was passed to Schedule().
    22.     // The useEnableMask param is true if any of the
    23.     // entities in the chunk have disabled components
    24.     // of the query. In other words, this param is true
    25.     // if any entities in the chunk should be skipped over.
    26.     // The chunkEnabledMask identifies which entities
    27.     // have all components of the query enabled, i.e. which entities
    28.     // should be processed:
    29.     //   - A set bit indicates the entity should be processed.
    30.     //   - A cleared bit indicates the entity has one or more
    31.     //     disabled components and so should be skipped.
    32.     [BurstCompile]
    33.     public void Execute(in ArchetypeChunk chunk,
    34.             int unfilteredChunkIndex,
    35.             bool useEnableMask,
    36.             in v128 chunkEnabledMask)
    37.     {
    38.         // Get the entity ID and component arrays from the chunk.
    39.         NativeArray<Entity> entities =
    40.                 chunk.GetNativeArray(EntityHandle);
    41.         NativeArray<Foo> foos =
    42.                 chunk.GetNativeArray(FooHandle);
    43.         NativeArray<Bar> bars =
    44.                 chunk.GetNativeArray(BarHandle);
    45.         // The ChunkEntityEnumerator helps us loop over
    46.         // the entities of the chunk, but only those that
    47.         // match the query (accounting for disabled components).
    48.         var enumerator = new ChunkEntityEnumerator(
    49.                 useEnableMask,
    50.                 chunkEnabledMask,
    51.                 chunk.ChunkEntityCount);
    52.         // Loop over all entities in the chunk that match the query.
    53.         while (enumerator.NextEntityIndex(out var i))
    54.         {
    55.             // Read the entity ID and component values.
    56.             var entity = entities;
    57.             var foo = foos;
    58.             var bar = bars;
    59.             // If the Bar value meets a criteria, we
    60.             // record a command in the ECB to remove it.
    61.             if (bar.Value < 0)
    62.             {
    63.                 Ecb.RemoveComponent<Bar>(unfilteredChunkIndex, entity);
    64.             }
    65.             // Set the Foo value.
    66.             foos = new Foo { };
    67.         }
    68.     }
    69. }
     
    LittleBigRex likes this.
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    There are many advantages to ECS. This is merely one of them, and only applies to an Archetype ECS (compared to a Sparse Set ECS).

    In an Archetype ECS, changing an entity's archetype can be pretty expensive, as it will often involve a random access on every component of the entity involved, plus some other random entity that has to move for chunk compaction reasons. In the case where some behavior needs to be toggled on an entity frequently, it may be cheaper to just check a bool value on the entity and conditionally perform the remaining logic. Enableable components take this a step further by compacting the bool values into a bunch of bits on a per-chunk basis and uses bitwise operations to evaluate multiple enableable components at once across the entire chunk.

    With regards to cache coherency, if the number of enabled component values in a chunk is sparse, you lose out on cache benefits, but you also do significantly less work. As the number of enabled component values increases, cache benefits also increase.

    When to use them is something you will have to feel out. You don't want to use them in all circumstances.
     
  6. 8bitgoose

    8bitgoose

    Joined:
    Dec 28, 2014
    Posts:
    448
    If this is a 128 bit mask, does that limit the number of components to 128 per chunk?
     
  7. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    128 entities per chunk. A 128 bit mask corresponds to a single component type for multiple entities.
     
  8. 8bitgoose

    8bitgoose

    Joined:
    Dec 28, 2014
    Posts:
    448
    Whoops, wrote this too fast. Yeah that is what I meant. 128 entities per chunk? What if the chunk has no enablable components. Is this limit still enforced?
     
  9. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Yes.

    That means each entity can have 128 bytes worth of components before chunk occupancy is limited by archetype size rather than the 128 entity limit (yes, both numbers are 128).
     
    CodeSmile likes this.
  10. 8bitgoose

    8bitgoose

    Joined:
    Dec 28, 2014
    Posts:
    448
    Okay cool. Thanks for the clarification. I guess 128 bytes isn't that much data for an entire entity. Hopefully I don't run into an issue where I am limited by this and just wasting chunk space.
     
  11. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    If my calculations are correct (or correct me please):

    Code (CSharp):
    1. 16    kB / Chunk
    2. 16 * 1024 =
    3. 16384 Bytes / Chunk
    4. 128   Entities / Chunk
    5.  
    6. That gives:
    7. 128   Bytes / Entity
    8. 4     Bytes / Float
    9. 32    Floats / Entity
    10.  
    11. Example:
    12. 16    Floats / Matrix 4x4
    13. 2     Matrixes 4x4 / Entity
    So anything below 128 Bytes in Entity, that will yield chunks wasting.
     
    Last edited: Dec 9, 2022
  12. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    16 kB is actually 16 * 1024 which also happens to be 128^2.

    So anything less than 128 bytes per entity is potential chunk wasting. And anything more reduces the number of entities per chunk which means more chunks.
     
    bb8_1, Antypodish and Anthiese like this.
  13. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    Yeah, that makes sense.
    Fraction values were odd.
    I have corrected. Thx.
     
  14. 8bitgoose

    8bitgoose

    Joined:
    Dec 28, 2014
    Posts:
    448
    I could see some situations where having more than 128 entities per chunk would be needed. If you had a double linked list entity path which had

    12 bytes for the position
    8 bytes for the entity before
    8 bytes for the entity after
    4 bytes for random information about that entity point

    That is 32 bytes per entity, so you could have 500 entities per chunk. But are limited to 128 entities per chunk, your chunks are gonna be 4 times as much with the limit.

    But not sure if that is a good example. Anyways, thanks for the info!