I call this line every frame, but it allocs 1.1kb everytime. This makes in 1 second (assuming 60fps) 66kb and therefore ~4mb per minute. I don’t know how to avoid this garbaging since it needs to update every frame.
text_height.text = ((int)player.transform.position.y).ToString("000") + "m"; //GC 1.1kb per frame
Strings are immutable objects in C# / .NET. There are ways (using unsafe code) to directly manipulating the memory of a string object. One example is the gstring library written by @vexe. However it’s not really recommended to do this.
There are two ways how you can improve your specific case:
First, don’t concat several strings. You can use string.Format.
Second, don’t update it every frame. Since you cast the position into an int, the value might not change every frame. You can use a second cached int value to check if the number has changed.
Example:
private int oldVal = int.MaxValue;
void Update()
{
int val = (int)player.transform.position.y;
if (val != oldVal)
{
oldVal = val;
text_height.text = string.Format("{0:D2}m", val);
}
}
This will only create a new string when the value has changed and it will only create “one” string.
.
1. Avoid calling it every frame, just only when it needed to change.
This is the same as Bunny83’s answer, so I wont get into detail.
2. Avoid using string + or +=, use StringBuilder instead
Because += and + creates temporary strings.
// Bad example
var desc = enemy.Name;
desc += " Current HP:" + enemy.Hp; // allocate new string
desc += " Current MP:" + enemy.Mp; // allocate new string
return desc;
Convert it to StringBuilder-based code. You should also cache string builder in some static variable, clear it before new use.
// Better
var sb = new StringBuilder();
sb.Append(enemy.Name);
sb.Append(" Current HP:");
sb.Append(enemy.Hp);
sb.Append(" Current MP:");
sb.Append(enemy.Mp);
return sb.ToString();
This is still not zero allocation, StringBuilder’s format will still need allocation for its formatters and probably for boxing of object parameters.
Also final .ToString() to create a string always allocate anyway. You cannot change that.
3. Use alternative string builder replacement package
It should further reduce the allocation, but again, still not zero.
Last reminder: Use it judiciously
By using these special practices, you are trading off code readability for performance.
You don’t have to apply those practice EVERYWHERE! Just only apply them at where it needed. Profile your game and see where they are.
For in-depth reading, ZString’s blog page has very detailed information about each string API and how they causes allocation.
and say goodbye to these pesky allocations forever.
I expand on this simple & practical solution a bit more in my CacheStrings repo but here is the essence of it:
Transform player;
UnityEngine.UI.Text text_height;
Dictionary<int,string> _lookup = new Dictionary<int,string>(100);
readonly System.Func<int,string> _keyToString = (i) => $"{i:000}m";
void Awake ()
{
// warm it up for range you expect in-game:
for( int i=0 ; i<100 ; i++ )
_lookup.Add( i , _keyToString(i) );
}
void Update ()
{
string text;
{
int key = (int) player.position.y;
if( !_lookup.TryGetValue( key , out text ) )// returns reference to existing allocation
{
text = _keyToString(key);// allocates string missing from the pool
_lookup.Add( key , text );
}
}
text_height.text = text;
}
The garbage generation is also coming from ‘player.transform’. MonoBehaviour.transform is a Property, which means it is actually a function. It is secretly calling GetComponent() every frame, which is a heavy operation since it uses reflection and generates garbage. Consider caching it.
public class MyMono : MonoBehaviour
{
private Transform myTransform;
private void Start()
{
myTransform = transform;
}
}