Go Back to Part 1 of the Tutorial:
Unity* Optimization Guide for Intel x86 Platforms: Part 1
We will explore two main areas of optimizations in this guide: Scripting and Editor-based. You will see a number of specifics for each optimization based on the roots in which it functions.
When you are profiling your application and see that a script’s Update() function does not need to be called every frame, you have a few great methods to reduce the amount of updates:
When looking for ways to optimize your memory usage, it is helpful to check the Unity profiler first. A great way to get an overview of how you are managing memory is to check the ‘GC Alloc’ section of the Overview window (Figure 14) and step through your frames until you see a significant allocation.
Figure 14. By inspecting many frames in a row, you can determine when GC will occur and adjust
It is also helpful to check how frequently garbage collection is being invoked. To see this, isolate the GarbageCollector field in the ‘CPU Usage’ sub-profiler (Figure 15):
Figure 15. Point identified in CPU usage when GC occurs
When collects are displayed, you can then click on a peak in the graph and look for the call to GC.Collect (Figure 16). By doing this you can see how much time each collect takes:
Figure 16. Statistics on GC
To avoid frequent allocations, it is advantageous to use structs instead of classes to have allocations be done on the stack, instead of in the heap. Multiple allocations to the heap can lead to significant memory fragmentation and frequent garbage collections.
As a rule of thumb, you should analyze your apps to find the most frequently used GameObjects and Components and make sure that these values are being cached. Any time you see an object being fetched every scene is an opportunity for caching and saving unnecessary computation.
The same rule applies to GameObject instantiation. In general, instantiation is a relatively slow call that should be avoided. If creating and destroying the same object types repeatedly in every scene, it is advantageous to maintain a list of those objects to be reused in an object manager script.
Unity recommends a central game manager to maintain lists of all of your cached game objects. After implementing this technique, you can include the following code snippet to compare the performance delta between the two methods by toggling a state button, or other control mechanism while viewing the CPU Usage profiler in real time. Here is the snippet (Figure 14) showing the difference in usage:
Figure 17. Using STATE to toggle objects
When working with dynamic objects in Unity, there are a few well-known optimizations and pitfalls to avoid. Whether you plan to move the object yourself or allow Unity to take control of the physics of an object, add a Rigidbody component to your object. This tells the Unity physics system that the object is moveable. When you wish to move the object manually, simply check the isKinematic flag (Figure 18). You also want to make sure that the static checkbox at the top right corner of the inspector is unchecked for that object (Figure 19).
Figure 18. isKinematic checked to take control over objects movement
Figure 19. Static property unchecked to keep dynamic object out of static set
To make sure that you are handling dynamic objects properly in your app, open up the profiler, isolate the physics subsection of the CPU Profiler, highlight a frame that lands on the physics time step (24 updates per second by default), and verify that you do not see any “Static Collider.Move (Expensive delayed cost)” entries (Figure 20) in the overview window under the object’s FixedUpdate() call. The lack of a Static Collider.Move message indicates the physics in this section is working appropriately.
Figure 20. Static Collider.Move (Expensive delayed cost) appears when you are not managing dynamic objects properly
For objects using a material with a ‘Fade Out’ rendering mode, or really anything that ever becomes fully transparent, it is important to make sure that you set the MeshRenderer component for that object to disabled once the object is fully transparent. These objects will always be dispatched in a draw call regardless of the alpha value. For example, sometimes developers use full screen quads to paint on damage indicators or vignette effects that pulse in and out when an event triggers. It’s important to know that the engine itself does not track when objects are fully transparent and resources will be wasted if care isn’t taken. The following screenshots were taken in the same scene, with the only difference being the alpha value of the semi-transparent objects in the foreground.
Figure 21. Five semi-transparent planes with the corresponding GPA frame capture. Semi-transparent objects are visible and taking 37.4% of the scene at 2,657.5 μs.
Figure 22. The five previously mentioned planes have their material’s alpha value set to 0. The corresponding GPA frame capture shows that there are still draw commands occurring on the GPU. These draws are taking 32.1% of the scene at 2,316.5 μs.
To make sure that you aren’t dispatching unnecessary draw calls, always check the alpha value associated with your potentially transparent objects if possible. For a simple material that renders based on color, just use a mechanism along the lines of the following code snippet.
Figure 23. When editing an objects transparency, always check that it is visible and disable it if necessary to save resources.
Continue to Part 3 of the Tutorial:
Unity* Optimization Guide for Intel x86 Platforms: Part 3
Performance varies by use, configuration and other factors. Learn more at www.Intel.com/PerformanceIndex.