How to Debug and Fix Performance Problems in Unity With the Profiler Tool
Learn how to debug and fix performance issues with the Unity profiler tool.
Join the DZone community and get the full member experience.
Join For FreeTo summarize what I’ve been up to this past month or two, I’ve continued to help out in the TInkerVR project like I’ve talked about in my Post 100 Day Challenge.
You might be thinking: “Josh! What cool VR programming wisdom will you share with us today?” (Hint: it’s in the title). That’s right! Today, it’s all about perf, perf, and even more perf!
Perf is for short for performance in case you’re wondering.
As it turns out, for projects like ours where we have a lot of technologies that are running at the same time (think Leap Motion, ObiSolver, and VRTK among some of the software that we have been using), you run into a whole slew of problems that destroy the performance of your game:
- We have too many cameras running from VRTK and Leap Motion, requiring a lot of processing from our computer to render graphics for all of our cameras
- Leap Motion and ObiSolver’s physics engine are taking a lot of processing time
- Some combination of all this combined with our other software
How did we find this out? We learned all about it through Unity’s profiler tool.
When I first started working with Unity, I never thought there would ever be a day where I would run into performance problems, but here we are talking about performance!
What is the Unity profiler tool? Well, according to the documentation:
“The Unity Profiler Window helps you to optimize your game. It reports for you how much time is spent in the various areas of your game. For example, it can report the percentage of time spent rendering, animating or in your game logic.”
Today, to demonstrate how to use the profiler tool, I’m going to create a new project with hypothetical inefficiencies that will hinder our performance and then use the profiler tool to solve these problems!
Creating an Inefficient Example to Profile
To demonstrate how we might be able to use the profiler tool, we’re going to create an example project to demonstrate how to use the tool.
Unfortunately, I don’t have any concrete examples of a bad performing game for me to release. I imagine that it’ll take a LOT of complex game objects all moving in a scene before we can even get close to seeing problems.
Instead, I decided to write some bad code that I know will affect performance. Here’s what I did so you can follow along.
- I created a new project that I just called
ProfilerTest
- In the new project, I added a
Cube
into the scene (or really any other game object) - I created a new script called
CubeController
that I attached to the cube.
Here’s what the script looks like:
using UnityEngine;
public class CubeController : MonoBehaviour
{
void Update () {
RotateGameObject();
print("a messsage!");
}
public void RotateGameObject()
{
this.gameObject.transform.Rotate(0, 100 * Time.deltaTime, 0);
// use a for loop as a bad wait function
for (int i = 0; i < 1000000; i++) {}
this.gameObject.transform.Rotate(0, 100 * Time.deltaTime, 0);
}
}
Looking at this script, we’re doing two bad things:
- Printing a message every time in
Update()
, which happens every frame in our game. - Using a for loop to wait for our variable to become 1,000,000 before we rotate our game object again (and again, once every
Update()
). I was inspired by thisfor
loop idea from a project where — I’m not joking — used this as a sleep function to wait for some time… Yikes!
Let’s say someone we are working with didn’t know any better and wrote this code. Our game is now showing signs of slowdown and we don’t know what’s causing it; what do we do?
Luckily for us (and you!), we have the Unity profiler tool!
So, we opened it up to see if we can diagnose where our performance problems were coming from.
- Open the profiler tool by going to Window > Profiler or hitting Ctrl + 7
It should look something like this:
If you explore the tool, you’ll see that we have a lot of categories we can analyze:
- CPU Usage
- Rendering
- Memory
- Audio
- Video
- Physics
- …etc.
There are a lot of categories that we can read, but what matters the most is the hierarchy window at the bottom of the Profiler.
Now that we have the profiler tab up, let’s use it!
- Run the profiler tool by playing the game
- In the Hierarchy window, click the Total tab to organize everything by their Total computation percentage. Note that a high percentage doesn’t mean that we’ll have a high CPU usage. It just means that the category is the most CPU intensive operation out of all your current operation.
Here’s what our profiler looks like:
Immediately after we organized our processes by highest to lowest Total, we can see that Update.ScriptRunBehaviourUpdate
is consuming most of our system resources. Uh oh, this looks like something we did!
Note: the second largest category isEditorOverHead
. EditorOverHead
is essentially the profiler tool. If you are experiencing performance problems and turn on the profiler, you’ll see it become even slower. If your tasks are comparable to it, you don’t need to worry too much about your performance. It's only when EditorOverHead
is a small percent of your total resources that you should start getting worried!
If we drill down this specific category, we can see that most likely the problem is something from a script’s Update
function. In fact, this is from CubeController.Update()
.
Unfortunately, aside from telling us which script’s update function is causing our performance problem, we don’t receive any more information, but this is a good start.
Looking at Update()
in CubeController
, we see that nothing looks wrong, but when we look at RotateGameObject()
, we see that someone (definitely not me!) wrote this:
for (int i = 0; i < 1000000; i++) {}
I don’t know about you, but I don’t think having a for
loop going from 0 to 1,000,000 is ever a good idea!
I understand that we want to wait before we execute the code; however, instead of using a blocking for
loop, let’s create a coroutine to wait and allow our computer to use its resources for other processing.
Here’s what our script looks like now:
using UnityEngine;
using System.Collections;
public class CubeController : MonoBehaviour
{
void Update () {
RotateGameObject();
print("a messsage!");
}
public void RotateGameObject()
{
this.gameObject.transform.Rotate(0, 100 * Time.deltaTime, 0);
StartCoroutine(WaitRotate());
}
private IEnumerator WaitRotate()
{
yield return new WaitForSeconds(10);
this.gameObject.transform.Rotate(0, 100 * Time.deltaTime, 0);
}
}
Now, if we run our game and play the profiler, you’ll see something like this:
Here’s what our profiler looks like after we replaced our CPU blocking for loop with a non-blocking WaitForSecond
coroutine.
Wow! It’s significantly faster now running at 3.1 percent of our total resources. This is more than a 90 percent reduction from where it was before at 43.8 percent!
Instead, we now see that we have our WaitRotate
coroutine in our profiler, but that’s okay; it’s barely using any resources at all!
However, before we call it a day, it looks like there are still some resources usages of Update.ScriptRunBehaviourUpdate
that we can try to optimize.
If we keep expanding the hierarchy, you’ll see that 2.8 percent of our resources are being used by LogStringToConsole
.
It sounds like we’re using a print somewhere inside CubeController.Update()
.
If we go back in look at our Update
function, what do we see?
using UnityEngine;
using System.Collections;
public class CubeController : MonoBehaviour
{
void Update () {
RotateGameObject();
print("a messsage!");
}
public void RotateGameObject()
{
this.gameObject.transform.Rotate(0, 100 * Time.deltaTime, 0);
StartCoroutine(WaitRotate());
}
private IEnumerator WaitRotate()
{
yield return new WaitForSeconds(10);
this.gameObject.transform.Rotate(0, 100 * Time.deltaTime, 0);
}
}
Hmm, that print statement right there seems suspicious right now. Someone probably left it there when they were debugging something (and once again, it DEFINITELY, wasn’t me!)
While it’s not a bad idea to use print statements to inform yourself and other developers of any warnings or errors, you should never call it every single time in Update()
.
As mentioned before, Update()
runs every frame of our game. For creating variables, that might be fine, but for printing, we write our message to the console every single time. This action by itself isn’t CPU intensive, but it’s a different story if we do it every frame!
- Get rid of the print statement.
This is what you’ll have left:
using UnityEngine;
using System.Collections;
public class CubeController : MonoBehaviour
{
void Update () {
RotateGameObject();
}
public void RotateGameObject()
{
this.gameObject.transform.Rotate(0, 100 * Time.deltaTime, 0);
StartCoroutine(WaitRotate());
}
private IEnumerator WaitRotate()
{
yield return new WaitForSeconds(10);
this.gameObject.transform.Rotate(0, 100 * Time.deltaTime, 0);
}
}
Now, if we run the profiler tool again, here’s what we get:
Wow, we went from 3.1 percent to 0.1 percent CPU usage. Now, that’s some performance optimization!
Conclusion
By the end of this article, I hope that you can now see that solving performance problems doesn’t have to be a scary or mysterious process with Unity’s profiler tool.
In this article, we took a simple look at how we can begin to use the profiler tool to debug performance problems.
We made a new project with a simple script that was written poorly that we then optimized. Unfortunately, in a real-world scenario, it’ll be far more complex than this.
Chances are, you’ll run into situations where its not an algorithm problem with your scripts but rather because of the following:
- Large number of assets in a scene taking your GPU
- Library dependencies that are doing computational-heavy things (like bringing in their own physics engine)
- Lighting/camera rendering issues
- Or like us, a combination of all three, plus more!
Either way, after going through a live example of using the Unity tool, you now have a rough idea on the first steps you can take to diagnose performance problems in your game! Stay tuned for what we work on next!
Published at DZone with permission of Josh Chang, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments