Previous: Presenting The Contenders
The Mighty Have Fallen
Here are the times per-iteration, lower is better, for 10, 100, 1000 and 10000 loop iterations. Loop execution time is normalized to per-iteration times (ie. loop run times were divided by 10 to the 10 iterations loop, 100 for the 100 iterations loop, etc.).
1 – The first thing that is obvious is that if you’re using TStringStream, well, stop. You shouldn’t. Or you need a very, very good reason to do so.
2 – The second is that if you changed your trivial, KISS string concatenations for
TStringBuilder, well… take that hanky. Maybe you got lucky, and are in one of the cases where TStringBuilder is okay? Okay, maybe not.
3 – Trivial concatenation is simple, readable, and scales well. We’re not in .Net or Java La-La-Land where a simple string concatenation gone wrong can throw you in deep pits of swapping hell. This is why I like the Delphi String type.
Format does not seem to be shining, but in addition to string concatenation and integer conversion, it also has to parse the format string, and given the flexibility, the performance is not bad at all.
TWriteOnlyBlockStream is doing fine with a decent lead, but its real-magic becomes visible in multi-threaded scenarios (which this benchmark isn’t).
10 thoughts on “Efficient String Building in Delphi”
TTextWriter from Arnaud Bouchez doesn’t have these problems AFAIK
Yes TTextWriter is fast, however it’s dealing with utf-8, so wouldn’t be “fair” with other methods, and the performance is very close to TWriteOnlyBlockStream anyway (slightly behind in the 10 and 10000 tests, slightly ahead in the 100 and 1000 tests, but the deltas are very minor in all cases).
For the fun of it, you could try to see how this one performs:
Or even this version, and using the AppendResidentBuf for the constant strings.
HVStringData performs half-way between trivial string concatenation and TStringBuilder. AFAICT it uses a strategy similar to TWriteOnlyBlockStream, but with a buffer size (Chars) way too small, so it ends up bottle-necking on reference counting (UStrClr) and the memory manager.
I suspect in multithread, both TWriteOnlyBlockStream and TTextWriter.Add(aInteger) would shine, since neither the two do allocate any temporary string.
What make TWriteOnlyBlockStream a bit more efficient is the fact that it allocates memory blocks via a linked list.
But on the other hand, TWriteOnlyBlockStream will enforce all data to fit in memory, whereas TTextWriter is more versatile, and is able to flush its content by chunk into any external TStream – e.g. a file. For instance, we use TTextWriter for our logging features, while I would not use TWriteOnlyBlockStream for it. TTextWriter is also the base class for all our JSON generation. And I like very much the TTextWriter.CancelLastChar method: pretty useful you want to ignore a trailing ‘,’ in your content. 🙂
Yep, the lack of CanceLastChar is a limitation. But data isn’t really enforced in memory, since it’s “write-only”, it can be flushed at any time to another stream or disk (the size then becomes a partial size though).
And yes, for integers and multi-threaded scenarios, both outshine their competitors by as many CPU cores as they can grab 😉
Also TWOBS only has an Int64 converter (since it was made for DWScript which only deals in Int64), which is why TTextWriter comes slightly on top for the 100 & 1000 iterations tests.
What time measurements are you using? Using a TStringStream, 100,000 iterations consistently takes about 50 ms on my not-very-new i7 before doing any normalisation.
for i := 1 to aCount do
lStream.WriteString(#13#10’Eating apple #’ + IntToStr(i));
Result := lStream.DataString;
@Bruce Timings are the minimum run times of 15 runs, each run being for 100k iterations (so the 10k test is executed 10 times). Using a single WriteString (as in your snippet) rather than two (as in mine) is about 10% faster here, and is in the 50 ms range as well.
Note that your snippet cuts the TStringStream stress in half, and leverages regular String concatenation instead for the other half.
The concatenation was an oversight.
I’ll follow up by e-mail for some more details.
Comments are closed.