Because all non-null reference-type objects and all boxed value-typed objects in managed code must be allocated on the managed heap, these objects might be the cause of performance issues in your application. The following secions outline approaches you can take to improve the performance of your code.
Strings in C# are immutable reference types. Unity allocates reference types on the managed heap and they are subject to garbage collection. Because strings are immutableYou cannot change the contents of an immutable (read-only) package. This is the opposite of mutable. Most packages are immutable, including packages downloaded from the package registry or by Git URL.
See in Glossary and can’t change once created, avoid creating temporary strings wherever possible.
The following example code combines an array of strings into a single string. Every time a new string is added inside the loop, the previous contents of the result variable become redundant, and the code allocates a whole new string.
// Bad C# script example: repeated string concatenations creates lots of
// temporary strings.
using UnityEngine;
public class ExampleScript : MonoBehaviour {
string ConcatExample(string[] stringArray) {
string result = "";
for (int i = 0; i < stringArray.Length; i++) {
result += stringArray[i];
}
return result;
}
}
If the input stringArray
contains { “A”, “B”, “C”, “D”, “E” }
, this method generates storage on the heap for the following strings:
“A”
“AB”
“ABC”
“ABCD”
“ABCDE”
In this example, you only need the final string, and the others are redundant allocations. The more items that there are in the input array, the more strings this method generates, each longer than the last.
If you need to concatenate a lot of strings together then use Mono library’s System.Text.StringBuilder
class. An improved version of the previous script looks like this:
// Good C# script example: StringBuilder avoids creating temporary strings,
// and only allocates heap memory for the final result string.
using UnityEngine;
using System.Text;
public class ExampleScript : MonoBehaviour {
private StringBuilder _sb = new StringBuilder(16);
string ConcatExample(string[] stringArray) {
_sb.Clear();
for (int i = 0; i < stringArray.Length; i++) {
_sb.Append(stringArray[i]);
}
return _sb.ToString();
}
}
A repeated concatenation doesn’t decrease performance too much unless it’s called frequently, like on every frame update. The following example allocates new strings each time Update
is called, and generates a continuous stream of objects that garbage collector must handle:
// Bad C# script example: Converting the score value to a string every frame
// and concatenating it with “Score: “ generates strings every frame.
using UnityEngine;
using UnityEngine.UI;
public class ExampleScript : MonoBehaviour {
public Text scoreBoard;
public int score;
void Update() {
string scoreText = "Score: " + score.ToString();
scoreBoard.text = scoreText;
}
}
To prevent this continuous requirement for garbage collection, you can configure the code so that the text only updates when the score changes:
// Better C# script example: the score conversion is only performed when the
// score has changed
using UnityEngine;
using UnityEngine.UI;
public class ExampleScript : MonoBehaviour {
public Text scoreBoard;
public string scoreText;
public int score;
public int oldScore;
void Update() {
if (score != oldScore) {
scoreText = "Score: " + score.ToString();
scoreBoard.text = scoreText;
oldScore = score;
}
}
}
To improve this further, you can store the score title (the part that says “Score: ”
) and the score display in two different UI.Text
objects, which means that there’s no need for string concatenation. The code must still convert the score value into a string, but this is an improvement on the previous versions:
// Best C# script example: the score conversion is only performed when the
// score has changed, and the string concatenation has been removed
using UnityEngine;
using UnityEngine.UI;
public class ExampleScript : MonoBehaviour {
public Text scoreBoardTitle;
public Text scoreBoardDisplay;
public string scoreText;
public int score;
public int oldScore;
void Start() {
scoreBoardTitle.text = "Score: ";
}
void Update() {
if (score != oldScore) {
scoreText = score.ToString();
scoreBoardDisplay.text = scoreText;
oldScore = score;
}
}
}
For a more optimized version of this, you could use the SetText(Char [])
method in the TMPro.TMP_Text
class of the UGUI package. The SetText
method allows you to use and reuse a char
array to build the score digit by digit and update the values in the char
array without needing to use a string.
In general, avoid closures in C# whenever possible. You should minimize the use of anonymous methods and method references in performance-sensitive code, especially in code that executes on a per-frame basis.
Method references in C# are reference types, so they’re allocated on the managed heap. This means that if you pass a method reference as an argument, you might create temporary allocations. This allocation happens regardless of whether the method you pass is an anonymous method or a predefined one.
Also, when you convert an anonymous method to a closure, the amount of memory required to pass the closure to a method increases a lot.
Here’s a code sample in which a list of randomized numbers need to be sorted in a particular order. This uses an anonymous method to control the sorting order of the list, and the sorting doesn’t create any allocations.
// Good C# script example: using an anonymous method to sort a list.
// This sorting method doesn’t create garbage
List<float> listOfNumbers = getListOfRandomNumbers();
listOfNumbers.Sort( (x, y) =>
(int)x.CompareTo((int)(y/2))
);
To make this snippet reusable, you might substitute the constant 2 for a variable in local scope:
// Bad C# script example: the anonymous method has become a closure,
// and now allocates memory to store the value of desiredDivisor
// every time it is called.
List<float> listOfNumbers = getListOfRandomNumbers();
int desiredDivisor = getDesiredDivisor();
listOfNumbers.Sort( (x, y) =>
(int)x.CompareTo((int)(y/desiredDivisor))
);
The anonymous method now needs to access the state of a variable which is outside of its scope, and so the method has become a closure. The desiredDivisor
variable must be passed into the closure so that the closure’s code can use it.
To ensure that the correct values are passed in to the closure, C# generates an anonymous class that can keep the externally scoped variables that the closure needs. A copy of this class is instantiated when the closure is passed to the Sort
method, and the copy is initialized with the value of the desiredDivisor
integer.
Executing the closure requires instantiating a copy of its generated class, and all classes are reference types in C#. For this reason, executing the closure requires allocation of an object on the managed heap.
When a value-typed variable gets automatically converted to a reference type this is called boxing. Boxing is one of the most common sources of unintended temporary memory allocations found in Unity projects. This most often happens when passing primitive value-typed variables (such as int
and float
) to object-typed methods.
In this example, the integer in x
is boxed so that it can be passed to the object.Equals
method, because the Equals
method on an object requires that an object is passed to it.
int x = 1;
object y = new object();
y.Equals(x);
C# IDEs and compilers don’t issue warnings about boxing, even though boxing leads to unintended memory allocations. This is because C# assumes that small temporary allocations are efficiently handled by generational garbage collectors and allocation-size-sensitive memory pools.
While Unity’s managed memory allocator does use different memory pools for small and large allocations, Unity’s garbage collector isn’t generational, so it can’t efficiently sweep out the small, frequent temporary allocations that boxing generates.
Boxing appears in CPU traces as calls to one of a few methods, depending on the scripting back end in use. These take one of the following forms, where <example class>
is the name of a class or struct, and …
is a number of arguments:
<example class>::Box(…)
Box(…)
<example class>_Box(…)
To find boxing, you can also search the output of a decompiler or IL viewer, such as the IL viewer tool built into ReSharper or the dotPeek decompiler. The IL instruction is box
.
Methods that list their optional parameters as with a params
modifier allocate an array for the parameters you pass into them. If available, use overrides of these methods that don’t rely on that modifier.
Did you find this page useful? Please give it a rating:
Thanks for rating this page!
What kind of problem would you like to report?
Thanks for letting us know! This page has been marked for review based on your feedback.
If you have time, you can provide more information to help us fix the problem faster.
Provide more information
You've told us this page needs code samples. If you'd like to help us further, you could provide a code sample, or tell us about what kind of code sample you'd like to see:
You've told us there are code samples on this page which don't work. If you know how to fix it, or have something better we could use instead, please let us know:
You've told us there is information missing from this page. Please tell us more about what's missing:
You've told us there is incorrect information on this page. If you know what we should change to make it correct, please tell us:
You've told us this page has unclear or confusing information. Please tell us more about what you found unclear or confusing, or let us know how we could make it clearer:
You've told us there is a spelling or grammar error on this page. Please tell us what's wrong:
You've told us this page has a problem. Please tell us more about what's wrong:
Thank you for helping to make the Unity documentation better!
Your feedback has been submitted as a ticket for our documentation team to review.
We are not able to reply to every ticket submitted.
When you visit any website, it may store or retrieve information on your browser, mostly in the form of cookies. This information might be about you, your preferences or your device and is mostly used to make the site work as you expect it to. The information does not usually directly identify you, but it can give you a more personalized web experience. Because we respect your right to privacy, you can choose not to allow some types of cookies. Click on the different category headings to find out more and change our default settings. However, blocking some types of cookies may impact your experience of the site and the services we are able to offer.
More information
These cookies enable the website to provide enhanced functionality and personalisation. They may be set by us or by third party providers whose services we have added to our pages. If you do not allow these cookies then some or all of these services may not function properly.
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us to know which pages are the most and least popular and see how visitors move around the site. All information these cookies collect is aggregated and therefore anonymous. If you do not allow these cookies we will not know when you have visited our site, and will not be able to monitor its performance.
These cookies may be set through our site by our advertising partners. They may be used by those companies to build a profile of your interests and show you relevant adverts on other sites. They do not store directly personal information, but are based on uniquely identifying your browser and internet device. If you do not allow these cookies, you will experience less targeted advertising. Some 3rd party video providers do not allow video views without targeting cookies. If you are experiencing difficulty viewing a video, you will need to set your cookie preferences for targeting to yes if you wish to view videos from these providers. Unity does not control this.
These cookies are necessary for the website to function and cannot be switched off in our systems. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms. You can set your browser to block or alert you about these cookies, but some parts of the site will not then work. These cookies do not store any personally identifiable information.