Showing posts with label cpu. Show all posts
Showing posts with label cpu. Show all posts

Friday, April 29, 2016

Is (Embarcadero) Delphi's code optimized for speed? No? Here is how to optimize string speed

The short answer is a big NO!

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