Garbage Collector

Working on a fast pace game where performance is key. We have great CPU time - under 2 ms on PC - and generate very little garbage, which means the collector pass once every 1-2 minutes.

However, from the profiler, we see that the GC takes 7 to 9 ms to perform its task when it decides to do it. On mobile, that could mean 24 to 40 ms, which would kill our framerate once in a while!

Now, I have no definitive proof that it occurs on mobile, but the number in Unity profiler are not fun to look at.

  1. In the .NET Framework, the GC is multithreaded by default… is it in Unity?

  2. Is there a way to reduce that GC spike? Calling it every second is not an option, as it’s just having a 6-7 ms spike every second instead of 7-9 ms every minute.

  3. Shouldn’t the GC be fast? So why is it so damn slow in Unity?

  4. Is it slow because it has many objects to parse? We don’t have that many object, but is there a way to make it ignore some we know we will never destroy?

I have read heaps of complaining about the slowness of Unity’s garbage collection. (Well, I’ve read it like 2 or 3 times.) Unity uses a pretty old version of Mono, and that version has notoriously slow GC. FWIW, I also recall hearing that Unity is working on this, though it might not come to fruition until IL2CPP is finished (perhaps sometime in the 5.x cycle). There’s some discussion about it here, along with some memory management tips. (I’m actually reading it right now, as I believe I have some GC issues of my own.)

Speaking of memory management tips, I’m sure you’ve seen this page, which might help a bit in reducing the frequency of GC.

You can manually call system.GC.Collect() at opportune moments. If the player opens a menu, during any level transition, if there’s a moment (even one a fraction of a second long) where you know there will be no action, anything along those lines, you can take advantage of it and call the collector.

Sadly, it’s an endless fast pace runner, there is no downtime to perform manual collect.

I think the only way to fix it, is to have no garbage at all.

what is generating your garbage?

Well, an endless runner requires to spawn road and gameplay. We have pooled everything that can be pooled, but it still leaves us with a few bytes of garbage every few frames. Sorry, I’m at home, so I don’t have an exact list.

Yep Im working on an endless-ish runner… however mine is dead basic. No GC so far. What havnt you been able to pool out of curiosity?

We have dozen of different track models, and each model got a collection of potential “pattern” of gameplay that can spawn on it. We pre-spawn 6 tracks module ahead of the player… So precaching all our stuff would be insane.

I use the PoolManager asset by Path-o-logical Games which is fantastic for pooling things: http://poolmanager.path-o-logical.com/

Since you have patterns perhaps it would be worth having a sort of sub-pooling system whereby you pool the pattern, and then inside OnEnable() of the pattern you proceed to rebuild the pattern from pooled parts.

This way you would be able to reuse parts between all patterns which share them.

Already done… Like I said, everything we could realistically pool is already pooled.

In one of his QuakeCon talks John Carmack posited running the GC every frame in an effort to have a more even, consistent experience. He figured a known overhead every frame was better than an unknown dip in performance at uneven intervals.

Made me wonder if anyone has actually ever tried that. Given that it isn’t a “standard practice” I would imagine someone has with not so great results.

I did, and it’s no good. The GC have to read all the heap pointer to see which variable is dead, and from what I see, that appears to be the slowest part of its job. Calling the GC every frame or every few frames still eat 6-7 ms of CPU, which on mobile means it would be impossible to have a steady 30 FPS.

Could you explain why it is impossible to pool everything?

When you have 50 tracks type, with each having 10-12 possible pattern… Would you really pre-cache everything, even more considering the same track or pattern can happen twice?

I have certainly no idea how the game works. Would it be possible to make a selection at the beginning and then just use those ones?