I was wondering about StringBuilder and I\'ve got a question that I was hoping the community would be able to explain.
Let\'s just forget about code readability, which o
casperOne is correct. Once you reach a certain threshold, the Append()
method becomes slower than AppendFormat()
. Here are the different lengths and elapsed ticks of 100,000 iterations of each method:
Append() - 50900
AppendFormat() - 126826
Append() - 1241938
AppendFormat() - 1337396
Append() - 12482051
AppendFormat() - 12740862
Append() - 61029875
AppendFormat() - 60483914
When strings with a length near 20,000 are introduced, the AppendFormat()
function will slightly outperform Append()
.
Why does this happen? See casperOne's answer.
Edit:
I reran each test individually under Release configuration and updated the results.
StringBuilder
also has cascaded appends: Append()
returns the StringBuilder
itself, so you can write your code like this:
StringBuilder sb = new StringBuilder();
sb.Append(string1)
.Append("----")
.Append(string2);
Clean, and it generates less IL-code (although that's really a micro-optimization).
1 should be faster becuase it's simply appending the strings whereas 2 has to create a string based on a format and then append the string. So there's an extra step in there.
Of course profile to know for sure in each case.
That said, I think in general it will be the former because you aren't repeatedly parsing the format string.
However, the difference would be very small. To the point that you really should consider using AppendFormat
in most cases anyway.
I'd assume it was the call that did the least amount of work. Append just concatenates strings, where AppendFormat is doing string substitutions. Of course these days, you never can tell...
casperOne is entirely accurate that it depends on the data. However, suppose you're writing this as a class library for 3rd parties to consume - which would you use?
One option would be to get the best of both worlds - work out how much data you're actually going to have to append, and then use StringBuilder.EnsureCapacity to make sure we only need a single buffer resize.
If I weren't too bothered though, I'd use Append
x3 - it seems "more likely" to be faster, as parsing the string format tokens on every call is clearly make-work.
Note that I've asked the BCL team for a sort of "cached formatter" which we could create using a format string and then re-use repeatedly. It's crazy that the framework has to parse the format string each time it's used.
EDIT: Okay, I've edited John's code somewhat for flexibility and added an "AppendWithCapacity" which just works out the necessary capacity first. Here are the results for the different lengths - for length 1 I used 1,000,000 iterations; for all other lengths I used 100,000. (This was just to get sensible running times.) All times are in millis.
Unfortunately tables don't really work in SO. The lengths were 1, 1000, 10000, 20000
Times:
So as it happened, I never saw AppendFormat beat Append - but I did see AppendWithCapacity win by a very substantial margin.
Here's the full code:
using System;
using System.Diagnostics;
using System.Text;
public class StringBuilderTest
{
static void Append(string string1, string string2)
{
StringBuilder sb = new StringBuilder();
sb.Append(string1);
sb.Append("----");
sb.Append(string2);
}
static void AppendWithCapacity(string string1, string string2)
{
int capacity = string1.Length + string2.Length + 4;
StringBuilder sb = new StringBuilder(capacity);
sb.Append(string1);
sb.Append("----");
sb.Append(string2);
}
static void AppendFormat(string string1, string string2)
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat("{0}----{1}", string1, string2);
}
static void Main(string[] args)
{
int size = int.Parse(args[0]);
int iterations = int.Parse(args[1]);
string method = args[2];
Action<string,string> action;
switch (method)
{
case "Append": action = Append; break;
case "AppendWithCapacity": action = AppendWithCapacity; break;
case "AppendFormat": action = AppendFormat; break;
default: throw new ArgumentException();
}
string string1 = new string('x', size);
string string2 = new string('y', size);
// Make sure it's JITted
action(string1, string2);
GC.Collect();
Stopwatch sw = Stopwatch.StartNew();
for (int i=0; i < iterations; i++)
{
action(string1, string2);
}
sw.Stop();
Console.WriteLine("Time: {0}ms", (int) sw.ElapsedMilliseconds);
}
}