What is the basic difference between IntToStr()
and Integer.ToString()
when converting an Integer
to a string
. Which one is faster?
var
VarInt: integer;
VarStr: string;
begin
VarInt := 5;
VarStr := IntToStr(VarInt);
VarStr := VarInt.ToString;
end;
Disclaimer: The following text contains details that only apply to Delphi 10.2.1 (and also 10.2.2) which seems to have made something worse about inlining and RVO:
The code produced by the compiler indeed differs (regardless the compiler version) as you can easily see when looking into the disassembly window.
Lets take this routine:
procedure Main;
var
i: Integer;
s: string;
begin
i := 0;
s := IntToStr(i);
s := i.ToString;
end;
Now let's run this and look into the disassembly window to check the code the compiler produced:
This is what you got with Delphi 10.1:
Project1.dpr.14: s := IntToStr(i);
00419810 8D55F8 lea edx,[ebp-$08]
00419813 8B45FC mov eax,[ebp-$04]
00419816 E80DA4FFFF call IntToStr
Project1.dpr.15: s := i.ToString;
0041981B 8D55F4 lea edx,[ebp-$0c]
0041981E 8B45FC mov eax,[ebp-$04]
00419821 E802A4FFFF call IntToStr
00419826 8D45F8 lea eax,[ebp-$08]
00419829 8B55F4 mov edx,[ebp-$0c]
0041982C E843D2FEFF call @UStrLAsg
And this is what you get with 10.2.1 (and also 10.2.2):
Project1.dpr.14: s := IntToStr(i);
00419B04 8D55F8 lea edx,[ebp-$08]
00419B07 8B45FC mov eax,[ebp-$04]
00419B0A E8C5A2FFFF call IntToStr
Project1.dpr.15: s := i.ToString;
00419B0F 33C0 xor eax,eax
00419B11 55 push ebp
00419B12 68499B4100 push $00419b49
00419B17 64FF30 push dword ptr fs:[eax]
00419B1A 648920 mov fs:[eax],esp
00419B1D 8D55F4 lea edx,[ebp-$0c]
00419B20 8B45FC mov eax,[ebp-$04]
00419B23 E8ACA2FFFF call IntToStr
00419B28 8D45F8 lea eax,[ebp-$08]
00419B2B 8B55F4 mov edx,[ebp-$0c]
00419B2E E805D0FEFF call @UStrLAsg
00419B33 33C0 xor eax,eax
00419B35 5A pop edx
00419B36 59 pop ecx
00419B37 59 pop ecx
00419B38 648910 mov fs:[eax],edx
00419B3B 68509B4100 push $00419b50
00419B40 8D45F4 lea eax,[ebp-$0c]
00419B43 E8D4CCFEFF call @UStrClr
00419B48 C3 ret
00419B49 E9CEC3FEFF jmp @HandleFinally
00419B4E EBF0 jmp $00419b40
Now the million dollar question is, what is all that extra instructions there?!
The extra instructions that you can see in both compilers is the result of missing so called return value optimization. As you might know the compiler treats results of functions that are of a managed type (like string) as hidden var parameter. Now when the compiler does the inlining it does not eliminate this parameter and directly passes the s
variable to the IntToStr
as it does when directly calling it. It rather reserves a temporary variable that it uses to pass to the IntToStr
and then after that assigns that variable to s
(that's the call @UStrLAsg
you see there 3 lines after the IntToStr
call).
As I mentioned above there seems to be a regression in 10.2 or 10.2.1 where they changed something about temporary variable cleanup right after the inlined call (that's the extra instructions before and after that).
Reported as RSP-19439.
To be continued ...
There is no difference.
Int.ToString
is defined like so:
function TIntHelper.ToString: string; inline;
begin
Result := IntToStr(Self);
end;
Because it is inlined, it will just translate to IntToStr(Int)
.
The var.action
methods were added to bring make the Runtime Library (RTL) more hospitable to Java and C# programmers. Which is especially relevant on the mobile platforms where people are likely to have been exposed to Java.
A major benefit of doing it this way is that the functions are much more discoverable. You can just type VarInt.
and the auto-completion will show you all available options. If you don't know about IntToStr
already it's kind of hard to find it.