我正在移植一些32到64位delphi的应用程序,它们进行了大量的文本处理,并注意到处理速度发生了极大的变化。例如,使用一些程序进行了一些测试,这比64位的时间已经超过200%,而不是编译为32(2000+ ms与~900相比)
这是正常的吗?
function IsStrANumber(const S: AnsiString): Boolean;
var P: PAnsiChar;
begin
Result := False;
P := PAnsiChar(S);
while P^ <> #0 do begin
if not (P^ in ['0'..'9']) then Exit;
Inc(P);
end;
Result := True;
end;
procedure TForm11.Button1Click(Sender: TObject);
Const x = '1234567890';
Var a,y,z: Integer;
begin
z := GetTickCount;
for a := 1 to 99999999 do begin
if IsStrANumber(x) then y := 0;//StrToInt(x);
end;
Caption := IntToStr(GetTickCount-z);
end;
答案 0 :(得分:34)
目前还没有解决方案,因为它是由64位中的大多数字符串例程的代码编译为PUREPASCAL
定义的事实引起的,IOW,它是普通的Delphi,没有汇编程序,而32位中许多重要字符串例程的代码由FastCode项目和汇编程序完成。
目前,64位中没有FastCode等价物,我认为开发团队无论如何都会尝试消除汇编程序,特别是因为他们正在转向更多平台。
这意味着生成的代码的优化变得越来越重要。我希望宣布转移到LLVM后端将大大加快代码的速度,因此纯Delphi代码不再是这样的问题了。
很抱歉,没有解决方案,但也许是一种解释。
从XE4开始,相当多的FastCode例程已经取代了我在上面段落中谈到的未经优化的例程。它们通常仍为PUREPASCAL
,但它们代表了一种很好的优化。所以情况并不像以前那么糟糕。 TStringHelper
和普通字符串例程仍显示 OS X 中的一些错误和一些非常慢的代码(特别是在从Unicode转换为Ansi或反之亦然的情况下),但是 Win64 部分RTL似乎好多了。
答案 1 :(得分:6)
尽量避免循环中的任何字符串分配。
在您的情况下,可能涉及x64调用约定的堆栈准备。您是否尝试将IsStrANumber
声明为inline
?
我想这会让它更快。
function IsStrANumber(P: PAnsiChar): Boolean; inline;
begin
Result := False;
if P=nil then exit;
while P^ <> #0 do
if not (P^ in ['0'..'9']) then
Exit else
Inc(P);
Result := True;
end;
procedure TForm11.Button1Click(Sender: TObject);
Const x = '1234567890';
Var a,y,z: Integer;
s: AnsiString;
begin
z := GetTickCount;
s := x;
for a := 1 to 99999999 do begin
if IsStrANumber(pointer(s)) then y := 0;//StrToInt(x);
end;
Caption := IntToStr(GetTickCount-z);
end;
RTL的“纯粹帕斯卡”版本确实是这里缓慢的原因......
请注意,与32位版本相比,FPC 64位编译器更糟糕......听起来Delphi编译器不是唯一的编译器!无论市场营销如何,64位并不意味着“更快”!有时甚至相反(例如,已知JRE在64位上较慢,并且当涉及指针大小时,将在Linux中引入新的x32 model。)
答案 2 :(得分:5)
代码可以像这样编写,具有良好的性能结果:
function IsStrANumber(const S: AnsiString): Boolean; inline;
var
P: PAnsiChar;
begin
Result := False;
P := PAnsiChar(S);
while True do
begin
case PByte(P)^ of
0: Break;
$30..$39: Inc(P);
else
Exit;
end;
end;
Result := True;
end;
Intel(R)Core(TM)2 CPU T5600 @ 1.83GHz
英特尔(R)奔腾(R)D CPU 3.40GHz
展开上述循环可以加快执行速度:
function IsStrANumber(const S: AnsiString): Boolean; inline;
type
TStrData = packed record
A: Byte;
B: Byte;
C: Byte;
D: Byte;
E: Byte;
F: Byte;
G: Byte;
H: Byte;
end;
PStrData = ^TStrData;
var
P: PStrData;
begin
Result := False;
P := PStrData(PAnsiChar(S));
while True do
begin
case P^.A of
0: Break;
$30..$39:
case P^.B of
0: Break;
$30..$39:
case P^.C of
0: Break;
$30..$39:
case P^.D of
0: Break;
$30..$39:
case P^.E of
0: Break;
$30..$39:
case P^.F of
0: Break;
$30..$39:
case P^.G of
0: Break;
$30..$39:
case P^.H of
0: Break;
$30..$39: Inc(P);
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
end;
Result := True;
end;
Intel(R)Core(TM)2 CPU T5600 @ 1.83GHz
英特尔(R)奔腾(R)D CPU 3.40GHz
如果你也应用了Arnaud Bouchez所说的,你可以更快地实现它。
答案 3 :(得分:1)
64位的好处在于地址空间,而不是速度(除非您的代码受可寻址内存的限制)。
从历史上看,这种字符操作代码在更广泛的机器上一直比较慢。确实从16位8088/8086转移到32位386.将64位寄存器放入64位寄存器是浪费存储器带宽&amp;缓存。
对于速度,您可以避免使用char变量,使用指针,使用查找表,使用位并行(在一个64位字中操作8个字符),或使用SSE / SSE2 ...指令。显然,其中一些将使您的代码CPUID依赖。此外,在调试时打开CPU窗口,并寻找编译器做“愚蠢”的事情,因为你喜欢静默字符串转换(特别是在调用时)。
您可以尝试查看FastCode库中的一些本机Pascal例程。例如。 PosEx_Sha_Pas_2虽然不如汇编程序版本快,但速度比RTL代码快(32位)。
答案 4 :(得分:1)
这是两个功能。一个只检查正数。第二个检查是负面的。并不仅限于尺寸。第二个比常规Val
快4倍。
function IsInteger1(const S: String): Boolean; overload;
var
E: Integer;
Value: Integer;
begin
Val(S, Value, E);
Result := E = 0;
end;
function IsInteger2(const S: String): Boolean; inline;
var
I: Integer;
begin
Result := False;
I := 0;
while True do
begin
case Ord(S[I+1]) of
0: Break;
$30..$39:
case Ord(S[I+2]) of
0: Break;
$30..$39:
case Ord(S[I+3]) of
0: Break;
$30..$39:
case Ord(S[I+4]) of
0: Break;
$30..$39:
case Ord(S[I+5]) of
0: Break;
$30..$39:
case Ord(S[I+6]) of
0: Break;
$30..$39:
case Ord(S[I+7]) of
0: Break;
$30..$39:
case Ord(S[I+8]) of
0: Break;
$30..$39:
case Ord(S[I+9]) of
0: Break;
$30..$39:
case Ord(S[I+10]) of
0: Break;
$30..$39: Inc(I, 10);
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
end;
Result := True;
end;
function IsInteger3(const S: String): Boolean; inline;
var
I: Integer;
begin
Result := False;
case Ord(S[1]) of
$2D,
$30 .. $39:
begin
I := 1;
while True do
case Ord(S[I + 1]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 2]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 3]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 4]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 5]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 6]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 7]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 8]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 9]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 10]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 11]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 12]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 13]) of
0:
Break;
$30 .. $39:
Inc(I, 13);
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
end;
else
Exit;
end;
Result := True;
end;
答案 5 :(得分:1)
测试p^ in ['0'..'9']
在64位中很慢。
添加了内联函数,其中包含测试下限/上限而不是in []
测试,以及对空字符串的测试。
function IsStrANumber(const S: AnsiString): Boolean; inline;
var
P: PAnsiChar;
begin
Result := False;
P := Pointer(S);
if (P = nil) then
Exit;
while P^ <> #0 do begin
if (P^ < '0') then Exit;
if (P^ > '9') then Exit;
Inc(P);
end;
Result := True;
end;
基准测试结果:
x32 x64
--------------------
hikari 1420 3963
LU RD 1029 1060
在32位中,主速度差异是内联的,P := PAnsiChar(S);
将在分配指针值之前调用外部RTL例程进行零检查,而P := Pointer(S);
只是指定指针。
观察这里的目标是测试字符串是否为数字然后转换它,
为什么不使用RTL TryStrToInt()
,它一步完成并处理标志,空白。
通常在分析和优化例程时,最重要的是找到解决问题的正确方法。