String Concatenation Performance

Which string concatenation method has the best performance? - I'm sure at some point in time, a developer will ask (or be asked) this question. Here's a spike I did to benchmark the following methods:
  • String.Format
  • StringBuilder
  • + Operator
  • String.Concat
  • String.Join
  • StringWriter
  • .Replace()

The machine specifications that the tests ran on:


Test #1 - Using fixed string values. 

String.Format
for (int i = 0; i < _iterationCount; i++)
{
    result = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}",
        "The ", "Quick ", "Brown ", "Fox ", "Jumps ", "Over ", "The ",
        "Lazy ", "Dog");
}

StringBuilder
for (int i = 0; i < _iterationCount; i++)
{
    result = builder.Append("The ").Append("Quick ").Append("Brown ")
        .Append("Fox ").Append("Jumps ").Append("Over ")
        .Append("The ").Append("Lazy ").Append("Dog").ToString();
    builder.Clear();
}

+ Operator
for (int i = 0; i < _iterationCount; i++)
{
    result = "The " + "Quick " + "Brown " + "Fox " + "Jumps "
             "Over " + "The " + "Lazy " + "Dog";
}

String.Concat
for (int i = 0; i < _iterationCount; i++)
{
    result = string.Concat("The ", "Quick ", "Brown ", "Fox "
                           "Jumps ""Over ", "The ", "Lazy ", "Dog");
}

String.Join
for (int i = 0; i < _iterationCount; i++)
{
    result = string.Join("",
        "The ", "Quick ", "Brown ", "Fox ", "Jumps ", "Over ", "The "
        "Lazy ", "Dog");
}

StringWriter
for (int i = 0; i < _iterationCount; i++)
{
    writer.Write("The ");
    writer.Write("Quick ");
    writer.Write("Brown ");
    writer.Write("Fox ");
    writer.Write("Jumps ");
    writer.Write("Over ");
    writer.Write("The ");
    writer.Write("Lazy ");
    writer.Write("Dog");

    result = writer.ToString();
    writer.GetStringBuilder().Clear();
}

.Replace()
for (int i = 0; i < _iterationCount; i++)
{
    result = "{0}{1}{2}{3}{4}{5}{6}{7}{8}"
        .Replace("{8}", "Dog")
        .Replace("{7}", "Lazy ")
        .Replace("{6}", "The ")
        .Replace("{5}", "Over ")
        .Replace("{4}", "Jumps ")
        .Replace("{3}", "Fox ")
        .Replace("{2}", "Brown ")
        .Replace("{1}", "Quick ")
        .Replace("{0}", "The ");

}

Iteration count is set to 100,000 and here are the results:
  • String.Format = ~150 ms
  • StringBuilder = ~56 ms
  • + Operator = 0 ms
  • String.Concat = ~69 ms
  • String.Join = ~64 ms
  • StringWriter = ~59 ms
  • .Replace() = ~380 ms

Are you surprised to see 0 ms for the + operator? If you use a disassembler you will noticed that the compiler has optimized it to:

result = "The Quick Brown Fox Jumps Over The Lazy Dog"

Therefore, it shows that it is safe to use the + operator to break up long strings in our code for readability sake.

Let's proceed to another test and this time, we will replace all the fixed string values with variables instead.

private string the = "The ";
private string quick = "Quick ";
private string brown = "Brown ";
private string fox = "Fox ";
private string jumps = "Jumps ";
private string over = "Over ";
private string lazy = "Lazy ";
private string dog = "Dog ";


Test #2 - Using string variables. 

String.Format
for (int i = 0; i < _iterationCount; i++)
{
    result = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}",
        the, quick, brown, fox, jumps, over, the, lazy, dog);
}

StringBuilder
for (int i = 0; i < _iterationCount; i++)
{
    result = builder.Append(the).Append(quick).Append(brown)
        .Append(fox).Append(jumps).Append(over)
        .Append(the).Append(lazy).Append(dog).ToString();
    builder.Clear();
}

+ Operator
for (int i = 0; i < _iterationCount; i++)
{
    result = the + quick + brown + fox + jumps + 
             over + the + lazy + dog;
}

String.Concat
for (int i = 0; i < _iterationCount; i++)
{
    result = string.Concat(the, quick, brown, fox, jumps, over, the, 
                           lazy, dog);
}

String.Join
for (int i = 0; i < _iterationCount; i++)
{
    result = string.Join("",
        the, quick, brown, fox, jumps, over, the, lazy, dog);
}

StringWriter
for (int i = 0; i < _iterationCount; i++)
{
    writer.Write(the);
    writer.Write(quick);
    writer.Write(brown);
    writer.Write(fox);
    writer.Write(jumps);
    writer.Write(over);
    writer.Write(the);
    writer.Write(lazy);
    writer.Write(dog);

    result = writer.ToString();
    writer.GetStringBuilder().Clear();
}

.Replace()
for (int i = 0; i < _iterationCount; i++)
{
    result = "{0}{1}{2}{3}{4}{5}{6}{7}{8}"
        .Replace("{8}", dog)
        .Replace("{7}", lazy)
        .Replace("{6}", the)
        .Replace("{5}", over)
        .Replace("{4}", jumps)
        .Replace("{3}", fox)
        .Replace("{2}", brown)
        .Replace("{1}", quick)
        .Replace("{0}", the);

}

The results for 100,000 iterations are:
  • String.Format = ~146 ms
  • StringBuilder = ~54 ms
  • + Operator = ~70 ms
  • String.Concat = ~70 ms
  • String.Join = ~64 ms
  • StringWriter = ~57 ms
  • .Replace() = ~398 ms

From this we can conclude that StringBuilder provides the best performance for string concatenations done inside loops. StringWriter uses a StringBuilder internally and therefore, the performance is comparable however, take note to dispose it after used. String.Format is the slowest for string concatenation. Replace() is the slowest even though replacement is done from the end of the string to improve performance. Therefore, we need to be mindful when using it to substitute keywords in strings with values.

Just in case you get too excited and want to inherit the StringBuilder class and then provide an overloaded + operator for it, you need to know that StringBuilder is a sealed class. Oh! Bummer!

[Updated: 5-Jan-2014] Included machine specifications and .Replace() benchmark.

No comments:

Post a Comment

Popular Post