String Concatenation Performance
As programmers, we concatenate strings all the time. Should we worry about the performance? How about the amount of garbage we’re producing for the garbage collector? Today’s article runs a quick test to find out!
Today’s article is a followup to String.Format() vs. Concatenation vs. String Builder due to a comment by Shawn Blais Skinner:
Thanks Jackson. What I was really hoping to see, was some analysis of the garbage created with all 3 of these techniques. Maybe in a future article?
The following test hopes to answer that question with some real-world data. I’m not just going to time how long it takes to concatenate a bunch of strings, but do the concatenation each frame and measure frame times in Unity. This will allow the garbage collector time to run and clean up all those temporary strings.
Which temporary strings are these, you ask? Well, strings are immutable so concatenating them results in a third string:
string a = "a"; string b = "b"; string c = a + b; // a and b aren't modified. a new string "ab" is created.
Temporary strings start to show up when you concatenate more than two strings:
string a = "a"; string b = "b"; string c = "c"; string d = a + (b + c); // b+c creates a string that isn't saved to a variable: garbage!
With that in mind, here’s the relevant snippet of code from the test app designed to create a lot of garbage strings by concatenating over and over:
// num is the number of concatenations per frame for (var i = 0; i < num; ++i) { // temp is a temporary string to concatenate into // toConcat is "hello" temp += toConcat; }
So what happens when num
is 100? 1,000? 10,000? Here’s the test app that finds out:
using System; using UnityEngine; public class TestScript : MonoBehaviour { private const string toConcat = "hello"; private static readonly int[] options = {0, 100, 1000, 10000}; private const long optionRunTime = 10000; private int optionIndex; private string temp; private long lastTime; private System.Diagnostics.Stopwatch stopwatch; private int numFrames; private float[] results; private string report; void Start() { temp = string.Empty; results = new float[options.Length]; stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start(); } void Update() { if (optionIndex >= options.Length) { return; } var num = options[optionIndex]; for (var i = 0; i < num; ++i) { temp += toConcat; } temp = string.Empty; numFrames++; if (stopwatch.ElapsedMilliseconds > optionRunTime) { var avgTime = stopwatch.ElapsedMilliseconds / (float)numFrames; results[optionIndex] = avgTime; Debug.Log(num + " took " + avgTime); numFrames = 0; optionIndex++; GC.Collect(); stopwatch.Reset(); stopwatch.Start(); if (optionIndex == options.Length) { report = "Concats/Frame,Avg Frame Time\n"; for (var i = 1; i < options.Length; ++i) { var result = results[i]-results[0]; report += options[i] + "," + result + "\n"; } } } } void OnGUI() { if (report != null) { GUI.TextField(new Rect(0, 0, Screen.width, Screen.height), report); } } }
If you want to try out the test yourself, simply paste the above code into a TestScript.cs
file in your Unity project’s Assets
directory and attach it to the main camera game object in a new, empty project. Then build in non-development mode for 64-bit processors and run it windowed at 640×480 with fastest graphics. I ran it that way on this machine:
- 2.3 Ghz Intel Core i7-3615QM
- Mac OS X 10.10.5
- Unity 5.2.0f3, Mac OS X Standalone, x86_64, non-development
- 640×480, Fastest, Windowed
And here are the results I got:
Concats/Frame | Avg Frame Time |
---|---|
100 | -0.06895638 |
1000 | -0.0528698 |
10000 | 217.3486 |
Disregarding the slightly negative values for the 100 and 1,000 concatenation tests—likely just a minor testing fluctuation—the 10,000 concatenation test is the only one that takes any serious amount of time per frame. Suddenly, the concatenations go from not costing you hardly anything to costing you a ton! 217 milliseconds per frame turns your game into a slideshow: 5 FPS.
Somewhere between 1,000 and 10,000 concatenations per frame (on this test machine) lies a tipping point where the garbage collector really kicks in. For most games and apps, you’ll probably never hit this point though. So, in general, don’t worry about a few string concatenations per frame. If you start doing hundreds or thousands though, you might run into trouble.
This post was inspired by a comment. If you’ve got any ideas for an article you’d like to see, post a comment and I just might write it!