Optimization principal 5: Minimize actions requiring Unity to perform "reflection" over objects. Reflection is when at run-time, Unity has to analyze objects to see whether they contain a given set of methods or components. An example of this would be the simple, useful, but slow, FindObjectsByTag()
. Another action that slows Unity down is each time we make it look up an object's component, either explicitly using GetComponent()
, or implicitly using a component accessor
variable such as transform
or renderer
. In this recipe, we'll incrementally refactor a method, making it more efficient at each step by removing reflection and component lookup actions. The method we improve finds half the distance from the Main Camera (to which the script is attached) to another GameObject in the scene tagged Player
.
To improve code performance by caching component lookups, perform the following steps:
Profile.cs
script.Cube-Player
at (1, 1, 1), and give it the tag Player
.SimpleMath
to the Main Camera:// file: SimpleMath.cs using UnityEngine; using System.Collections; public class SimpleMath : MonoBehaviour { public float Halve(float n){ return n / 2; } }
ProfileScript1
to the Main Camera:// file: ProfileScript1.cs using UnityEngine; using System.Collections; public class ProfileScript1 : MonoBehaviour { private int ITERATIONS = 2000; private void Start(){ for(int i=0; i < ITERATIONS; i++){ FindDistanceMethod(); } Profile.PrintResults(); } private void FindDistanceMethod(){ Profile.StartProfile("Method-1"); Vector3 pos1 = transform.position; Transform playerTransform = GameObject.FindGameObjectWithTag("Player").transform; Vector3 pos2 = playerTransform.position; float distance = Vector3.Distance(pos1, pos2); SimpleMath mathObject = GetComponent<SimpleMath>(); float halfDistance = mathObject.Halve(distance); Profile.EndProfile("Method-1"); } }
Profile Method-1 took 0.012487000 seconds to complete over 2000 iterations, averaging 0.000006244 seconds per call = 6.243500000 micro-seconds per call
FindGameObjectsWithTag()
method is slow, so let's fix that by adding a public Transform
variable named playerTransformCache
, and dragging Cube-Player over this variable in the Inspector when the Main Camera is selected:public Transform playerTransformCache;
FindDistanceMethod()
with the following, which makes use of this cached reference to the transform of Cube-Player
, avoiding the slow object-tag reflection lookup altogether:private void FindDistanceMethod(){ Profile.StartProfile("Method-2"); Vector3 pos1 = transform.position; Vector3 pos2 = playerTransformCache.position; float distance = Vector3.Distance(pos1, pos2); SimpleMath mathObject = GetComponent<SimpleMath>(); float halfDistance = mathObject.Halve(distance); Profile.EndProfile("Method-2"); }
pos1
we are making Unity find the transform component of the Main Camera every time the method is called. Since the camera is not moving, let's cache this Vector3
position as follows:private Vector3 pos1Cache = new Vector3(); private void Awake(){ pos1Cache = transform.position; }
FindDistanceMethod()
with the following, which makes use of this cached Main Camera position:private void FindDistanceMethod(){ Profile.StartProfile("Method-3"); Vector3 pos2 = playerTransformCache.position; float distance = Vector3.Distance(pos1Cache, pos2); SimpleMath mathObject = GetComponent<SimpleMath>(); float halfDistance = mathObject.Halve(distance); Profile.EndProfile("Method-3"); }
GetComponent()
call to get a reference to our mathObject
. Let's cache this scripted component reference as well, to save GetComponent()
for each iteration. We'll declare a variable called mathObjectCache
, and in Awake()
, we will set it to refer to our SimpleMath
scripted component.private SimpleMath mathObjectCache; private void Awake(){ pos1Cache = transform.position; mathObjectCache = GetComponent<SimpleMath>(); }
FindDistanceMethod()
with the following, which makes use of this cached component reference:private void FindDistanceMethod(){ Profile.StartProfile("Method-4"); Vector3 pos2 = playerTransformCache.position; float distance = Vector3.Distance(pos1Cache, pos2); float halfDistance = mathObjectCache.Halve(distance); Profile.EndProfile("Method-4"); }
Profile Method-2 took 0.006818000 seconds to complete over 2000 iterations, averaging 0.000003409 seconds per call = 3.409000000 micro-seconds per call Profile Method-3 took 0.006009000 seconds to complete over 2000 iterations, averaging 0.000003005 seconds per call = 3.004500000 micro-seconds per call Profile Method-4 took 0.003555000 seconds to complete over 2000 iterations, averaging 0.000001778 seconds per call = 1.777500000 micro-seconds per call
This recipe illustrates how we try to cache references once, before any iteration, for variables whose value will not change. This can be done for items such as references to GameObjects and their components; and in this example, the Vector3
position of the Main Camera. Through this removing of implicit and explicit component and object lookups from a method that has to be executed many times, the performance improvement is clear to see.
Two good places to learn more about Unity performance optimization techniques are from the Performance Optimization web page in the Unity Script Reference; and Unity's Joachim Ante's Unite07—a presentation on "Performance Optimization". Some of the recipes in this chapter had their origins from suggestions found in the following sources:
18.226.163.229