Memory related Issues with Strings or Garbage Collection

Hi MonoGame Community,

I have currently been trying to do a lot of optimization on a game I am working on and ran a Memory profiler to see where there could be issues.

I have reduced the issues to just mainly strings being created constantly. One example I can give you is this segment of code:

if (ScreenManager.Instance.bShowMusicInfo)
{
musicBackUI.Draw(spriteBatch);

            spriteBatch.DrawString(TextManager.Instance.Chewy_32, _songString + ScreenManager.Instance.Music.currentSong.Name,
                new Vector2(80, 2160 - 170), Color.White);

            _currentMusicTime = TimeSpan.FromSeconds(ScreenManager.Instance.Music.CurrentMusicTime);
            _totalMusicTime = TimeSpan.FromSeconds(ScreenManager.Instance.Music.countDuration);
            _thisCurrentTimeString = String.Format("{1:D2}:{2:D2}",
                _currentMusicTime.Hours, _currentMusicTime.Minutes, _currentMusicTime.Seconds);
            _thisTotalTimeString = String.Format("{1:D2}:{2:D2}",
                _totalMusicTime.Hours, _totalMusicTime.Minutes, _totalMusicTime.Seconds);


            spriteBatch.DrawString(TextManager.Instance.Chewy_24,
                _thisCurrentTimeString,
                new Vector2(80, 2160 - 120), Color.White);
            spriteBatch.DrawString(TextManager.Instance.Chewy_24,
               " / " + _thisTotalTimeString,
                new Vector2(170, 2160 - 120), Color.White);
        }

It is not the only area where there are strings constantly being created each frame but if I enable the music display this code will run and the number of strings being created increases and causes a Garbage Collection say every 4 seconds.

I have tried multiple ways including a StringBuilder but the way I went about it was to use one StringBuilder _string and I was constantly Appending and Clearing each update and had the same string counts.

My question would be, is there a way to display these strings still onscreen and update when the data changes without there being massive amounts of strings to be created and causing the GC to fire so many times?
Or could someone educate me on these issues and the right way to approach it?
I have checked out willmotil’s MgStringBuilder at No Garbage Text ( MgStringBuilder ) But I am unsure how to utilize.

Any help is appreciated

If you want to have such tight a level of control of memory that 6 byte strings created 30-60 times a second need to be de-allocated immediately after usage, managed code is not what you want.

That kind of optimization needs a lower-level language with direct control of allocation.

GC doing a latest generation only collection every 4 seconds shouldn’t be so bad for performance.

1 Like

I can’t understand why those tiny number of strings should be causing issues.

But there are some things you can do

 public const String TimeFormat = "{0:D2}:{1:D2}:{2:D2}";

 _thisCurrentTimeString = String.Format(TimeFormat, _currentMusicTime.Hours, _currentMusicTime.Minutes, _currentMusicTime.Seconds);

Will prevent you creating two temporary strings every pass.

" / " + _thisTotalTimeString,

Is creating another temporary string

I really don’t know why you are having a problem with strings though, they are pretty fire and forget

Thanks for the replies,
I had switched to StringBuilder also and used all as 1 StringBuilder to try but it was the same.
This was a memory capture on a new game which doesn’t have many strings to draw:
(Purple line is number of GC0)


The mid part was on the main menu with V-Sync off so FPS is around 3k, and the end part as you see there is a slow increase each time and the only thing to be created was strings.

Is this normal behaviour?
It is even worse on a game I have released, because it has so many more strings to write.
This is a memory capture on my released game:

On this one the GC is firing almost every second to remove mostly strings. I think this amount of GC is having an impact on performance. This is what is being displayed to get the behavior you see in the 2nd Memory Test:

Seeing these results has me worried and wondered how I could optimize the display of strings.

All you comment is normal when using lots of strings. Note that running at 3000 FPS instead of 60 FPS is giving you 50 times more garbage, because you run that garbage generating code 50 times more.

However, are you absolutely sure you were using the StringBuilder correctly? (e.g. allocating it every frame, or converting it to string before passing it to DrawString)
I find very hard to believe string and StringBuilder giving you the same amount of garbage.

Its possible I am using it wrong: here is the code I tried with Stringbuilder instead where _string is my StringBuilder: (if this isn’t the right way to use it please let me know)

