Caching, rather than component lookups and "reflection" over objects

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.

Getting ready

For this recipe we have provided C# script Profile.cs in the 0423_10_11 folder.

How to do it...

To improve code performance by caching component lookups, perform the following steps:

  1. Start a new project and import the Profile.cs script.
  2. Create a cube named Cube-Player at (1, 1, 1), and give it the tag Player.
  3. Add the following C# script class 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;
      }
    }
  4. Add the following C# script class 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");
      }
    }
  5. When you run the scene you should see something similar to the following:
    Profile Method-1 took 0.012487000 seconds to complete over 2000 iterations, averaging 0.000006244 seconds per call = 6.243500000 micro-seconds per call
    
  6. The 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;
  7. Now replace your 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");
      }
  8. That should run faster (around 25-35% faster). But wait! Let's improve things some more. At the moment, to find 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;
      }
  9. Now replace your 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");
      }
  10. That should improve things around 10 percent. But we can still improve things—you'll notice an explicit 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>();
      }
  11. Now replace your 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");
      }
  12. After running the scene, after each improved method you should see something similar to the following, which gives us quantitative evidence of how the performance of our method is improving after each step in our refactoring process:
    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
    

How it works...

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.

Note

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:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.88.54