Delphi XE2 64位字符串例程的运行时性能非常慢

hik*_*ari 25 delphi 64-bit delphi-xe2

我正在移植一些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;
Run Code Online (Sandbox Code Playgroud)

Rud*_*uis 34

目前还没有解决方案,因为它是由64位的大多数字符串例程的代码用已PUREPASCAL定义的IOW 编译的事实引起的,它是普通的Delphi,没有汇编程序,而许多重要的代码32位的字符串例程由FastCode项目和汇编程序完成.

目前,64位中没有FastCode等价物,我认为开发人员团队无论如何都会尝试消除汇编程序,特别是因为他们正在转向更多平台.

这意味着生成的代码的优化变得越来越重要.我希望宣布转移到LLVM后端将大大加快代码的大部分代码,因此纯Delphi代码不再是这样的问题了.

很抱歉,没有解决方案,但也许是一个解释.

更新

从XE4开始,相当多的FastCode例程已经取代了我在上面段落中谈到的未经优化的例程.它们通常仍然存在PUREPASCAL,但它们代表了一种很好的优化.所以情况并不像以前那么糟糕.在TStringHelper与普通字符串程序仍然显示了一些错误,并在一些极其缓慢的代码OS X(尤其是从Unicode转换为ANSI或反之而言),但Win64中的RTL的一部分似乎是好了很多.

  • @hikari:基准测试很好,如果在整个项目上完成,优化只有在profilng转出某些例程确实需要加速时才有用.Knuth已经警告过早优化. (3认同)
  • 也许我们可以在社区中建立一个新的"Fastcode64"项目. (2认同)

Arn*_*hez 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;
Run Code Online (Sandbox Code Playgroud)

RTL的"纯粹帕斯卡"版本确实是这里缓慢的原因......

请注意,与32位版本相比,FPC 64位编译器更糟糕......听起来Delphi编译器不是唯一的编译器!无论市场营销如何,64位并不意味着"更快"!有时甚至相反(例如,已知JRE在64位上较慢,并且当涉及指针大小时,将在Linux中引入新的x32模型).


pan*_*ani 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;
Run Code Online (Sandbox Code Playgroud)

Intel(R)Core(TM)2 CPU T5600 @ 1.83GHz

  • x32位:2730 ms
  • x64位:3260毫秒

英特尔(R)奔腾(R)D CPU 3.40GHz

  • x32位:2979毫秒
  • x64位:1794毫秒

展开上述循环可以加快执行速度:

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;
Run Code Online (Sandbox Code Playgroud)

Intel(R)Core(TM)2 CPU T5600 @ 1.83GHz

  • x32位:2199毫秒
  • x64位:1934毫秒

英特尔(R)奔腾(R)D CPU 3.40GHz

  • x32位:1170毫秒
  • x64位:1279毫秒

如果你也应用了Arnaud Bouchez所说的你可以让它更快.


LU *_* RD 2

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;
Run Code Online (Sandbox Code Playgroud)

基准测试结果:

        x32     x64
--------------------
hikari  1420    3963
LU RD   1029    1060
Run Code Online (Sandbox Code Playgroud)

在 32 位中,主要的速度差异是内联,它P := PAnsiChar(S);会在分配指针值之前调用外部 RTL 例程进行 nil 检查,而P := Pointer(S);只是分配指针。

观察到这里的目标是测试字符串是否是数字,然后将其转换,为什么不使用 RTL TryStrToInt(),它一步完成所有操作并处理符号、空格。

通常,在分析和优化例程时,最重要的是找到解决问题的正确方法。