[fpc-pascal] AnsiStrings and Memory Management
Adrian Veith
adrian at veith-system.de
Wed Dec 14 17:10:38 CET 2005
Hi,
two days ago i posted a comment about my experiences porting our
database server to fpc. yesterday i tried to figure out where the speed
differences between delphi and fpc come from. At the moment i stuck a
little bit, because the results i get don't make any sense at the moment
to me.
Anyway i dropped over another problem, which might be part of the cause
of my problems. I tried to create a test base for my investigations and
created a function NumberToText which converts an integer to written
text (like NumberToText(1234) -> "one thousand two hundred thirty
four"). When I filled my database with these generated strings I found
some significant speed differences between delphi and fpc (over factor
10), which made me curious. According to your comments about my first
posting, i suspected the Memory Manager and created three different
versions of this NumberToText function and three test loops which each
calls the according NumberToText function for 0..999999. Here are the
results for fpc and delphi (w and w/o FastMM4)
Test1 uses "optimized" string concatention which avoids heap fragmentation
Test2 passes the string results using a var parameter
Test3 passes the string results as function results
Test1:
fpc: 18.8 sec - factor = 8.17
delphi (standard MM): 3.1 sec - factor = 1.34
delphi (FastMM4): 2.3 sec - factor = 1
fpc (ShortStrings): 1.25 sec - factor = 1.04
delphi (ShortStrings): 1.20 sec - factor = 1
Test2:
fpc: 45.2 sec - factor = 23.78
delphi (standard MM): 2.9 sec - factor = 1.52
delphi (FastMM4): 1.9 sec - factor = 1
fpc (ShortStrings): 0.72 sec - factor = 1
delphi (ShortStrings): 5.97 sec - factor = 8.29
Test3:
fpc: 27.1 sec - factor = 12.9
delphi (standard MM): 3.4 sec - factor = 1.61
delphi (FastMM4): 2.1 sec - factor = 1
fpc (ShortStrings): 1.27 sec - factor = 1
delphi (ShortStrings): 4.84 sec - factor = 3.81
as you can see the "optimized" version (test 1) brings great benefits
for fpc compared to the more natural approach (test 3). If someone has
optimized code for delphi usage (test 2), he will suffer with the
greatest performance hit in fpc.
Anyway these results have nothing to do with the code quality of the
compiler (look at the ShortString results), but i think it's quite an
important rtl and memory manager issue - and Delphi proves, that it a
much higher speed with AnsiStrings is possible (astonishing that
ShortStrings are slower than AnsiStrings in this example in delphi).
cheers,
Adrian Veith.
Code is below:
program Bench2;
{$APPTYPE CONSOLE}
{$H+}
uses
//{$ifndef fpc} FastMM4, {$endif}
sysutils,
Windows;
const
cTimes = 999999;
Number1: array [0..19] of string = (
'zero', 'one', 'two', 'three', 'four', 'five',
'six', 'seven', 'eight', 'nine', 'ten', 'eleven',
'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen',
'seventeen', 'eighteen', 'nineteen');
Number9: array [0..9] of string = (
'', ' one', ' two', ' three', ' four', ' five',
' six', ' seven', ' eight', ' nine');
Number10: array [0..9] of string = (
'zero', 'ten', 'twenty', 'thirty', 'fourty', 'fifty',
'sixty', 'seventy', 'eighty', 'ninety');
var
StartTick: Cardinal;
procedure StartLog(const Text: string; Count: Integer);
begin
if Count > 0 then
write(Text, ': ', Count, ' ... ')
else
write(Text, ' ... ');
StartTick:= GetTickCount;
end;
procedure EndLog(const Text: string);
begin
writeln(Text, ' done in ', (GetTickCount - StartTick) / 1000.0: 0:
3, ' sec');
end;
type
TFastStringRec = record
l: Cardinal;
s: string;
end;
procedure FS_Clear(var AFS: TFastStringRec); inline;
begin
AFS.L:= 0;
AFS.S:= '';
end;
procedure FS_Assign(var AFS: TFastStringRec; const s: string); inline;
begin
AFS.l:= Length(s);
SetLength(AFS.s, (AFS.l and not 63) + 64);
if AFS.l > 0 then
Move(s[1], AFS.s[1], AFS.l);
end;
procedure FS_Append(var AFS: TFastStringRec; const s: string); overload;
inline;
var
L, ls: Cardinal;
begin
ls:= Length(s);
if ls > 0 then begin
L:= AFS.l;
AFS.l:= L + ls;
SetLength(AFS.s, (AFS.l and not 63) + 64);
Move(s[1], AFS.s[1 + L], ls);
end;
end;
procedure FS_Append(var AFS, S: TFastStringRec); overload; inline;
var
L: Cardinal;
begin
if S.L > 0 then begin
L:= AFS.l;
AFS.l:= L + S.L;
SetLength(AFS.s, (AFS.l and not 63) + 64);
Move(S.S[1], AFS.S[1 + L], S.L);
end;
end;
function FS_ToStr(var AFS: TFastStringRec): string; inline;
begin
if AFS.L > 0 then begin
SetLength(Result, AFS.L);
Move(AFS.S[1], Result[1], AFS.L);
end else
Result:= '';
end;
procedure NumberToText_V1(out s: string; n: Integer);
procedure TensToText(var s: TFastStringRec; dig: Integer);
var
x: Integer;
begin
if dig > 0 then begin
if dig >= 20 then begin
x:= dig mod 10;
FS_Assign(s, Number10[dig div 10]);
if x <> 0 then
FS_Append(s, Number9[x]);
end else begin
FS_Assign(s, Number1[dig]);
end;
end else
FS_Clear(s);
end;
procedure HundredsToText(var s: TFastStringRec; dig: Integer);
var
h, t: Integer;
s1: TFastStringRec;
begin
if dig > 0 then begin
t:= dig mod 100;
h:= dig div 100;
if h > 0 then begin
TensToText(s, h);
if t > 0 then begin
FS_Append(s, ' houndred ');
TensToText(s1, t);
FS_Append(s, s1);
end else
FS_Append(s, ' houndred');
end else
TensToText(s, t);
end else
FS_Clear(s);
end;
var
dig, h: Integer;
s0, s1: TFastStringRec;
begin
if n > 0 then begin
dig:= n div 1000;
h:= n mod 1000;
if dig > 0 then begin
HundredsToText(s0, dig);
if h > 0 then begin
FS_Append(s0, ' thousand ');
HundredsToText(s1, h);
FS_Append(s0, s1);
end else
FS_Append(s0, ' thousand');
end else
HundredsToText(s0, h);
s:= FS_ToStr(s0);
end else
s:= Number1[0];
end;
procedure NumberToText_V2(out s: string; n: Integer);
procedure TensToText(out s: string; dig: Integer);
var
x: Integer;
begin
if dig > 0 then begin
if dig >= 20 then begin
x:= dig mod 10;
if x <> 0 then begin
s:= Number10[dig div 10] + Number9[x]
end else
s:= Number10[dig div 10];
end else begin
s:= Number1[dig];
end;
end else
s:= '';
end;
procedure HundredsToText(out s: string; dig: Integer);
var
h, t: Integer;
s1: string;
begin
if dig > 0 then begin
t:= dig mod 100;
h:= dig div 100;
if h > 0 then begin
TensToText(s, h);
if t > 0 then begin
s:= s + ' houndred ';
TensToText(s1, t);
s:= s + s1;
end else
s:= s + ' houndred';
end else
TensToText(s, t);
end else
s:= '';
end;
var
dig, h: Integer;
s1: string;
begin
if n > 0 then begin
dig:= n div 1000;
h:= n mod 1000;
if dig > 0 then begin
HundredsToText(s, dig);
if h > 0 then begin
s:= s + ' thousand ';
HundredsToText(s1, h);
s:= s + s1;
end else
s:= s + ' thousand';
end else
HundredsToText(s, h);
end else
s:= Number1[0];
end;
function NumberToText_V3(n: Integer): string;
function TensToText(dig: Integer): string;
var
x: Integer;
begin
if dig > 0 then begin
if dig >= 20 then begin
x:= dig mod 10;
if x <> 0 then begin
Result:= Number10[dig div 10] + Number9[x]
end else
Result:= Number10[dig div 10];
end else begin
Result:= Number1[dig];
end;
end else
Result:= '';
end;
function HundredsToText(dig: Integer): string;
var
h, t: Integer;
begin
if dig > 0 then begin
t:= dig mod 100;
h:= dig div 100;
if h > 0 then begin
if t > 0 then
Result:= TensToText(h) + ' houndred ' + TensToText(t)
else
Result:= TensToText(h) + ' houndred';
end else
Result:= TensToText(t);
end else
Result:= '';
end;
var
dig, h: Integer;
begin
if n > 0 then begin
dig:= n div 1000;
h:= n mod 1000;
if dig > 0 then begin
if h > 0 then
Result:= HundredsToText(dig) + ' thousand ' + HundredsToText(h)
else
Result:= HundredsToText(dig) + ' thousand';
end else
Result:= HundredsToText(h);
end else
Result:= Number1[0];
end;
procedure Test1;
var
i: Integer;
s: string;
begin
StartLog('Test 1', cTimes + 1);
for i:= 0 to cTimes do begin
NumberToText_V1(s, i);
end;
EndLog('');
end;
procedure Test2;
var
i: Integer;
s: string;
begin
StartLog('Test 2', cTimes + 1);
for i:= 0 to cTimes do begin
NumberToText_V2(s, i);
end;
EndLog('');
end;
procedure Test3;
var
i: Integer;
s: string;
begin
StartLog('Test 3', cTimes + 1);
for i:= 0 to cTimes do begin
s:= NumberToText_V3(i);
end;
EndLog('');
end;
procedure Benchmark;
begin
Test1;
Test2;
Test3;
end;
begin
Benchmark;
Readln;
end.
More information about the fpc-pascal
mailing list