TStringBuilder – Optimizing Delphi’s strings for speed

Today I needed a function that will wrap a string (a contiguous block of characters with no spaces) after 80 characters. Not only that I have found SysUtils.WrapText unsuitable (it can only wrap text IF the text contains spaces) but it is also terrible slow.

So I build my own function:

function WrapString(CONST s: string; RowLength: integer): string;
VAR i, Row: Integer;
Begin
Row:= 0;
Result:= ”;
for i:= 1 TO Length(s) DO
begin
inc(Row);
Result:= Result+ s[i];
if Row >= RowLength then
begin
Result:= Result+ CRLF; 

     Row:= 0;
end;
end;
End;

Works nice but is is also slow. If you look into the code the problem is Result:= Result+ CRLF . It involves too many memory allocations.

Solution. The solution is to pre-allocate space for the result.
For this I created a new class TCStringBuilder:

TYPE
TCStringBuilder = class(TObject)
private
s: string;
CurBuffLen, BuffPos: Integer;
public
BuffSize: Integer;
constructor Create(aBuffSize: Integer= 10*Kb);
procedure AddChar(Ch: Char);
procedure AddEnter;

function  AsText: string;
procedure Clear;
end;

IMPLEMENTATION

constructor TCStringBuilder.Create(aBuffSize: Integer= 10*Kb);
begin
BuffSize:= aBuffSize;
Clear;
end;

procedure TCStringBuilder.Clear;
begin
BuffPos:= 1;
CurBuffLen:= 0;
s:= ”;
end;

function TCStringBuilder.AsText: string;
begin
SetLength(s, BuffPos-1);                    { Cut down the prealocated buffer that we haven’t used }
Result:= s;
end;

procedure TCStringBuilder.AddChar(Ch: Char);
begin
if BuffPos > CurBuffLen then
begin
SetLength(s, CurBuffLen+ BuffSize);
CurBuffLen:= Length(s)
end;

s[BuffPos]:= Ch;
Inc(BuffPos);
end;

procedure TCStringBuilder.AddEnter;
begin
if BuffPos+1 > CurBuffLen then    { +1 because we enter two characters into the string instead of 1 }
begin
SetLength(s, CurBuffLen+ BuffSize);
CurBuffLen:= Length(s)
end;

s[BuffPos  ]:= CR;
s[BuffPos+1]:= LF;
Inc(BuffPos, 2);
end;

Speed test:

  • 500x loop
  • test file: TesterForm.pas 2.7K
  • wrap after 20 chars

Speed test results:

  •   484ms  SysUtils.WrapText – unbuffered
  •   5788ms WrapString        – unbuffered (Result:= Result+ s[i])
  •   31ms   WrapString        – buffered (using cStrBuilder)

I used a buffer of 10K. but the ideal buffer size would be the size of the input text plus 3%.

Please let me know if you can further improve this. Enjoy.

____

Further reading:

Efficient String Building in Delphi

Find the code in my Delphi LightSaber library on GitHub.

 

04.2016

Leave a Comment

Scroll to Top