if (ScreenManager.Instance.bShowMusicInfo)
{
musicBackUI.Draw(spriteBatch);
_string.Append("Song: ");
_string.Append(ScreenManager.Instance.Music.currentSong.Name);
spriteBatch.DrawString(TextManager.Instance.Chewy_32, _string,
new Vector2(80, 2160 - 170), Color.White);
_string.Clear();

            _currentMusicTime = TimeSpan.FromSeconds(ScreenManager.Instance.Music.CurrentMusicTime);
            _totalMusicTime = TimeSpan.FromSeconds(ScreenManager.Instance.Music.countDuration);

            _string.AppendFormat("{1:D2}:{2:D2}",
                _currentMusicTime.Hours, _currentMusicTime.Minutes, _currentMusicTime.Seconds);
            _string.Append(" / ");
            _string.AppendFormat("{1:D2}:{2:D2}",
                _totalMusicTime.Hours, _totalMusicTime.Minutes, _totalMusicTime.Seconds);

            spriteBatch.DrawString(TextManager.Instance.Chewy_24,
                _string,
                new Vector2(80, 2160 - 120), Color.White);
            _string.Clear();

        }

You’re right, it is creating a lot of garbage. My fault, I really thought StringBuilder was more garbage friendly than what I’m seeing.
Replacing AppendFormat with Append (_currentMusicTime.Minutes). Append (":").Append (_currentMusicTime.Seconds) reduces the garbage a bit, but not significatively (And you’d still have to format it)

If your game is as string heavy as it seems for the screenshot, my suggestion would be either creating your own string class (not hard, only a PITA to replace it everywhere) or using something already created like :

