Ještě malá poznámka k inline optimalizacím. Tentokrát na ni upozornil přímo Allen Bauer a dokud na ni neupozornil on, tak jsem si tuto souvislost neuvědomil.
Mějme následující kvalitní kód:
program InlineTest;
{$APPTYPE CONSOLE}
{$O+}
uses
SysUtils;
var
s : string;
begin
s:= 'Test';
writeln(s);
writeln(Length(s));
end.
Interně kompilátor pro length volá "magic" funkci, v případě Delphi 5 je to _LStrLen v system.pas, v případě Delphi 2010 nebo XE (ale i předchozích) je to _UStrLen také v system.pas.
Pro Delphi 5 je to
function _LStrLen{str: AnsiString}: Longint;
asm
{ -> EAX str }
TEST EAX,EAX
JE @@done
MOV EAX,[EAX-skew].StrRec.length;
@@done:
end;
Pro Delphi XE je to
function _UStrLen(const S: UnicodeString): Integer;
{$IF defined(CPUX64)}
begin
Result := 0;
if Pointer(S) <> nil then // PStrRec should be used here, but
Result := PLongInt(NativeInt(S) - 4)^; // a private symbol can't be inlined
end;
{$ELSE}
begin
Result := Longint(S);
if Result <> 0 then // PStrRec should be used here, but
Result := PLongint(Result - 4)^; // a private symbol can't be inlined
end;
{$IFEND}
Hlavní rozdíl je v tom, že v Delphi 5 je to v assembleru, kdežto v Delphi XE je to v Pascalu. Myslel jsem si původně, že je to k vůli 64bit.
OK, takže implementace v Delphi 2007 (tj. neunicode verze s inline)
function _LStrLen(const S: AnsiString): Longint;
begin
Result := Longint(S);
if Result <> 0 then // PStrRec should be used here, but
Result := PLongint(Result - 4)^; // a private symbol can't be inlined
end;
Od Delphi 2007 jsou patřičné funkce označeny jako inline, tj. kompilátor může vynechat volání a kód začlenit přímo.
Delphi 5
004086F8 E8BFAEFFFF call @Write0LString
004086FD E8C5BEFFFF call @WriteLn
00408702 E8F99EFFFF call @_IOTest
Project1.dpr.11: writeln(Length(s));
00408707 A100A64000 mov eax,[s]
0040870C E85FADFFFF call @LStrLen
00408711 8BD0 mov edx,eax
00408713 A1EC924000 mov eax,[$004092ec]
00408718 E8BFA0FFFF call @Write0Long
0040871D E8A5BEFFFF call @WriteLn
00408722 E8D99EFFFF call @_IOTest
Delphi XE bez inline
004111E0 E87B33FFFF call @Write0UString
004111E5 E8EE33FFFF call @WriteLn
004111EA E8012BFFFF call @_IOTest
004111EF A1787E4100 mov eax,[$00417e78]
004111F4 E8774DFFFF call @UStrLen
004111F9 8BD0 mov edx,eax
004111FB A1EC2C4100 mov eax,[$00412cec]
00411200 E80B33FFFF call @Write0Long
00411205 E8CE33FFFF call @WriteLn
0041120A E8E12AFFFF call @_IOTest
Delphi XE s inline
004111E1 E87A33FFFF call @Write0UString
004111E6 E8ED33FFFF call @WriteLn
004111EB E8002BFFFF call @_IOTest
004111F0 8B1D787E4100 mov ebx,[$00417e78]
004111F6 85DB test ebx,ebx
004111F8 7405 jz $004111ff
004111FA 83EB04 sub ebx,$04
004111FD 8B1B mov ebx,[ebx]
004111FF A1EC2C4100 mov eax,[$00412cec]
00411204 8BD3 mov edx,ebx
00411206 E80533FFFF call @Write0Long
0041120B E8C833FFFF call @WriteLn
00411210 E8DB2AFFFF call @_IOTest
Tím, že inlinovaná funkce je psána přímo v Object Pascalu, je umožněno kompilátoru při inline optimalizaci používat i jiné registry, než by původně programátor zamýšlel. V případě, že by inline funkce byla psaná v assembleru (jako v Delphi 5), musel by kompilátor před voláním šachovat s registry, což určitě není optimální.
Závěr: pokud opravdu nemáte velký důvod pro assembler, tak to nedělejte :-)