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

Question GetComponent versus GetComponentDataFromEntity on Main Thread

Discussion in 'Entity Component System' started by Rupture13, Aug 5, 2022.

  1. Rupture13

    Rupture13

    Joined:
    Apr 12, 2016
    Posts:
    129
    Aloha,

    I've noticed something that I cannot explain:
    When I, on the main thread, want to read some component data:
    • If I get that component data via a SystemBase.GetComponent, it causes a JobHandle.Complete to be executed
    • If I get that component data via getting a ComponentDataFromEntity and immediately retrieving the component data from that (still all on the main thread), there is no JobHandle.Complete
    In practice, this causes ComponentDataFromEntity to be faster on the main thread, since it doesn't come with the overhead of the JobHandle.Complete.

    This seems to be against what I understood and what is stated in the docs:
    https://docs.unity3d.com/Packages/com.unity.entities@0.50/api/Unity.Entities.ComponentDataFromEntity-1.html
    (Note: my entity count is usually low (or even singleton) in these situations, so maybe that would reduce the overhead of creating a ComponentDataFromEntity object? I imagine the cost of that scales with how many instances of the component exist)

    From how I understand JobHandle.Complete, it exists to make sure the data I use in my system is accurate (and isn't still being manipulated by an ongoing job). If that is true, questions arise:
    • Why does the GetComponent force job completion even when the job that it forces to complete does not at all touch the same components as the system that forces the completion?
      (For example: why does system '2' (which uses components A and B) force completion of job '1' (which uses components C and D) when there seemingly shouldn't be a dependency between the two?)

    • Does GetComponent force job completion when it seemingly shouldn't have to?

    • Why doesn't the ComponentDataFromEntity force job completion when GetComponent does?

    • Is it safe to use the ComponentDataFromEntity on the main thread (i.e. is it guaranteed that the data will be accurate)?

    Since all the potentially unnecessary job completions could be a significant point of optimisation in my game, clearing this up would greatly help me :)
     
    Last edited: Aug 5, 2022
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    CDFE is just a "reference" to the data structure that's inside. Its a struct with a pointer. There isn't much of an overhead.

    Personally, we use CDFE instead of GetComponent to ensure its easier to transition system to jobified version, in case if needed. In terms of whether data or not is correct - it didn't caused any troubles for us in hybrid environment including reading / writing from / to MonoBehaviours.

    So, I guess even if data change is received one frame later (in theory) - its no big deal.
    [and I'd rather prefer no-sync in this case]


    As for the implementation itself - its either an oversight, or a feature.
    Because if you call GetComponent in ForEach on main thread, it generates EntityManager.GetComponent;

    If you call GetComponent in a jobified ForEach, it generates as CDFE.
     
    Last edited: Aug 5, 2022
    Elapotp, Anthiese and Antypodish like this.
  3. Elapotp

    Elapotp

    Joined:
    May 14, 2014
    Posts:
    98
    So, is it safe to assume to always prefer GetComponent<T> to obtain read-only data inside SystemBase?
    Update: Regardless of performance, of course.

    Code (CSharp):
    1. /// <summary>
    2. /// Look up the value of a component for an entity.
    3. /// </summary>
    4. /// <param name="entity">The entity.</param>
    5. /// <typeparam name="T">The type of component to retrieve.</typeparam>
    6. /// <returns>A struct of type T containing the component value.</returns>
    7. /// <remarks>
    8. /// Use this method to look up data in another entity using its <see cref="Entity"/> object. For example, if you
    9. /// have a component that contains an Entity field, you can look up the component data for the referenced
    10. /// entity using this method.
    11. ///
    12. /// When iterating over a set of entities via [Entities.ForEach], do not use this method to access data of the
    13. /// current entity in the set. This function is much slower than accessing the data directly (by passing the
    14. /// component containing the data to your lambda iteration function as a parameter).
    15. ///
    16. /// When you call this method on the main thread, it invokes <see cref="EntityManager.GetComponentData{T}"/>.
    17. /// (An [Entities.ForEach] function invoked with `Run()` executes on the main thread.) When you call this method
    18. /// inside a job scheduled using [Entities.ForEach], this method gets replaced with component access methods
    19. /// through <see cref="ComponentDataFromEntity{T}"/>.
    20. ///
    21. /// In both cases, this lookup method results in a slower, indirect memory access. When possible, organize your
    22. /// data to minimize the need for indirect lookups.
    23. ///
    24. /// [Entities.ForEach]: xref:Unity.Entities.SystemBase.Entities
    25. /// </remarks>
    26. /// <exception cref="ArgumentException">Thrown if the component type has no fields.</exception>
    27. protected internal T GetComponent<T>(Entity entity) where T : struct, IComponentData
     
    Last edited: Aug 5, 2022
  4. Rupture13

    Rupture13

    Joined:
    Apr 12, 2016
    Posts:
    129
    The documentation speaks of some overhead, which may still be true, but in practice is not as big an overhead as the job completion. And I assumed that if the CDFE overhead indeed exists, it will probably scale somewhat with the number of instances of the relevant component and how much they are scattered across chunks.

    If the lack of job completion when using CDFEs on main thread does not bring the danger of inaccurate data, then I don't know why anyone would ever want to use
    EntityManager.GetComponentData<T>
    (or code that converts to that)…

    If I am indeed to avoid
    EntityManager.GetComponentData<T>
    (or code that converts to that), that would also mean that using
    SystemBase.GetComponent<T>
    on the main thread (which converts to the EntityManager version) is suboptimal and would best be avoided.

    I think you mean inside Entities.ForEach instead of SystemBase.
    In a SystemBase's OnUpdate, for example, the code that is outside of scheduled/jobified Entities.ForEaches executes on the main thread.
    So the SystemBase.GetComponent<T> on main thread code converts to an EntityManager.GetComponentData<T>, and the point of this thread is that from my testing, that seems to be worse for performance than always using CDFEs, even on the main thread.
     
    Elapotp and StickyTommie like this.
  5. Elapotp

    Elapotp

    Joined:
    May 14, 2014
    Posts:
    98
    @Rupture13 , if it is as you wrote, it feels like I need to write an extension method GetComponentFast<T>() and SetComponentFast<T>(). Under the hood, they will use CDFE.

    Sounds hilarious to have regular GetComponent<T> as it is now. Perhaps Entities 1.0 will have "final" version.

    Are we talking about working with a single entity or a bunch of them?
     
  6. Rupture13

    Rupture13

    Joined:
    Apr 12, 2016
    Posts:
    129
    Yeah, I feel like I'm missing something, but it indeed does seem like there's no valid usecase for
    GetComponent<T>
    on the main thread.

    I'm indeed talking single entity or just a few entities. I sometimes need to collect some data before the Entities.ForEach, which usually exists on either a singleton or a very small number of entities. If I needed to do it for a lot of entities on the main thread, that would probably be a sign that I'm doing something wrong ;)
     
    Elapotp likes this.
  7. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    By the way, just figured this out in a practical manner;

    If there's a job that writes to the IComponentData, while you're attempting to fetch via CDFE on a main thread - you'll receive safety check exception "job X writes to the IComponentData" (even with read only set).

    This will happen only if that job is still runing. Otherwise it will run just fine.
     
    StickyTommie and Rupture13 like this.
  8. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,653
    GetComponent
    on main thread (as already mentioned above) converts to
    EntityManager.GetComponentData<T>
    which ensures safety and completes write fence (if no one writes to component there would be nothing to complete).
    GetComponentDataFromEntity
    by itself is different, it just gets you access to data lookup, but not lookup itself. The CDFE indexer if what you compare with
    EntityManager.GetComponentData<T>
    , and in that matter they're the same as use same
    EntityComponentStore->GetComponentDataWithTypeRO (RW in case of setter in indexer of CDFE or EntityManager.SetComponentData<T>)
    to access data from entity, and there is the slight difference comes, as already said EM will ensure safety and complete write fence and CDFE[e] wouldn't (and shouldn't as it's usually intended to be used inside job where you shouldn't complete any dependency) and in the later case we have safety system, which will throw you error about trying to access data when someone "possibly" (according to dependency chain) writes to the same data.
     
    Rupture13 likes this.
  9. Rupture13

    Rupture13

    Joined:
    Apr 12, 2016
    Posts:
    129
    This is very good to know. It assures me that if no errors are thrown, my data is accurate.

    This is where my issue comes in: I have a completion happening despite the job that's being forced to complete having nothing in common with the system that wants to run. In other words, I don't see why there would be a dependency.

    I wouldn't want to use CDFE[e] on the main thread, but in practice I can replace EM.GetComponent calls that result in a completion with a CDFE[e] that doesn't throw an error. This, to me, indicates that the EM.GetComponent is doing an unnecessary completion (because if it were necessary, the replacing CDFE[e] would throw an error).

    This seems to me to be some sort of bug where EM.GetComponent (and some other functions as well, btw) force job completions when they shouldn't have to, which drains performance from the main thread to a significant enough degree that it matters (like main thread systems taking 0.2ms instead of 0.01ms).