600 words
~3min read

Minimizing string GC in Unity

April 3rd 2019
1K views

An easy trap to fall into when you’re starting out in Unity is creating strings each frame in your update loop. Say you have score that can change from a bunch of things: eliminating foes, jumping on a block, collecting a ring, or just existing. Whatever floats your boat. Then in your score updater component you just do:

  void Update(){
    textComponent.text = "Score: " + score.ToString();
  }

Easy and done. Problem is, you’re now creating a string (which are immutable in C# and allocate memory) each frame. This will really add up and create lots of garbage which will need collecting.

The Unity docs have a little bit on this subject if you search for “strings”. However it leaves much to the reader to figure out in their own sweet time.

There are a couple approaches I’ve thought of that each have some pros and cons.

Solution 1#

Use images or some other render method instead of text. For example if you have a countdown timer that only uses a single digit you can make images for 0-9 and then swap them out based on the timer value.

Pros: Doesn’t need to allocate memory at all.

Cons: Tedious to implement, only works well for fixed length and known strings.

Solution 2#

Use events to trigger a function to update the text when the value changes. For example if you have an OnScoreChange event that you wire up to an UpdateScoreText function.

Pros: Only update the text (and allocate memory) when the value changes

Cons: Cumbersome to wire up all these events and handlers for every piece of text that has this pattern.

Solution 3#

Manually keep track of the previous value and only change the text when it changes.

  int previousScore = -1;

  void Update(){
    if(score != previousScore){
      previousScore = score;
      tmp.text = "Score: " + score.ToString();
    }
  }

Pros: Only update the text when the value changes

Cons: Annoying amount of boilerplate for each string to update, even though it’s less than the event binding solution.

Solution 4 - Preferred#

Create a class to keep track of all the previous values and only update the strings when the value changes. In the example below there is a ScoreChanger component that arbitrarily increases a score and then uses this method to only update the string when the score changes.

/* StringChanger.cs */

using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class ScoreChanger : MonoBehaviour {
  TMP_Text tmp;
  StringChanger stringChanger = new StringChanger();
  float timer;

  int score = 0;

  void Awake(){
    tmp = GetComponent<TMP_Text>();
  }

  void Update(){
    timer += Time.deltaTime;
    if((int)timer % 2 == 0){
      score++;
    }

    stringChanger.UpdateString(tmp, nameof(score), score);
  }
}

public class StringChanger {

  Dictionary<string, int> prevIntValues = new Dictionary<string, int>();
  Dictionary<string, float> prevFloatValues = new Dictionary<string, float>();

  public void UpdateString(TMP_Text tmp, string name, int value, string format = null){
    if(!prevIntValues.ContainsKey(name) || prevIntValues[name] != value){
      prevIntValues[name] = value;
      if(!string.IsNullOrEmpty(format)){
        tmp.text = string.Format(format, value);
      }else{
        tmp.text = value.ToString();
      }
    }
  }

  public void UpdateString(TMP_Text tmp, string name, float value, string format = null){
    if(!prevFloatValues.ContainsKey(name) || prevFloatValues[name] != value){
      prevFloatValues[name] = value;
      if(!string.IsNullOrEmpty(format)){
        tmp.text = string.Format(format, value);
      }else{
        tmp.text = value.ToString();
      }
    }
  }
}

Pros: Only update the text when the value changes, and a single point to store previous values

Cons: Overloads for each type of data you want to track have to be created to avoid allocations from boxing that would happen if you stored the values as objects.

This way is great for most of my use cases and since I primarily have int and float values it’s not too clunky to create the overloads. Let me know in the comments if you think of any other ways to tackle this problem or have suggestions for improvement!

Want the inside scoop?

Sign up and be the first to see new posts

No spam, just the inside scoop and $10 off any photo print!

comments powered by Disqus