I have my own too for my games, but unfortunately it’s not in a decent state to be shared : (

There are also other tips to avoid garbage creation. In example, the string you’re creating (e.g. 03m:15s ) only needs to be recreated once every second. You can cache the string, and change it only when the seconds change. This would reduce the creations from 3000 every second to 1 every second.

In the screenshot menus, you could print the strings once (as they’re all probably constant) and save into a list, and then reprinting those strings. Unfortunately all those tricks will dirty your code a lot, so I’d go with a No Garbage string unless it’s a very small problem like the time printing issue.

2 Likes

1+ for this. Pretty sure this is the solution OP needs. Don’t allocate more than you need and the GC will act nicer

Thanks for the reply, what I also did when I was trying to resolve was something kind of like that, I had a timer that would fire the string update every second instead and then I still had
spriteBatch.DrawString(TextManager.Instance.Chewy_24,
_string,
new Vector2(80, 2160 - 120), Color.White);
_string.Clear();
with _string being only updated each second. but i was still drawing a string though spritebatch.drawstring each frame and saw still a lot of strings being created and my assumption was drawstring was creating a new string each frame even though it was the exact same string being passed in. Is that how drawstring works? and would that be the correct way to do it similar to your explanationn, or was I doing that wrong?

Please format your code properly.

Don’t fire a timer, check if the current time is different than the old time, and write to a new string if it has.

Pseudo

var newTime = TimeSpan.FromSeconds(ScreenManager.Instance.Music.CurrentMusicTime);
if (time has increased in seconds)
{
    _currentMusicTime = TimeSpan.FromSeconds(ScreenManager.Instance.Music.CurrentMusicTime);
   // Update strings here
}

EDIT:

If you are running _string_Clear() here, then doesn’t _string get updated every frame?

Oh yes, I accidentally copied the clear for the example but it wasn’t clearing when I attempted. I just had a timer which updated the string when it hit 0 and then reset to 1.

Even with your example though what I am unsure about is whether the spriteBatch.DrawString creates a new string each frame even when passing in the same string over 1 second. As in my attempt I monitored the memory and it still went up when displaying the music info with # of strings being created still increased as if it was frame by frame even though I confirmed the timer was working as intended and updated the string only once per second.

I’ve set the code to update the string only once a second in the update and this time did it through the time check which is working as intended but the memory use is as I expected, it is still increasing when turning on the display:


The start rows it increases (but a bit slower, mainly due to the options menu as well), and then as soon as I turn on the music info it increases at a faster rate. Which makes me think my assumptions about DrawString creating new strings regardless if they are updated or not are correct?

My Code Currently looks like this: (With better alignment)

private double _previousTime = 0;
private TimeSpan _currentMusicTime;
private TimeSpan _totalMusicTime;
private string _thisCurrentTimeString = "";
private string _thisTotalTimeString = "";
private static string _songString = "Song: ";
private static string _versionString = "Version: ";

In the update (using Math.Floor to get nearest Second):

double _newTime = Math.Floor(TimeSpan.FromSeconds(ScreenManager.Instance.Music.CurrentMusicTime).TotalSeconds);
        if (ScreenManager.Instance.bShowMusicInfo && _newTime > _previousTime)
        {
            _currentMusicTime = TimeSpan.FromSeconds(ScreenManager.Instance.Music.CurrentMusicTime);
            _previousTime = Math.Floor(_currentMusicTime.TotalSeconds);
            _totalMusicTime = TimeSpan.FromSeconds(ScreenManager.Instance.Music.countDuration);
            _thisCurrentTimeString = String.Format("{1:D2}:{2:D2}",
                _currentMusicTime.Hours, _currentMusicTime.Minutes, _currentMusicTime.Seconds);
            _thisTotalTimeString = String.Format("{1:D2}:{2:D2}",
                _totalMusicTime.Hours, _totalMusicTime.Minutes, _totalMusicTime.Seconds);
        }

and in my draw:

    if (ScreenManager.Instance.bShowMusicInfo)
            {
                musicBackUI.Draw(spriteBatch);

                spriteBatch.DrawString(TextManager.Instance.Chewy_32, _songString + ScreenManager.Instance.Music.currentSong.Name,
                    new Vector2(80, 1990), Color.White);

                spriteBatch.DrawString(TextManager.Instance.Chewy_24,
                    _thisCurrentTimeString,
                    new Vector2(80, 2040), Color.White);
                spriteBatch.DrawString(TextManager.Instance.Chewy_24,
                   " / " + _thisTotalTimeString,
                    new Vector2(170, 2040), Color.White);
            }

You are generating a new string for every Draw() here:

and here:

In both cases you are joining 2 string with _songString + ScreenManager.Instance.Music.currentSong.Name and " / " + _thisTotalTimeString, which both generates a new string for every frame.

ah wow, thanks. I’ve fixed it now to not do that, and memory increase is consistent now whether Music is displayed or not.
Thank you guys. I can fix the other sections now and hopefully bring that right down.

Yea, this is generally how I handle the bulk of my UI text rendering. I render the text to a RenderTarget2D object and only update when the text has actually changed. When the text hasn’t changed, I just render the same texture that was generated previously.

@MrDelusive, I dunno if it helps but I have an example of this in a font scaling prototype I made.

As others have mentioned, the idea is to limit changes of memory to cases where it needs to change.
However there is trick which I believe no one has mentioned yet.

A string in C# is an immutable object which is allocated and tracked on some memory heap by the garbage collector (GC). The immutability means to modify a string a new string has to be created. The consequence of doing that however is that a new allocation has to happen. What we really want in is to avoid dynamically allocating memory in loops. Since a game is a loop, we want to avoid dynamically allocating memory in games every frame. Best performance can be achieved if allocation of memory can happen once at startup or at strategic times like loading a level, but not every frame (iteration of a loop).

The idea with StringBuilder is it’s an abstraction to manipulate an intermediate buffer of char values and “build” the string from this buffer. Note that in .NET a char is 2 bytes. Using StringBuilder allows to use the intermediate buffer directly. SpriteBatch.DrawString even has an overload for accepting a StringBuilder as a parameter allowing the intermediate buffer to be used directly instead of having to create a string from the buffer. Great! We can use a StringBuilder to achieve mutable strings without dynamic allocation. Sadly, some methods of StringBuilder allocate memory. Can we do better?

Well yes, but it’s not pretty. We can use our own buffers of char and create our own logic for manipulating and drawing these buffers. This is where the No Garbage Text ( MgStringBuilder ) and others do. For example lets say you want to draw the string “FPS: 1000” to the screen. If we fix the buffer to be a certain size we would need to know how long the string can possibly get. So lets say we have the format: “FPS: NNNNNN”. From here every frame we can simply change the contents of the buffer (mutate) to match what we want to render. Simple, effective, but not intuitive from how .NET traditional looks at how to solve problems with strings.

1 Like

Thanks, appreciate it. I have a working example now from earlier posts which I will most likely turn into its own string wrapper class to help me cement my understanding. But if I have issues I will definitely check it out.

If you truly want zero allocation without reinventing .NET, have you considered pre-rendering all font characters at your preferred sizes and storing their dimensions? You could generate them all into memory and create your own string graphics on the fly by rendering them individually.

Monogames spritebatch is garbage free you can profile that.

When we say garbage around here we mean memory DE-Allocation make sure you understand the distinction garbage is not bad to say allocation in the general use of the term is a requirement. Only a collect is bad as it freezes the rendering thread. So what we really mean when we say garbage around here is memory that has been allocated and then immediatly or soon after marked by the gc for deallocation and adds to the pool of memory for the gc to take out.

C# is the problem here… simple tricks wont help with some things aka. Every time you convert ANY number to a string aka implicitly explicitly that is unavoidable garbage allocation coupled to de-allocation that immediately gets tracked by the gc for later collection.
That class i posted is 90% a direct number to character converting algorithm meaning to string never gets called on numbers you pass to it directly thru its appends. Unlike StringBuilder though you could just yank out one of those functions stand alone to a method i just turned it into a whole class for everything.

There are tricks now with span of t and the sort but im not sure that applies to monogame out of the box at present.

Hey guys, thanks again for all the help with this issue. I wanted to link how the memory is looking now cause I was so happy with it. I spent the last 13 hours fixing up all my code in the first game which had the GC firing at worst once per second on that example pic. Now on any part of the game the memory increases very slightly even on 1k fps.

Original had around 150 GC in 100 sec, now has only 3.

2 Likes