When it comes to optimization, Unity once again comes with a ready to use tool which is quite easy to use: Profiler.
Unity Profiler provides us a complete performance sketch of our project including its every aspect. By using the Profiler tool, we can easily determine the parts that needs to be improved. In this article, I will try to briefly explain the basics of Unity Profiler. For detailed information, I highly recommend you to take a look at Unity’s own article on this topic:
Optimize your mobile game performance: Tips on profiling, memory, and code architecture from…
Our Accelerate Solutions team knows the source code inside out and supports a plethora of Unity customers so they can…
Profiler Window Summary
First we need to open the Unity Profiler Windowby clicking Window — Analysis — Profiler.
The Profiler window provides us detailed analysis on CPU and GPU Usage, Rendering, Memory, Audio, Video, UI and Illumination during the gameplay. By clicking on the colored dots on the left side, you can enable/disable the view of the selected item. You can enable/disable the modules by clicking the drop down handle on the right side of Profiler Modules:
Before going further on, it is better to have the complete understanding of how Unity Profiler benchmarks our game:
How to Interpret ms/FPS Values on the Profiler
You might have observed that the primary performance unit is collected as ms and converted to FPS to make us easier to read. As you already know, FPS is the number of frames produced in a second. A second is consisted of 1000 milliseconds. Thus, we can calculate the FPS by dividing 1000 to the time that passed to create a single frame. So you got the point, the higher the time passed for producing a frame, the lower number of frames we get per second. Despite there are some trade offs according to the game or application (we might be OK with losing some FPS in the aim of gaining some other thing — such as rendering), we would want our game/application to have higher FPS rates for smooth gameplay.
Probably you already knew these details. Going furher on; I shall note that Profiler stores finite number of frames and then rewrites the new data onto existing ones. While running the game from the Editor, the game will be automatically paused once you click a point on the chart.
In order to analyze the data gathered, it is better to change the view on the bottom window from Timeline to Hierarchy:
On the middle, you can easily observe if your game is CPU or GPU dependent. Another important thing to note here is that by switching the Deep Profile tab ON (just on the top of the Profiler Window), we can gather detailed information on a resulted spike — including detecting the method causing it. Thus, it is highly recommended to using the Deep Profile.
The screenshot above is taken from a prototype. As you can observe, there are many spikes that needs to be examined.
Our aim on the Profiler is to analyze and determine the methods or the actions taken by the Unity Engine that are consuming the workforce of the device in an unoptimal way.
As can be seen above, Editor has taken the 88.4% of the load on selected frame. I can hear you telling me I need the data without the Editor. Well, Unity allows us to integrate the Profiler to game run in a build.
Using Unity Profiler in a Build
In order to test the game in the build mode, simply click on the Build Settings and check “Development Build”. Then, you will be also able to check the Deep Profiling Support and other settings regarding the Profiling as below.
Make sure to keep the Unity Project window open while running the game. The data collected by the Profiler in the running build is visible on the project window. Here, I shall note that the Profiler also runs smoothly in the mobile builds.
Idea in Brief
Unity Profiler simply visualizes the workload processed by the CPU and GPU. By also using the Deep Profiler, we easily have the ability to determine the parts that are responsible for lower frame rates. Above, I have already provided some information on total/self usage and Time(ms) columns.
The Calls column simply denotes the amount of times a method is called within a single frame. I will not be focusing on Calls on this example but make sure to keep an eye on the Calls column to ensure that you are not gathering unnecessary amount of calls within a single frame.
The Garbage Allocation column denotes the amount of garbage collected/processed within that frame. I will be focusing on the Garbage Collection on my next article.
Certainly, we are aiming to keep the Calls, GC Allocation and Time (ms) as low as possible.
I have been spending some time with the Profiler for the last couple of days. When it comes to the optimizing the script, the usual suspect comes to your mind is probably the Update methods. Certainly, it is better to eliminate the unnecessary usage of methods within the Update method. The Deep Profile option makes the debugging easier than ever.
To ease up the debugging within the Unity profiler, we can create labels just like below:
As a basic reminder, you shall avoid using the Debug.Log statements except for debugging purposes. Once you handle the bug, it is better to comment the Debug.Log’s out. I was pretty surprised to find out that simple Debug.Log statements may cause unnecessary load.
Here, as you can observe, a 5.4KB of garbage was collected because of a simple Debug Log statement. Thanks to Deep Profile, I now know that it is caused by the Player.Move method.
As stated in the Unity manual (the link I have provided at the beginning), it is really surprising to learn that there’s a huge performance difference in between using GameObject.CompareTag and manually looking for match with GameObject.Tag == “value”.
Another surprising fact for me was to find out that CoRoutines might also cause unneccessary load. As we are using “yield return new” keyword, we are actually creating a new game object (of type WaitForSeconds in my case) each time we start the coroutine.
On the script above, especially the coroutine called many times during the gameplay — as it is a simple click delay. By creating a variable of type WaitForSeconds and caching it, we can eliminate the redundant object creating during runtime — resulting in eliminated garbage creation.
I will be providing more information in the upcoming articles.
So, you got the point. PROFILER IS GOOD! :)