No the long answer:
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
- 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:
https://www.delphitools.info/2013/10/30/efficient-string-building-in-delphi/2/