One might argue that there are greater resource hogs in Unity than the humble string, but it is one of the easier aspects to fix early on.
Most string operations build tiny amounts of garbage, but if those operations are called several times over the course of a single update, it stacks up. Over time it will trigger the automatic Garbage Collection, which may result in a visible CPU spike.
Consider the following example.
string[] StringKeys = new string[] {
"Key0",
"Key1",
"Key2"
};
void Update()
{
for (var i = 0; i < 3; i++)
{
// Cached, no garbage generated
Debug.Log(StringKeys[i]);
}
for (var i = 0; i < 3; i++)
{
// Not cached, garbage every cycle
Debug.Log("Key" + i);
}
// The most memory-efficient way is to not create a cache at all and use literals or constants.
// However, it is not necessarily the most readable or beautiful way.
Debug.Log("Key0");
Debug.Log("Key1");
Debug.Log("Key2");
}
It may look silly and redundant, but if you're working with Shaders, you might run into situations such as these. Caching the keys will make a difference.
Please note that string literals and constants do not generate any garbage, as they are injected statically into the program stack space. If you are generating strings at run-time and are guaranteed to be generating the same strings each time like the above example, caching will definitely help.
For other cases where the string generated is not the same each time, there is no other alternative to generating those strings. As such, the memory spike with manually generating strings each time is usually negligible, unless tens of thousands of strings are being generated at a time.
Doing string operations for Debug messages, ie. Debug.Log("Object Name: " + obj.name)
is fine and cannot be avoided during development. It is, however, important to ensure that irrelevant debug messages do not end up in the released product.
One way is to use the Conditional attribute in your debug calls. This not only removes the method calls, but also all the string operations going into it.
using UnityEngine;
using System.Collections;
public class ConditionalDebugExample: MonoBehaviour
{
IEnumerator Start()
{
while(true)
{
// This message will pop up in Editor but not in builds
Log("Elapsed: " + Time.timeSinceLevelLoad);
yield return new WaitForSeconds(1f);
}
}
[System.Diagnostics.Conditional("UNITY_EDITOR")]
void Log(string Message)
{
Debug.Log(Message);
}
}
This is a simplified example. You might want to invest some time designing a more fully fledged logging routine.
This is a minor optimisation, but it's worth a mention. Comparing strings is slightly more involved than one might think. The system will try to take cultural differences into account by default. You can opt to use a simple binary comparison instead, which performs faster.
// Faster string comparison
if (strA.Equals(strB, System.StringComparison.Ordinal)) {...}
// Compared to
if (strA == strB) {...}
// Less overhead
if (!string.IsNullOrEmpty(strA)) {...}
// Compared to
if (strA == "") {...}
// Faster lookups
Dictionary<string, int> myDic = new Dictionary<string, int>(System.StringComparer.Ordinal);
// Compared to
Dictionary<string, int> myDictionary = new Dictionary<string, int>();