[Solved] Optimizing for garbage collection leads to poorly readable code?

I experience performance spikes due to garbage collection in my game. So I went through the Unity Tutorial on optimizing garbage collection, and I already was able to take out some offenders. =)

But I also noticed that your code readability can suffer in some situations when you would like to avoid a heap allocation. For example:

SomeReferenceType theThing= someObject.someProperty.aListofObjects[index].youGetTheIdea.theThingINeed;
theThing.someVariable = newValue;
theThing.doThis();
theThing.doThat();

This code would create garbage because I am creating the “theThing” reference Type variable. When this variable goes out of scope, it will be collected. So instead, I’m trying to avoid to create that variable, but that leads to:

someObject.someProperty.aListofObjects[index].youGetTheIdea.theThingINeed.someVariable = newValue;
someObject.someProperty.aListofObjects[index].youGetTheIdea.theThingINeed.doThis();
someObject.someProperty.aListofObjects[index].youGetTheIdea.theThingINeed.doThat();

This is a relatively harmless example, but I think you can imagine how bad this can get with multiple complex objects when you are never allowed to create a reference due to heap allocation.
I mean I can code like this, but this does not seem good practice to me. Is this something I need to live with when I want heap allocation free code, or did I miss something?

Inside a method, let’s say… your code would be creating stack variables. Using a reference (the first line), and re-using it to assign and call methods seems much better, because you do not have to go through the property lookups.

I think the guide is suggesting to not abuse the heap, not to not use it.

Since it’s a made up example, it’s hard to say, but if you posted some real code you were concerned about, you might be able to get some better feedback.

1 Like

Thanks for the reply! I also think using a reference makes the code more readable, and I agree with the “using not abusing” idea: If I write something for the main menu that will be called only once in the game, I should not be worried about it if there is a single reference in it.

However if there is code that will be called on each frame a little garbage can quickly pile up, the guide mentions this as well.

I took the made-up example so I can get the issue explained easier without having to explain what my code is supposed to do, but here is a real example that inspired me to ask on the forums:

if (speakerOrigin.GetType() == typeof(EnemyPatrolView))
{
   int storyNPCIndex = -999;
   for(int i =0;i< ((EnemyPatrolView)speakerOrigin).enemyPatrol.enemyRequirements.Count; i++)
   {
      if (((EnemyPatrolView)speakerOrigin).enemyPatrol.enemyRequirements[i].isStoryNPC)
      {
         storyNPCIndex = i;
         break;
      }
   }
   if (((EnemyPatrolView)speakerOrigin).enemyPatrol.enemyRequirements[storyNPCIndex].currentEnemyId != null && ((EnemyPatrolView)speakerOrigin).enemyPatrol.enemyRequirements[storyNPCIndex].currentEnemyId != "")
   {
      if (app.view.GetEnemyCharacterViewById(((EnemyPatrolView)speakerOrigin).enemyPatrol.enemyRequirements[storyNPCIndex].currentEnemyId) != null)
      return app.view.GetEnemyCharacterViewById(((EnemyPatrolView)speakerOrigin).enemyPatrol.enemyRequirements[storyNPCIndex].currentEnemyId).transform;
   }
}

This snippet is supposed to find the transform of a speaking NPC in a patrol. This is part of a component that is supposed to let the player overhear / eavesdrop on dialogue between guards in enemy patrols.

As you can see I wanted to avoid references, which makes the code horrible imo because I am repeating the type conversions & property lookups over and over again.

You can safely create a reference on the stack there.

You are only assigning a reference to the already created object in memory. You are not allocating memory for a new object. I hope that makes sense. I was going to write some small example, but if this satisfies as an answer, I’ll skip it. :slight_smile:

2 Likes

Wait a minute…so basically I’m only creating garbage when I create an actual new object, with the “new” keyword for example? Then I really had a conceptual misunderstanding, I thought even those references to existing objects would be considered as garbage & would be cleaned up during garbage collection. If this is not the case, my problem is solved. :slight_smile:

This example seems to confuse the allocatiion of memory and creation of new objects with referencing existing objects, as mentioned by @methos5k , i.e. creating stack variables for reference types.

Though, to be exact, you clearly have to make a distinction between two implementation details of getter/setter mechanisms:

  • returning a reference to an existing object

  • returning a copy of an existing object, i.e. actually allocating new memory for new objects

Most of the time, you deal with the first case - which does not create objects and therefore no additional garbage. As mentioned by @methos5k , all that happens in this case is creating a temporary stack-variable which is nothing to worry about (unless you create too many and get an overflow… but this requires a hell lot of variables to fill the stack and/or deep call stacks (for instance deep recursive calls) and the like).

When this stack-variable (which references an existing object on the heap) leaves its scope, it’ll be popped off the stack as if it was a value-type - in the end, references to an object on the heap are simply values as well. Reference type variables never hold the object directly, otherwise they’d be value types.

Technically, your “optimized” code in this example should be a few ticks slower.

1 Like

Sorry, yes I’m glad you pointed out #2. I was thinking of this & even discussed in not long ago in a similar thread referencing that article, too.

Unity will return a new array in some cases. For those times, since the array is new and a copy, it is allocated.
Usually the docs will say this, too.

Your own code, unless you asked it to do that, will not by default.

the rest of @Suddoha post covers anything I could have possibly added.

All combined, hopefully it makes sense. :slight_smile:

1 Like

This distinction was exactly the thing I was not 100% aware of, thanks for the detailed explanation!

That was another thing that I was wondering about as well. I do not know much about how this is resolved behind the scenes, but doing the same conversions / lookups over and over again did not seem very efficient to me. :slight_smile:

Yes, thank you both for your replies, I’m back on track again and can now go fix my messy code. :smile: