Why are Plain Delphi Strings doing so Well?
Should not simple String concatenation fail like Schlemiel the Painter?
Two things: Copy-On-Write and FastMM. Both work hand in hand there.
Copy-On-Write is what makes Delphi String different, it gives them both the advantages of immutability and those of mutability, which are leveraged here.
FastMM on the other hand performs automatic speculative allocation for buffers that grow, and mutable strings that are appended to are just buffers that grow.
Why is TStringBuilder slow?
Some will say that is because it was just ripped from .Net or Java, where constraints are difference, and where it’s used as a trick to work around String immutability. While there is some truth is that, TWriteOnlyBlockStream is using a structurally similar approach, yet leads comfortably.
No, one of the main reasons is because the implementer of TStringBuilder ran afoul of implicit workloads:
- most of the Append overloads work by creating a small local string and appending it, which means an exception frame, a string allocation and de-allocation each time (in contrast, the trivial implementations reuse the same local variable each time)
- Append(String) looks innocent, but is choke-full of implicitness, just look at it in the CPU view.
Last but not least, the SetLength implementation, which is invoked from each Append call, is just not very efficient. For instance it does two checks on the value where one guard check could suffice, and it systematically enters an exception block that is only useful when growing the buffer.
So even if you pre-allocate the buffer, you still pay for most of the overhead of TStringBuilder, which is why pre-allocating doesn’t have a magical effect. Buffer growth isn’t the bottleneck (and would not be under FastMM anyway).
Check the followup article: Going Multi-Threaded.