
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 should be acquired with no additional memory cost. However, retrieving string properties from GameObjects is another subtle way of accidentally crossing over the Native-Managed Bridge.
The two properties of GameObject affected by this behavior are tag and name. 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 the runtime identification of objects, which can make this a significant problem for some teams.
For example, the following code would cause an additional 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 a better practice to identify objects by their components and class types and to identify values that do not involve string objects, but sometimes we're forced into a corner. Maybe we didn't know any better when we started, we inherited someone else's code base, 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 the Native-Managed Bridge overhead cost.
Fortunately, the tag property is most often used in comparison situations, and GameObject provides the CompareTag() method, which is an alternative way to compare tag properties that avoids the Native-Managed Bridge entirely.
Let's perform a simple test to prove how this simple change can make all the difference:
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 loops. Here are the results:

Looking at the breakdown view for each spike, we can see two completely different outcomes:

Retrieving the tag property 10 million times (way more than makes sense in reality, but this is useful for comparison) results in about 400 megabytes of memory being allocated just for string objects alone. We can see this memory allocation happening in the spike within the GC Allocated element in the Memory Area of the Timeline View. Also, this process takes around 2,000 milliseconds to process, where another 400 milliseconds are spent on garbage collection once the string objects are no longer needed.
Meanwhile, using CompareTag() 10 million times costs around 1,000 milliseconds to process and causes no memory allocations, and hence no garbage collection. This is made apparent from the lack of a spike in the GC Allocated element in the Memory Area. This should make it abundantly clear that we must avoid accessing the name and tag properties whenever possible. If tag comparison becomes necessary, then we should make use of CompareTag(). Unfortunately, there is no equivalent for the name property, so we should stick to using tags where possible.