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 Garbage Collection FPS spikes

Discussion in 'Entity Component System' started by Ndogg27, Sep 2, 2023.

  1. Ndogg27

    Ndogg27

    Joined:
    Feb 3, 2023
    Posts:
    8
    I have been learning ECS and have a basic "game" that will spawn enemies that move from the right side of the area to the left. It can run smoothly with thousands of enemies spawned over time, but I get these insane FPS spikes. I have tried so many things to fix it and nothing changes. Is there a way for me to actually see what is causing the spike? So far I just assume it is from 100s or 1000s of entities being spawned.

    This is the basic spawn ISystem I have rewritten multiple different ways...
    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.Physics;
    3. using Unity.Mathematics;
    4. using Unity.Transforms;
    5. using Unity.Burst;
    6. using Unity.Collections.LowLevel.Unsafe;
    7. using System.Diagnostics;
    8. using Unity.Collections;
    9.  
    10. [BurstCompile]
    11. public partial struct EnemySpawnerISystem : ISystem
    12. {
    13.  
    14.     private Entity prefabEnemy;
    15.     private float timer;
    16.  
    17.     [BurstCompile]
    18.     public void OnCreate(ref SystemState state)
    19.     {
    20.         //Wait for game settings to process before moving to OnUpdate
    21.         state.RequireForUpdate<GameSettingsComponent>();
    22.     } //End
    23.  
    24.     [BurstCompile]
    25.     public void OnDestroy(ref SystemState state) { }
    26.  
    27.     [BurstCompile]
    28.     public void OnUpdate(ref SystemState state)
    29.     {
    30.         if (prefabEnemy == Entity.Null)
    31.         {
    32.  
    33.             prefabEnemy = SystemAPI.GetSingleton<EnemySpawnerComponent>().prefabEnemy;
    34.  
    35.             return;
    36.         }
    37.  
    38.         var ecb = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
    39.         var commandBuffer = ecb.CreateCommandBuffer(state.WorldUnmanaged);
    40.  
    41.         var settings = SystemAPI.GetSingleton<GameSettingsComponent>();
    42.  
    43.         var deltaTime = SystemAPI.Time.DeltaTime;
    44.  
    45.         var random = new Random((uint)Stopwatch.GetTimestamp());
    46.  
    47.         var count = settings.enemySpawnMax;
    48.  
    49.         timer -= deltaTime;
    50.         if (timer > 0)
    51.         {
    52.             return;
    53.         }
    54.         timer = settings.enemySpawnRate;
    55.  
    56.         // Remove the NewSpawn tag component from the entities spawned in the prior frame.
    57.         var newSpawnQuery = SystemAPI.QueryBuilder().WithAll<NewSpawnTag>().Build();
    58.         state.EntityManager.RemoveComponent<NewSpawnTag>(newSpawnQuery);
    59.  
    60.         state.EntityManager.Instantiate(prefabEnemy, count, Allocator.Temp);
    61.  
    62.         new RandomPositionJob
    63.         {
    64.             random = random,
    65.             settings = settings
    66.         }.ScheduleParallel();
    67.  
    68.  
    69.     } //End
    70.  
    71. } //End ISystem
    72.  
    73.  
    74. [WithAll(typeof(NewSpawnTag))]
    75. [BurstCompile]
    76. public partial struct RandomPositionJob : IJobEntity
    77. {
    78.  
    79.     [NativeDisableUnsafePtrRestriction] public GameSettingsComponent settings;
    80.     public Random random;
    81.  
    82.     public void Execute([EntityIndexInQuery] int index, ref LocalTransform transform, ref PhysicsVelocity physicsVelocity)
    83.     {
    84.         var xPosition = random.NextFloat(-settings.fieldAreaX / 2, settings.fieldAreaX / 2);
    85.         transform.Position = new float3(xPosition, 0, -settings.fieldAreaZ / 2);
    86.         transform.Scale = 0.25f;
    87.  
    88.         physicsVelocity.Linear = new float3(0f, 0f, 2f);
    89.  
    90.     }
    91. }
    92.  
    upload_2023-9-2_15-40-25.png
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    The hierarchy view of the profiler shows GC allocations.
     
  3. Ndogg27

    Ndogg27

    Joined:
    Feb 3, 2023
    Posts:
    8
    It doesn't tell me anything new though.
    upload_2023-9-2_19-15-18.png
     
    Last edited: Sep 3, 2023
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    Are you pro0filing in the editor or in a build?
     
  5. Ndogg27

    Ndogg27

    Joined:
    Feb 3, 2023
    Posts:
    8
    I am profiling through the editor.
     
  6. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    Okay. that won't give you an accurate way to profile GC spikes, so don't do that. Profile a build.
     
    Ndogg27 likes this.
  7. Ndogg27

    Ndogg27

    Joined:
    Feb 3, 2023
    Posts:
    8
    Thank you, I will give that a try.
     
  8. Arnold_2013

    Arnold_2013

    Joined:
    Nov 24, 2013
    Posts:
    262
    You could try setting the profiler on 'deep profile' this will allow you to drill down in more method calls to see, with any luck, the line of code that is allocating the memory. And with this info you could dispose it yourself, of make it a persistent allocation.
    Make sure you filter the list by GC alloc in the profiler and expand out everything until you find the lowest thing that allocs the memory. Now its filtered by time, showing you the GC collection, but you need the GC allocation because this is what you need to avoid. Because no garbage equals no collection.
     
    Ndogg27 likes this.
  9. Ndogg27

    Ndogg27

    Joined:
    Feb 3, 2023
    Posts:
    8
    Using the deep profiler runs my game so slow it was pretty difficult to replicate this issue since the fps was trash the whole time anyways. But, I did find the spike and still don't see anything causing it. Most of the issue still falls under that TimeUpdate.Wait.... which doesn't help me. When I get a minute, I will try the profiler through a build.

    On a positive note, I did notice through the deep profiler one of my systems was "considerably" worse than the others and fixed that. So that's cool.
     
  10. Arnold_2013

    Arnold_2013

    Joined:
    Nov 24, 2013
    Posts:
    262
    The GC will take its time here, but that is not the problem. The problem is that you are creating garbage. Sort it by GC.Alloc and make sure you create 0 bytes per frame (in the best case). The allocation can be in frames before the GC taking time to clean it. Your screenshot shows only 1kb of alloc this frame, that should not cause a big garbage collection.
    You could also try to disable the incremental GC to simplify the situation.
     
  11. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,160
    What these two said but without using Deep Profiling to avoid it's performance impact and by instead using Allocation Callstacks for GC.Alloc samples to get the details you need might be what could help you here.
     
    Arnold_2013 likes this.