C# StringBuilder Cache Tip

Letters of the alphabet: ABC

Your C# program uses the StringBuilder type as an optimization to a string append operation. However, the StringBuilder itself is created many times in your program. With a StringBuilder cache you can reduce the cost further.

This C# article shows how you can optimize StringBuilder by caching it.

Method examples

The first version of the method, Method1, creates a new StringBuilder in every call. It then adds strings to the StringBuilder and returns the string representation. The second version, Method2, references a static StringBuilder field and then clears it each time. The StringBuilder field will remain allocated in one part of memory throughout all calls to Method2.

Method that creates new StringBuilder [C#]

static string Method1()
{
    StringBuilder builder = new StringBuilder();
    foreach (string value in _array)
	builder.Append(value);
    return builder.ToString();
}

Method that uses cached StringBuilder [C#]

static StringBuilder _builder = new StringBuilder();

static string Method2()
{
    StringBuilder builder = _builder;
    builder.Clear();
    foreach (string value in _array)
	builder.Append(value);
    return builder.ToString();
}

Benchmark result

Question and answer

Does storing the StringBuilder in a field cache improve performance here? The main advantage we see with the field is that the single StringBuilder has its capacity changed fewer times. Also, fewer allocations occur for the StringBuilder itself, not just its buffer.

Program that tests StringBuilder cache [C#]

using System;
using System.Diagnostics;
using System.Text;

class Program
{
    static string[] _array = { "dot", "net", "perls", "string", "array" };
    const int _max = 1000000;
    static void Main()
    {
	Method1();
	Method2();

	var s1 = Stopwatch.StartNew();
	for (int i = 0; i < _max; i++)
	{
	    Method1();
	}
	s1.Stop();
	var s2 = Stopwatch.StartNew();
	for (int i = 0; i < _max; i++)
	{
	    Method2();
	}
	s2.Stop();
	Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) /
	    _max).ToString("0.00 ns"));
	Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) /
	    _max).ToString("0.00 ns"));
	Console.Read();
    }
}

Output

187.20 ns
84.55 ns

Capacity. In the benchmark, if you adjust the capacity for the StringBuilder, Method1 will perform faster than before but still slower than Method2. This is because Method2 does not require capacity adjustments after the first call in the benchmark.

StringBuilder Capacity Test

Warning!

Warning

This optimization can sometimes reduce performance dramatically. This may occur when the StringBuilder buffers are very large and clearing them is slower than allocating new ones. Relying on the garbage collector may in this case be faster than using this kind of cache.

Summary

Using the StringBuilder type as a field can reduce memory allocations and improve performance. Unfortunately, you do need to call Clear every time it is accessed or the results will be incorrect.

StringBuilder Clear Method StringBuilder Performance StringBuilder Secrets
.NET