Search Unity

IJobChunk, performance & documentation misunderstanding

Discussion in 'Data Oriented Technology Stack' started by Omniaffix-Dave, Aug 15, 2019.

  1. Omniaffix-Dave

    Omniaffix-Dave

    Joined:
    Jan 11, 2019
    Posts:
    4
    While first learning the ECS approach I was adding and removing empty components in IForEachWithEntities with entitycommandbuffers on just about everything I was doing. I thought this was the ideal way to get a system to run on an Entity. The performance gain was still amazing, and I liked the organization and development speed, so I didn't think much of it. Other places on the forums I saw the "Unity Technologies" black tagged users saying that it's incredibly cheap to do(add/remove components /create entities) so with Unity ECS, so its normal practice. I started to notice that my begin/end simulationECB was getting pretty heavy. So while planning for optimization I decided I wanted to try out an IJobChunk instead of an IForEach, and attempt a different approach.

    From what I read in the documentation, the setup would be a little longer, but in particular cases the benefit could be worth it because you have more control.

    I still have much to learn, so I may have made the wrong choice for this particular job, but the reason I went with an IJobChunk was largely in part because of the following line in the documentation.

    "If you only need to update entities when a component value has changed, you can add that component type to the change filter of the EntityQuery used to select the entities and chunks for the job."

    I immediately imagined a scenario where instead of adding and removing components or creating and destroying entities, I would reuse them. I though this would be a simple way for me to avoid that large build up that was occurring from the ECB. I then started to put into place a replacement system with this IJobChunk, and ran into this code example in the documentation:

     m_Group.SetFilterChanged(new ComponentType{ typeof(InputA), typeof(InputB)});

    I thought it was strange that we were trying to make a component type with two types inside of it to try to filter through, but I tried writing it out anyway. I think this is a simple typo, really we should have

     m_Group.SetFilterChanged(new[] { typeof(InputA), typeof(InputB)});

    Either way I was able to run successfully and I was pretty happy with the performance, but then I found a bug in my code and decided to log out how many times the chunk iteration was being called to ensure my filter was working. I realized that it wasn't doing at all what I expected, every time a new entity was added that fit into the chunk the entire chunk would iterate again.

    I am still confused in particular about the "when a component value has changed" part. As I was logging out the amount of iterations that was happening it didn't feel consistent, it felt like the filter was somewhat working. Is that line actually correct, if there was a single byte in a component and that was unchanged but the chunk changed would that entity be excluded? Was the filter not working because other values in the chunk outside that filteredtypeof were changing (i.e Translation)?

    I understand slightly how chunks are working, and that the chunk has changed, but I thought the filter would filter all previous entities with the (typeof().values) that were unchanged in that chunk. I could see the filter still being useful to stop the job running when the chunk is unchanged AND the values in that filtered type are unchanged. Did I misread this or is it misleading?

    At this point I now am just using a Boolean flag in the component data that I was setting for my "SetFilterChanged" at the start of my iteration to ignore 99% of the entities. I am still getting great performance, but I can't help but think that I am still doing something wrong.

    I do think there is a large performance increase over the method I was using with add/remove components and ECB, and I also think that there is a very slight boost over IForEach(unless everything is[Excluded]), because the job isn't running at all when the chunk doesn't change.

    Sorry this turned into a novel, and thank you for any input.
     
    wobes likes this.
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    371
    For performance reasons, Unity only tracks changes to a chunk per component type. Any time a single component on the chunk changes, then all entities in that chunk will pass a change filter applied to that particular component type. And by "change", that actually means you request write access to that component type in that chunk, regardless of if you write anything.

    As I have suggested in other threads, you can use GlobalSystemVersion and LastSystemVersion to do the same change filter checking Unity does at chunk level, except you can do this at Entity level by adding an int field to your component that needs change tracking.

    However, if you are trying to tag a small percentage of entities that are evenly distributed throughout the chunks and you want to avoid a sync point, the best solution for this may be to accumulate your entities into an array or other container and then iterate on those.