Concatenating with StringBuilders vs. Strings

I would never make a statement so bold as "Use Stringbuilders to concatenate" without having a deeper understanding of the concatenation pattern.  I tend to give advice like "Many users find Stringbuilders useful for their concatenation pattens, consider using them and measure to see if they help you".  Wussy but safe :)

OK, now for some actual, no-kidding-around useful advice (I hope).

When stringbuilders are faster than regular strings it is because of string allocations and copies that didn't have to happen.  Imagine a string builder that had a buffer that was always the perfect size for the string it held... it'd be useless right because for sure the next string you appended wouldn't fit and you'd have to make a new buffer (the new exact size).   In fact, such a stringbuilder wouldn't be very much different than just a string.  So having some slop on the end is important, it's because there's a little room at the end that we can add things without having to copy.

Well, ok so how much slop?  Suppose there was some slop but that the slop wasn't usually enough to accomodate the appends that happen.  Again we'd be worse off than just strings (strings at least have no slop).  There has to be enough slop that its likely that many appends will fit within the slop, otherwise there isn't much savings.

Let me break it into 4 cases:

Big string and small appends
=>It's substantially likely that appends will fit in the slop and so they're fast, this is the best case(buffer size becomes double the string when it no longer fits so on average the slop is half the current string length) (if there are lots of small appends to a big string you win the most using stringbuilder)

Big string and big appends:
=>While the string is comparable in size (or smaller) to the appends stringbuilder won't save you much, if this continues to the point where the appends are small compared to the accumlated string you're in the good case

Small string big appends:
=> bad case, string builder will just slow you down until enough slop has built up to hold those appends, you move to "big string big appends" as you append and finally to "big string small appends" if/when the buffer becomes collossal

Small string, small appends:
=> could be ok if you had a good idea how big your string was going to get and preallocated enough so that you have sufficient slop for the appends. You might be able to do better if you just concated all the small appends together in one operation.

One last tip:  Sometimes you can get substantial savings by changing currency in the right places in your algorithm

Pattern A:

     StringBuilder sb = new StringBuilder(SuitableSize);
     // sb gets a bunch of stuff
    sb += GetMyObjectID(foo,bar);   // GetMyObjectID makes a string

Pattern B:

     StringBuilder sb = new StringBuilder(SuitableSize);
     // sb gets a bunch of stuff
    AppendMyObjectID(sb, foo,bar);   // function puts the ID directly into the buffer

PatternB has no temporary string for the return value, this *might* be better depending on the nature of the ObjectID composition/calculation. Something you'd want to measure.

Remember it's all about reducing memory traffic so the competitors are:

  • the memory in the stringbuilder, including the slop
  • the temporary strings (if any) in your algorithm with and without stringbuilders
  • the final output string (note that getting the string out of a stringbuilder doesn't cause a new alloc, the existing buffer is converted into a string and then the stringbuilder is logically empty so you don't pay this cost twice if you use stringbuilder. You do pay for the final output if you don't use stringbuilder but then you didn't have to pay for the builder up front)

It's very hard to say which is faster/smaller in general... it's all about the usage pattern.