Avoid retrieving string properties from GameObjects

Ordinarily, retrieving a string property from an object is the same as retrieving any other reference type property in C#; it is acquired with no additional memory cost. However, for whatever arcane reason hidden within the Unity source code, retrieving string properties from GameObjects duplicates the string in memory and results in a heap allocation. This draws the attention of the Garbage Collector, which, if we are not careful, can cause CPU spikes that will affect performance during runtime.

The two properties of GameObject affected by this strange behavior are tag and name. Retrieving either of these properties for any reason will cause unnecessary heap allocations. Therefore, it is unwise to use either property during gameplay, and you should only use them in performance-inconsequential areas such as Editor Scripts. However, the Tag system is commonly used for runtime identification purposes, which can make this a significant problem for some teams.

For example, the following code would cause an additional heap memory allocation during every iteration of the loop:

for (int i = 0; i < listOfObjects.Count; ++i) {
  if (listOfObjects[i].tag == "Player") {
    // do something with this object
  }
}

It is often better practice to identify objects by their Components, class types, and identifying values that do not involve strings, but sometimes we're forced into a corner. Maybe we didn't know any better when we started, we inherited someone else's codebase, or we're using it as a workaround for something. Let's assume that, for whatever reason, we're stuck with the Tag system, and we want to avoid these heap allocations.

Fortunately, the tag property is most often used in comparison situations, and GameObject provides an alternative way to compare tag properties, which does not cause a heap allocation—the CompareTag() method.

Let's perform a simple test to prove how this simple change can make all the difference in the world:

void Update() {
  int numTests = 10000000;
  if (Input.GetKeyDown(KeyCode.Alpha1)) {
    for(int i = 0; i < numTests; ++i) {
      if (gameObject.tag == "Player") {
        // do stuff
      }
    }
  }

  if (Input.GetKeyDown(KeyCode.Alpha2)) {
    for(int i = 0; i < numTests; ++i) {
      if (gameObject.CompareTag ("Player")) {
        // do stuff
      }
    }
  }
}

We can execute these tests by pressing the 1 and 2 keys to trigger the respective for loop. Here are the results:

Avoid retrieving string properties from GameObjects

Looking at the Breakdown view for each spike, we can see two completely different situations:

Avoid retrieving string properties from GameObjects

Retrieving the tag property 10 million times results in about 363 MB of memory being allocated just for strings alone. This takes 2435 milliseconds to process, where 488 milliseconds are spent on garbage collection. Meanwhile, using CompareTag() 10 million times costs 1788 milliseconds to process, and causes no heap memory allocations, and hence no Garbage Collection. This should make it abundantly clear that we must avoid using the name and tag properties. So, if Tag comparison becomes necessary, then we should make use of CompareTag().

Note that passing in a string literal like "Player" does not result in a runtime heap allocation, since the application technically allocates this value during initialization and merely references it at runtime. However, if we dynamically generate the comparison string, then we will run into the same heap memory allocation problems, because we're essentially creating a new string object each time.

You will learn more nuances about the Garbage Collector and string usage in Chapter 7, Masterful Memory Management.

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

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