Alex James
Alex James

Reputation: 696

Replacing string assignment by PChar operation

I have a puzzling result that I'm struggling to understand.

I've been attempting to improve the speed of this routine

function TStringRecord.GetWord: String;
begin
  // return the next word in Input
  Result := '';

  while (PC^ <> #$00) and not PC^.IsLetter do begin
    inc(FPC);
  end;

  while (PC^ <> #$00) and PC^.IsLetter do begin
    Result := Result + PC^;
    inc(FPC);
  end;
end;

by replacing the Result := Result + PC^ by a pointer-based operation. This is my attempt:

function TStringRecord.GetWord2: String;
var
  Len : Integer;
  StartPC,
  DestPC : PChar;
begin
  // return the next word in Input
  Result := '';

  while (PC^ <> #$00) and not PC^.IsLetter do begin
    inc(FPC);
  end;

  Len := Length(Input);
  SetLength(Result, Len);
  StartPC := PChar(Result);
  DestPC := PChar(Result);
  while (PC^ <> #$00) and PC^.IsLetter do begin
    WStrPLCopy(DestPC, PC, 1);
    inc(FPC);
    inc(DestPC);
  end;
  SetLength(Result, DestPC - StartPC);
end;

According to my line profiler, WStrPLCopy(DestPC, PC, 1) takes 50 times longer than Result := Result + PC^. As far as I can tell, this is because on entry to WStrPLCopy there is a call to _WStrFromPWChar which seems to copy many more characters than the one necessary. How can I avoid this, or can someone suggest an alternative PChar-based method?

The remainder of my code is below:

TStringRecord = record
private
  FPC: PChar;
  FInput: String;
  procedure SetInput(const Value: String);
public
  function NextWord : String;
  function NextWord2 : String;
  property Input : String read FInput write SetInput;
  property PC : PChar read FPC;
end;

procedure TStringRecord.SetInput(const Value: String);
begin
  FInput := Value;
  FPC := PChar(Input);
end;

Upvotes: 2

Views: 449

Answers (1)

Arnaud Bouchez
Arnaud Bouchez

Reputation: 43053

This is how I would write it:

function TStringRecord.GetWord: String;
var beg: PChar;
begin
  // return the next word in Input
  while (FPC^ <> #0) and not FPC^.IsLetter do 
    inc(FPC);
  beg := FPC;
  while (FPC^ <> #0) and FPC^.IsLetter do 
    inc(FPC);
  SetString(result, beg, FPC-beg);
end;

With this, code is very readable, and you have a single memory allocation, and I guess you could not write anything faster (but by inlining PC^.IsLetter, which is the only call to an external piece of code).

Upvotes: 4

Related Questions