I’m working on a pre-compiler for Delphi and as a result of that have to do a lot of string concatenations.

It’s reasonably fast to use plain old string concatenation, but using TStringBuilder turned out to be about twice as fast.

Emba is not known for writing efficient RTL code so I was wondering if we could speed up the generation (even) further.

The answer is FastStringBuilder , a drop-in replacement.

Building a large string by adding lots of small strings is always faster using FastStringBuilder.TStringBuilder . Note that the Win64 target has the lion share of optimizations. I plan to add additional Win32 optimizations in due course.

The degree of speed-up differs depending on the use case. I’m still working on a faster alternative for the calls to System.Move . Move can deal with overlapping moves, which I don’t need to worry about here.

You can download FastStringBuilder from https://github.com/JBontes/FastCode/blob/master/FastStringBuilder.pas

Here’s how to use the unit:

unit MyTest; interface uses SysUtils, FastStringBuilder; //Make sure FastStringBuilder is used after SysUtils! type TExample = class private SB: TStringBuilder; public function Test: integer; end; implementation function TExample.Test; var i: integer; begin if not(Assigned(SB)) then SB:= TStringBuilder.Create; for i:= 0 to 100 * 1000 do SB.Append('apple'); end;

Timings for a number of additions:

StringBuilder FastStringBuilder String concat string 1531 1203 1781 char 188 47 1734 integer 1563 562 1656

Note that the test timing are somewhat worse case; if I shorten the test string from 'appleappleapple to 'apple' FastStringBuilder speeds up to 562, String-Concat and StringBuilder stay more or less the same.

The code used to obtain the above timings:

var SSB: SysUtils.TStringBuilder; FSB: TStringBuilder; S: string; LTick: cardinal; const TestCount = 100 * 1000 * 100; procedure Timings; var Test: string; C: Char; i,j: integer; begin j:= 1457454; Test:= 'appleappleapple'; SSB:= SysUtils.TStringBuilder.Create; FSB:= TStringBuilder.Create; LTick:= TThread.GetTickCount; for i:= 0 to TestCount do begin SSB.Append(Test); end; WriteLn('Standard SB: ',TThread.GetTickCount- LTick, 'ms'); LTick:= TThread.GetTickCount; for i:= 0 to TestCount do begin FSB.Append(Test); end; Writeln('Fast SB: ',TThread.GetTickCount- LTick, 'ms'); LTick:= TThread.GetTickCount; for i:= 0 to TestCount do begin S:= S + Test; end; Assert(S ''); Writeln('string: ',TThread.GetTickCount- LTick, 'ms'); WriteLn('Char'); LTick:= TThread.GetTickCount; for i:= 0 to TestCount do begin SSB.Append(C); end; Writeln('Standard SB: ',TThread.GetTickCount- LTick, 'ms'); LTick:= TThread.GetTickCount; for i:= 0 to TestCount do begin FSB.Append(C); end; Writeln('Fast SB: ',TThread.GetTickCount- LTick, 'ms'); LTick:= TThread.GetTickCount; for i:= 0 to TestCount do begin S:= S + C; end; Assert(S ''); Writeln('String: ',TThread.GetTickCount- LTick, 'ms'); WriteLn('Integer'); LTick:= TThread.GetTickCount; for i:= 0 to TestCount do begin SSB.Append(i); end; Writeln('Standard SB: ',TThread.GetTickCount- LTick, 'ms'); LTick:= TThread.GetTickCount; for i:= 0 to TestCount do begin FSB.Append(i); end; Writeln('Fast SB: ',TThread.GetTickCount- LTick, 'ms'); LTick:= TThread.GetTickCount; for i:= 0 to TestCount do begin S:= S + i.ToString; end; Assert(S ''); Writeln('String: ',TThread.GetTickCount- LTick, 'ms'); SSB.Free; FSB.Free ReadLn; end;