Reputation: 9733
Im maintaining thise code here which often has a pattern like the following:
StringBuilder result = new StringBuilder();
result.Append("{=" + field.Name + "={");
It seems like a waste with a lot of useless object construction when doing it like this and I want to rewrite to this:
result.Append("{=").Append(field.Name).Append("={");
Is it correct that the first version is putting more strain on the GC? Or is there some optimization in the C# compiler with string literals where concatenating string's with string literals does not create temporary objects?
Upvotes: 6
Views: 3319
Reputation: 495
I actually built and ran several tests on this. To get to the results of the test, skip to the bottom. I used this benchmarking method:
public static string BenchmarkMethod(Action method, int iterations)
{
var watch = new Stopwatch();
var results = new List<TimeSpan>(iterations);
for (int iteration = 0; iteration < iterations; iteration++)
{
watch.Start();
method();
watch.Stop();
results.Add(watch.Elapsed);
watch.Reset();
}
var builder = new StringBuilder();
builder.Append("Method benchmarked: ");
builder.Append(method.Method.ReflectedType);
builder.Append(".");
builder.AppendLine(method.Method.Name);
builder.Append("Average time in ticks: ");
builder.AppendLine(results.Average(t => t.Ticks).ToString());
return builder.ToString();
}
I wrote several small methods like these:
public static void StringConcatOperatorX8()
{
var foo = strings[0] + strings[1] + strings[2] + strings[3] + strings[4] + strings[5] + strings[6] + strings[7] + strings[8];
}
and:
public static void StringBuilderAppendsX8()
{
var builder = new StringBuilder();
builder.Append(strings[0]);
builder.Append(strings[1]);
builder.Append(strings[2]);
builder.Append(strings[3]);
builder.Append(strings[4]);
builder.Append(strings[5]);
builder.Append(strings[6]);
builder.Append(strings[7]);
builder.Append(strings[8]);
var result = builder.ToString();
}
Where strings
is a string array that contains 9, 30 letter strings
They ranged from 1 to 8 concats/appends. I originally wrote them to go from 1 to 6, using 3 letter strings, and took 10,000 samples.
UPDATE: I have been getting far more samples (1 million to be precise) and adding more letters to the strings. Apparently using StringBuilder is a total waste of performance. At 30 letters using the StringBuilder takes twice as long as using the +
operator... At the tests taking several seconds now to complete, I think I shall retire from the subject.
FINAL UPDATE: This is very important as well. The difference in using the +
operator and the StringBuilder comes in when you concat on different lines. This method actually takes longer than using the StringBuilder:
public static void StringConcatAltOperatorX8()
{
var foo = strings[0];
foo += strings[1];
foo += strings[2];
foo += strings[3];
foo += strings[4];
foo += strings[5];
foo += strings[6];
foo += strings[7];
foo += strings[8];
}
So at 30 letters per string and 1 million samples, combining all strings into a single string in the same call takes about 5.809297 ticks. Combining all strings in separate lines takes about: 12.933227 ticks. Using the StringBuilder takes 11.27558 ticks. My apologies about the length of the reply. It was something that I needed to check into myself.
Upvotes: 1
Reputation: 8699
Note that StringBuilder
is mutable, unlike String
, and is specifically designed for building strings in this manner. Although string concatenation with large strings can easily place noticable strain on the GC, this is typically not a concern if you are using StringBuilder
.
If memory consumption is a concern (as it may be with large strings) and you know the approximate length of the final result, you can use the StringBuilder(int)
constructor to suggest a start size and minimize memory reallocation as the builder grows.
Note also that you can use AppendFormat
to insert a variable into a constant string, e.g.:
result.Append("{=").Append(field.Name).Append("={");
becomes:
result.AppendFormat("{{={0}={{", field.Name);
Which produces exactly the same result, and is largely a matter of preference rather than performance.
Finally, if result
is the entire string (rather than part of a larger string being built), simply use String.Format("{{={0}={{", field.Name)
or String.Concat("{=", field.Name. "={")
rather than StringBuilder
. As MSDN notes: "Although the StringBuilder class generally offers better performance than the String class, you should not automatically replace String with StringBuilder whenever you want to manipulate strings. Performance depends on the size of the string, the amount of memory to be allocated for the new string, the system on which your app is executing, and the type of operation. You should be prepared to test your app to determine whether StringBuilder actually offers a significant performance improvement."
Upvotes: 0
Reputation: 116
I agree with all the answers, but to me you need to understand strings in C# and they way they are actually manipulated 'under the covers'
The use of a StringBuilder comes in to its own when 5 or more strings are being concatenated. This is because the compiler intrinsically converts:
string a = b + c + d + e + f;
into
r = String.Concat(new String[5] { a, b, c, d, e });
so there is an implicit overhead of array creation.
I would suggest reading the following by Eric Lippert who wrote string concatenation in C#:
http://ericlippert.com/2013/06/17/string-concatenation-behind-the-scenes-part-one/
http://ericlippert.com/2013/06/24/string-concatenation-behind-the-scenes-part-two/
Upvotes: 6