Using the Unity Profiler

Getting the most out of every single frame of your game

Micha Davis
Nerd For Tech

--

Today we’re going to look at the Unity Profiler analysis tool to examine the performance of our code and look for ways to optimize scripts. So, let’s go WAAAAY back to my very first game and see if there is any drag in my code from when I was first beginning. The examples I will show aren’t very performance intensive because of the scale of the game, but we will see some impact from two areas of code that could bog down the frame rate if we were to scale up into a larger, more involved game.

The profiler window can be found in the top menu under Window > Analysis. It’s a good idea to situate your workspace so you can see the play area and the profiler simultaneously. The profiler won’t show anything until you press Play, so get that way. Once everything is set up your screen will look something like this:

The majestic purple hue is optional.

There are several settings to inspect. CPU Usage is probably where you’ll spend most of your time optimizing. Here are the other graph options:

I’ve gone ahead and deselected all the data I’m not worried about. I just want to focus on script performance for now. As you can see, we’re setting at a pretty comfortable 4000 frames per second.

But, this is also with the game looping at the level start. Nothing else is running. If we play the game for a bit, we can track the changes and zero in on spikes. If we click on the graph, it will give us data about everything happening in a single frame. We want to get the most information we can, so click on “Deep Profile” and reload the scripts when prompted.

Now, let’s watch the graph for a bit and see what happens.

Down in the overview we see that the Editor is using half of the resources consumed right now. We can get a cleaner view of the true operation of the game after it’s built, but for now we’ll disregard the editor.

If I play for a bit, keeping out for sudden spikes, I see a huge decrease in performance when the player dies.

This plateau is the time the player spends dead before the scene reloads.

You can see down in the profiler Hierarchy that there is a sudden jump in GC allocation — that’s Garbage Collection, which happens when memory loose ends are handled — and a concurrent drop in FPS (we’re down to only 250! lol). Drilling down the Hierarchy reveals that the source of the issue is the StackTraceUtility. An error is being sent up every frame, and that is (relatively) tanking our FPS.

In the console notification area we see the error: we’re looking for the Player, but the player is dead. The code referenced is this line from the Enemy script:

One of my enemy types searches for the player every frame so it can home in and ram the player. If these enemies are present when the player dies, then they are left searching for something that does not exist. I can put a null check on this line, to make sure that if the player is dead, these enemies stop looking for them.

Poof. Optimization achieved.

We can also keep an eye out for spikes in the GC by viewing the Memory graph:

If we look at that spike on the CPU graph, we can see the source of the problem:

Looks like the enemy’s FireRoutine is dragging us down a bit. Let’s investigate further.

Yeesh. This could use more than a little refactoring.

The likely culprit is the repeated use of the new keyword. Every time we use it, we’re creating a new instance of the WaitForSeconds class. That has to be cleaned up by GC when we’re done using it. So, every frame we are devoting memory to creating and destroying four new instances of a class in this method alone. What’s worse: three of them are for identical measures of time. And since the coroutine is running concurrently with other sections of code, these kinds of things can pile up quick.

What we can do here is cache the WaitForSeconds calls by creating them and assigning them values on Start. Then we only need to reference those existing instances rather than create and destroy, create and destroy, create and destroy over and over every frame.

So, let’s see what it looks like to rework this:

I am still calling a new WaitForSeconds() each frame because of the random wait time. There’s no way for me to avoid creating a new wait each time the random roll happens. But the other three calls have been condensed down to one variable.

That should get rid of our spike in garbage collection, and leave it as a blip.

Hopefully after these two examples you can begin to get an idea of the power at your disposal using the Unity Profiler. As I said above, the bigger and more involved your project, the more these small things will begin to add up. Arm yourself against low frame rates by adding the profiler to your process.

In the next article we’ll dive into a new project. See you then.

--

--

Micha Davis
Nerd For Tech

Unity Developer / Game Developer / Artist / Problem Solver