- Home /
How to avoid garbage for strings?
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
Answer by Bunny83 · Apr 01, 2017 at 07:40 PM
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. .
Since the question got already bumped I'd like to add that besides the string that is allocated we also will allocate a "box" for the "val" parameter because string.Format takes parameters of type object, so the int value need to be "boxed". However that's a tiny amount of memory. Though that was the main motivation for vexe to create the gstring library to get rid of most of those tiny allocations.
Apart from the the obvious memory allocations we just talked about, there might be additional allocations behind the scenes. For example when you set / change the text of an UI text component like we did here, the UI canvas has to update / reconstruct the mesh that represents the text in the scene. This might result in additional memory allocations.
@Bunny83 are there any drawbacks to using a StringBuilder which has had a reasonable buffer size pre-allocated through the use of one of the overloaded constructors?
StringBuilder is good for composing larger strings to prevent multiple allocations, the drawback is that calling ToString() on it creates a new string every time it is called. I heard that it isn't supposed to in the newer versions of .NET but it does in Unity. If you are using T$$anonymous$$P it will take a StringBuilder in one of the overrides for SetText(). That will get the underlying char array and use that to render the text, avoiding the allocation of ToString().
You can replace StringBuilder with string interpolation and get the same allocations when composing a string. Using StringBuilder.AppendFormat() will generate garbage when it uses ToString() on the type it is formatting, so if you want formatting you will generate garbage for each value you format.
I have a project somewhere that tested the difference between StringBuilder and string interpolation and found that both allocated the same or sometimes less with string interpolation.
I looked at the generated IL from string interpolation and it is basically syntactic sugar for String.Format() which will return a new string.
It seems like the happy medium would be with a pre-allocated StringBuilder using AppendFormat(), avoiding the use of ToString(), and passing the StringBuilder to methods that can work with the internal buffer directly (which I imagine is through the Chars[] array).
Answer by AnKOu · Dec 04, 2019 at 09:37 AM
You should cache each "{0:D2}m", "000" , "m" into member variable.
private const string m_format = "{0:D2}m";
private const string m_value = "000";
private const string m_m = "m";
Otherwise, it will instantiate a new string each time.
This is false. Any string literal, meaning anything between quotes will be cached by the system already so there is no need to do this redundantly.
Answer by Monsoonexe · Oct 03, 2020 at 07:23 PM
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;
}
}
I don't think calling the transform
getter generates garbage, nor calls GetComponent
. The transform is cached in the native code side AFAIK
https://forum.unity.com/threads/cache-transform-really-needed.356875/
(it's still more performant to cache it on the C# side)
Right, this answer is just as misleading as AnKOu's answer. People should be more careful with ter$$anonymous$$ology and generalisation. Yes, transform is a property and calls a native code method. This fact alone does not imply any memory allocations. On the other hand the "name" property of a gameobject will actually allocate a new string every time it's read. GetComponent isn't as bad as some people claim. GetComponent could allocate memory, but only when testing inside the editor and only when the component you're looking for does not exist (which is never the case for the transform component).
Reflection is a quite large system and most functions of the reflection system do not allocate any memory at all. Reflection mainly has issues with value types as they need to be boxed. Apart from that GetComponent doesn't use reflection since GetComponent finds the references on the native side. The reflection system is part of the managed runtime.
I can confirm this is not needed. I run the profiler quite often to see if methods generate garbage and this doesn't show up as generating any garbage, It also takes a total of 0.00ms. If it is in your project and it's using Unity 5.0 and up, I would file a bug report.
Answer by florinel2102 · Nov 07, 2020 at 02:19 PM
You could use StringBuilder Class from System.Text and also instead of creating a new instance every frame , you could use StringBuilder.Clear() . For more information about fixing memory performance
Answer by andrew-lukasik · Nov 07, 2020 at 05:29 PM
Lookup table
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;
}
Your answer
Follow this Question
Related Questions
Unity UI: Text Adventure 2 Answers
Display numbers in UI.Text Garbage Free? 2 Answers
X.Text just uses random values 1 Answer