I was trying to improve the performance of SynEdit in handling files with super long lines (e.g. 50K characters). In doing so I came across an unexpected optimization that had a spectacular impact on performance.
See this innocent looking function probably written two decades ago (fCasedLine is a PChar):
function TSynCustomHighlighter.GetToken: string;
var
Len: Integer;
begin
Len := Run - fTokenPos;
SetLength(Result, Len);
if Len > 0 then
StrLCopy(@Result[1], fCasedLine + fTokenPos, Len);
end;
By measuring I found that this function (called zillions of times) was a bottleneck and replaced it with:
function TSynCustomHighlighter.GetToken: string;
var
Len: Integer;
begin
Len := Run - fTokenPos;
SetString(Result, fCasedLine + fTokenPos, Len);
end;
The impact was massive.
Both SetString and StrLCopy in x64 are implemented in pascal (not assembly) using Move. So initially I was puzzled.
However here is the reason. Look at StrLCopy:
function StrLCopy(Dest: PWideChar; const Source: PWideChar; MaxLen: Cardinal): PWideChar;
var
Len: Cardinal;
begin
Result := Dest;
Len := StrLen(Source);
if Len > MaxLen then
Len := MaxLen;
Move(Source^, Dest^, Len * SizeOf(WideChar));
Dest[Len] := #0;
end;
Can you see the problem? It is the call to StrLen(Source)! In my case the Source was very very long strings, the length of which was unnecessarily calculated the hard way. The lesson here is that you need to identify the bottlenecks by measuring. You may be in for surprises, sometimes pleasant ones,