In many programming languages, there is something called the garbage collector. No, not the truck that comes by your house every week to collect your empty pizza boxes and energy drink cans. When you create objects, generally speaking they are added to what’s known as the heap, which is like a big pile of data used by all your programs. When you’re done using a piece of data, then the garbage collector comes along and frees up that memory for re-use later; without it, the heap would soon fill up with gunk and memory leaks and your computer would probably crash. And that’s bad.
There are certain operations notorious for garbage collection. One of those, in most languages, is string concatenation. Strings, as you’re probably aware, are objects. Like all objects, they occupy space on the heap. When you concatenate two strings together, it’s a common misconception to assume there isn’t much overhead and you’ll just be getting a bigger string back out. However, since strings are usually immutable (once set, their values can’t be changed – this is the case in languages such as Java and C#), you’re actually taking the two original strings and creating a third immutable string which contains the contents of the original two (or three, or four etc). You might see something like this in code (this example is specific to Unity C#):
and think it looks reasonable – it’ll print the line “Player John has 0 health”. However, you’re taking five strings – “Player “, “John”, ” has “, “0” and ” health.”, and then creating an entirely new string to print. What’s worst about this is since “Player “, ” has ” and ” health” were created on-the-fly and are no longer needed, they need to be removed from the heap, which means the garbage collector is called and we waste a load of time cleaning up space we didn’t need to allocate in the first place. A better solution would have been to cache those three strings somewhere (such as in their own variables) so they’re not wastefully created and immediately removed again. That also has the advantage of removing the overhead of creating them each time, especially if PrintStuff() were called frequently.
A Unity-specific warning zone for garbage collector abuse are the Instantiate() and Destroy() functions (although all scenarios in which objects are made and destroyed have the same effect). A common operation in games, for example enemy spawning or playing particles, is to create them when required using Instantiate() and then remove them once they are no longer needed using Destroy(). Each time Instantiate() is called, there is overhead for creating the object, and when Destroy() is called, the garbage collector gets involved. This means that overuse of these functions can lead to a significant performance decrease. It’s like how capitalism works – why re-use this old thing when I can buy a new one?
A much better way of working with multiple objects this way is to utilise object pooling; that is, we create a bunch of objects at the start of the game in a disabled state, enable them when we’d usually have instantiated them, then just disable and reset their parameters ready for re-enabling, when we’d previously have destroyed them. We still have a large overhead at the start because we need to create everything, you need to make the pool large enough for the maximum number of objects you’re likely to need (or the pool has to be extendible) and there’s only a point to this if the speed improvement is significant (on small object sets, this could actually be slower), but in games where things are frequently spawning and disappearing, object pools are more likely than not the way to go. Besides, it’s more amusing to code up a really big pile of stuff than an boring old create/destroy cycle. Yaaaaaawn.
A final bad Unity thingamajig is this innocent-looking thing here:
It looks fine, but there’s one deceptively-demanding call here: GetComponent(). Behind the scenes, this sneaky little fucker is grabbing a whole list of components on your game object just to find your singular component, then sending the list to our good ‘ol friend, the garbage collector. It’s best if you can avoid use where possible, or at least limit use to Start(). One good design tip is to have a sort-of ‘manager’ in your scene that, at the start, you send references of objects to without using GetComponent() – for example, from my PlayerController class, I send my GameManager a reference to the PlayerController for anyone to access. Then whenever I need to reference PlayerController, I can find it through GameManager rather than using GetComponent() all over the shop. It’s also far more elegant, since I know where I can find all the references I’ll need.