So I’ve been trying to deal with high ms GC spikes. I carefully pool most of the objects in my game and barely allocate any garbage. Most frames show 0 bytes GC on the profiler. In spite of this, I still get a relatively high (~20ms) GC.Collect spike every couple of minutes. I know it’s not a really big issue and I’m kinda wasting time investigating it but I still want to get rid of it if possible and I found it interesting to look into it further.
So first I tried manually calling GarbageCollector.CollectIncremental periodically, say every 30 seconds. I used 1,000,000 as the parameter, i.e 1ms. What I noticed though is, every time I would call it, I’d see a small 1ms spike, followed by a few more 1ms spikes in the following frames, but still ending with a high final spike. Albeit slightly smaller than it was but still pretty high (~15ms). As shown in this screenshot:
This was at an uncapped frame rate (Application.targetFrameRate = -1). What I found very weird though is that when I changed the target frame rate to an arbitarily large number (1000), suddeny the large GC.Collect spikes were gone. I was only seeing the small 1ms spikes from my manual calls as intended and that’s it. As you can see in this second screenshot:
Now I understand that the Unity manual itself says that when incremental garbage collection is enabled, Unity tries to use the waiting time between frames to perform the garbage collection so as not to cause spikes as much as possible. However, I set the target frame rate to 1000 while I was only getting barely above 100 FPS at any time, so the frame rate was not getting capped at all and there was no waiting between frames. I find this all very weird and I don’t know if I’m missing something, if it’s a bug, or what. Anyone ever experienced something similar?
1 Like
With the targetFrameRate = -1
it looks like incremental GC is not running automatically every frame as the intent is to run as fast as possible without any obstructions to the framerate. So what likely happens is that the amount of accumulated garbage eventually causes full GC run with a big spike.
If you want to keep framerate unrestricted, I would recommend calling GarbageCollector.CollectIncremental
every frame with e.g. 0.5ms - doing it every 1-3k frames is not enough I believe.
That makes more sense, thank you. The thing is having it set to -1 or to 1000 makes virtually no difference on the FPS that I’m getting, so this would mean a value of 1000+ will be better than -1 in most cases, which is not very intuitive.
Also after more experimenting, I noticed that if targetFrameRate is not -1, then GarbageCollector.CollectIncremental or GC.Collect calls just stop showing altogether in the profiler, unless I call them manually myself. Not just the big spikes. If I search GarbageCollector.CollectIncremental in the search field nothing shows up. While if I search that with targetFrameRate set to -1, I can see a call in every single frame. I’m not sure why that is? Could it be that the automatic spikes are still happening just the same, but the profiler is just not showing them for some reason?
Also, I tried calling Garbage.CollectIncremental every ~5 frames with a 1ms parameter to see what would happen, and that just caused the big GC spikes to occur much more frequently. I was getting these groups of 10+ consecutive big spikes every couple of seconds or so. The spikes were slightly smaller than in the original post but still. So basically calling Garbage.CollectIncremental more frequently is worse for some reason.
1 Like
this is really strange - if frame time is > 0 and incremental GC is enabled, we always call GarbageCollector.CollectIncremental
with a minimum timeslice of 1ms, so I would expect marker being there
every 5 frames may be not enough as well - it depends on the GC and the allocations pressure
perhaps with allocations rate in the game you have to call it it every frame.
iirc if you want to avoid large spikes you need to run GC more often - in your experiments GC every 30s caused 15ms spike, and every 5 frames 10ms spikes. Basically total costs over frames may be higher, but you get lower maximum GC times
I got similar results when calling it every frame too. But yeah the fact that it only shows up in the profiler when targetFrameRate is -1 is definitely strange. Do you think this could be a Unity bug? Or maybe if targetFrameRate is not -1, GC is handled through a different method that does not show up in the profiler? Or maybe it gets handled in the Editor loop instead?
Yep I switched the profiler to Edit Mode and I’m seeing the GC calls with same frequency of spikes there instead when targetFrameRate is not -1. I thought that GC would be handled separately for the game and for the editor in the profiler but I guess it isn’t? That would explain why I’m getting so many big spikes even though I’m barely allocating any GC in my game, since the profiler showed kilobytes of garbage being allocated every frame in the Editor loop. That would be good news if so. It was probably dumb of me to test this in editor and should have tested it in an actual build lol.