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

Garbage Collection caused by hybrid effect

Discussion in 'Entity Component System' started by tommox86, Mar 4, 2021.

  1. tommox86

    tommox86

    Joined:
    Apr 30, 2015
    Posts:
    81
    When converting an effect such as a particle system or line renderer using for example:
    Code (CSharp):
    1.  conversionSystem.AddHybridComponent(GetComponent<LineRenderer>());
    The CompanionGameObjectUpdateTransformSystem creates a small amount of garbage for each effect instantiated. This adds to performance spikes when there are multiple effects generated. Is there any method i could use to cancel this garbage? This garbage is generated when the effects are stationary and not updating.
    unity 2020.1.17f and all latest entity preview packages are used
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,579
    Did you check in build, if the gc is also collected? Did you try disable safety checks editor?
     
  3. tommox86

    tommox86

    Joined:
    Apr 30, 2015
    Posts:
    81
    yes safety check is disabled and garbage is apparent in build. it seems to generate 24b constantly under that system just for a particle system.
     
  4. tommox86

    tommox86

    Joined:
    Apr 30, 2015
    Posts:
    81
    deep profiler shows the garbage is generated here in the screenshot.
    issue can be replicated by creating empty gameobject, adding particle system then add that particle system with the AddHybridComponent and convert and destroy. Is there a setting im missing?
     

    Attached Files:

  5. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    First off - MonoBehaviour GetComponent<T> generates string garbage in editor.
    So if you want to avoid that - use TryGetComponent(out *component*);

    Second - Convert and Destroy obviously destroys your gameobject which generates garbage.
    As for how to avoid it? Don't destroy stuff. (Unless subscene conversion does some magic behind the scenes)

    Another options would be to enable Incremental GC in player settings. That will reduce GC collection spikes, but its a bit more CPU hungry overall.
     
  6. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,579
    I would expect, that you do that only at initialization. If you doing that multiple times at runtime, it looks like, you may be doing something wrong. If anything, use pooling mechanics.
     
  7. tommox86

    tommox86

    Joined:
    Apr 30, 2015
    Posts:
    81
    thanks ill give that a go. understand that destroying the gameobject creates garbage. but shouldnt that be a one time cost, not generated every frame
     
  8. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    It is one time cost per object. So it all comes down to numbers.
    Also note that I haven't looked into conversion pipeline mostly. If you really want to know what allocates - use Profiler + Deep Profile.

    It could be how that system is implemented. Sources are also available for Entities package, so might want look into those as well.
     
  9. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    Okay, I've realized something. Its just poor coding + TransformAccessArray issue.
    This is what CompanionGameObjectUpdateTransformSystem.OnUpdate does:
    Code (CSharp):
    1. protected override unsafe JobHandle OnUpdate(JobHandle inputDeps)
    2.     {
    3.         EntityManager.AddComponent<CompanionGameObjectUpdateTransformSystemState>(m_NewQuery);
    4.         EntityManager.RemoveComponent<CompanionGameObjectUpdateTransformSystemState>(m_DestroyedQuery);
    5.  
    6.         var entities = m_ExistingQuery.ToEntityArray(Allocator.Persistent);
    7.  
    8.         var transforms = new UnityEngine.Transform[entities.Length];
    9.         for (int i = 0; i < entities.Length; i++)
    10.         {
    11.             var link = EntityManager.GetComponentData<CompanionLink>(entities[i]);
    12.             transforms[i] = link.Companion.transform;
    13.         }
    14.  
    15.         m_TransformAccessArray.SetTransforms(transforms);
    16.  
    17.         return new CopyTransformJob
    18.         {
    19.             localToWorld = GetComponentDataFromEntity<LocalToWorld>(),
    20.             entities = entities
    21.         }.Schedule(m_TransformAccessArray, inputDeps);
    22.     }
    As you can see, it allocates an array of transform for the TransformAccessArray. Which is the source of GC per frame.

    This is a common problem, which I've encountered myself and wrote custom transform position sync system (similar approach can be used for anything accessable via TransformAccess, rotation, scale, matrices etc).

    Here's more on it:
    https://forum.unity.com/threads/imp...form-system-performance.1048907/#post-6797624

    TL;DR: Don't use companion / link system, sync required transform data manually via IJobParallelForTransform, but allocate only one array, and re-use it. This will both improve performance and remove GC.

    Although this approach doesn't scale well for thousands of transforms (most of the overhead comes from TransformAccess itself, and hierarchy access time). It work decently fast with small number of transform (<512), and there's no other solution right now AFAIK.

    Alternative for TransformAccess would be to either go full pure ECS, or wait until TransformAccess gets faster and GC pressure gets removed from querying Transforms / TransformAccessGroups.
     
    tommox86 likes this.
  10. tommox86

    tommox86

    Joined:
    Apr 30, 2015
    Posts:
    81
    brilliant. thanks for this response.
     
    tmonestudio likes this.