6

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?

Abbas
  • 14,186
  • 6
  • 41
  • 72
Serve Laurijssen
  • 9,266
  • 5
  • 45
  • 98
  • 6
    The compiler is smart enough to generate a call to one of the String.Concat() overloads. If you are using a StringBuilder for just this concatenation then you're actually worse off. – Hans Passant May 26 '14 at 13:45
  • 1
    Is this all you're using the StringBuilder for? If so, this is wasteful. Just use string concatenation. – Ant P May 26 '14 at 13:46
  • @AntP: i guess that OP has left out the loop and he just wants to know if there's a difference between the mix of string concatenation and `StringBuilder.Append` or a `Append` only version. – Tim Schmelter May 26 '14 at 13:53
  • I'm not sure, but I heard that if the number of objects you are concatenating is fixed at compile-time, it is well-optimized and doesn't waste memory. Second, StringBuilder may waste more memory and CPU time than short strings concatenation, so it's preferable to concat few short strings than constructing a StringBuilder for them – Sasha May 26 '14 at 13:54
  • @TimSchmelter That was my initial assumption, too, but then I realised that it might be an incorrect one. – Ant P May 26 '14 at 13:55
  • @AntP: what makes me think that this assumption was correct is that he mentions _"a lot of useless object construction"_. So not only three. Also `field` seems to be a loop variable like a field in a line of a text-file. Then three appends are more efficient than using `String.Concat` because `field.Name` is not a literal. – Tim Schmelter May 26 '14 at 14:03
  • 1
    "It. Just. Doesn't. Matter!" (Almost all of the time) http://blog.codinghorror.com/the-sad-tragedy-of-micro-optimization-theater/ – Jonas Elfström May 26 '14 at 14:53
  • @JonasElfström That post hits it right on the head. Do what is the easiest to understand, don't be a beginner making beginner mistakes, and let someone else do the testing. – bubbinator May 26 '14 at 16:07
  • yes its done within a loop and there's more concats than that one line. In most application I would not care about this kind of micro optimizations but in this one, it must have a response time of < 1 seconds and I try to avoid (full) garbage collections as much as possible. On top of that, if this type of micro-optimized code comes naturally out of your hands it doesnt hurt anyone – Serve Laurijssen May 27 '14 at 13:10
  • If you aren't *measuring* the costs of your choices then you are *guessing* about what is more expensive or less expensive. **Set a performance goal -- it sounds like you have already done this -- get a speed and memory profiler, try it multiple ways, and see which one meets your goals**. If all of them meet your goals then choose the simplest; if none of them meet your goals then it doesn't matter which you pick. – Eric Lippert May 27 '14 at 18:13

3 Answers3

6

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/

Stuart
  • 116
  • 2
  • I didnt know that so thanks. Although by looking at the Concat overloads this only happens when concatenating more than 4 strings at once – Serve Laurijssen May 27 '14 at 06:25
1

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.

bubbinator
  • 487
  • 2
  • 7
  • Correct, that is the only use case for `StringBuilder`, when the concatenation needs to happen over multiple lines or in a loop. Otherwise, you just use the binary addition operator. – Cody Gray - on strike May 27 '14 at 07:37
0

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."

drf
  • 8,461
  • 32
  • 50