【问题标题】:Delphi XE2 64-bit extremely slow runtime performance on string routinesDelphi XE2 64 位在字符串例程上运行时性能极慢
【发布时间】:2012-06-30 21:44:17
【问题描述】:

我正在将一些应用程序从 32 位移植到 64 位 delphi,这些应用程序执行大量文本处理,并注意到处理速度发生了极大的变化。使用一些程序进行了一些测试,例如,在 64 位中这已经比编译到 32 位花费了 200% 以上的时间(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;

【问题讨论】:

  • 如果你使用StrToInt(x),你会看到同样的问题吗?
  • 您是否进行了其他一些不涉及低级指针操作的测试?
  • 是的,只在循环中执行 StrToInt:2246ms 与 1498ms (64/32)。除此之外,我移植的一个大型应用程序有一个测试处理速度的基准(它将一些文本通过一个包含数百个字符串操作子例程的非常长的过程),64 位应用程序的处理时间几乎是两倍。
  • 我想说做一些测试,专门将您的变量转换为 in64 或 longint。
  • Int64/NativeInt 仍然没有区别。

标签: delphi 64-bit delphi-xe2


【解决方案1】:

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

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

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

很抱歉,没有解决方案,但也许可以解释一下。

更新

从 XE4 开始,相当多的 FastCode 例程已经取代了我在上面段落中谈到的未优化的例程。它们通常仍然是PUREPASCAL,但它们代表了一个很好的优化。所以情况没有以前那么糟糕了。 TStringHelper 和纯字符串例程仍然会在 OS X 中显示一些错误和一些极其缓慢的代码(尤其是涉及从 Unicode 到 Ansi 或反之亦然的转换),但 Win64 RTL 的一部分似乎要好得多。

【讨论】:

  • 我明白了.. 这很可悲。我在我的项目中投入了大量时间进行基准测试,并为每个小例程尝试不同的方法以提高速度;当我期待更快的代码时,这个 64 位的东西是 Delphi 的一记耳光。
  • @hikari:基准测试很好,如果它在整个项目上完成并且优化只有在分析结果表明某些例程确实需要加速时才有用。 Knuth 已经警告过过早的优化。
  • 也许我们可以在社区中创建一个新的Fastcode64 项目。
  • 我在 FastCode 邮件列表上看到了一些 cmets,他们说有一个 FastCode Pos 函数的临时 64 位汇编器版本。我想他们也会看看其他功能。我什至可以自己帮助他们,尽管这些人知道很多我不知道的技巧。
  • 在这里我认为“本机 CPU”编译器比其他任何技术都优越,它应该能够创建接近完美性能的代码,即使是“PUREPASCAL”实现;)跨度>
【解决方案2】:

尽量避免在循环中分配任何字符串。

在您的情况下,可能涉及 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)。

【讨论】:

  • 内联确实给了一些额外的毫秒,但在 64 位中仍然需要几乎两倍的时间。当然 64 位并不意味着它会自动更快,但是.. 一半的速度是一件大事,当您必须等待 10 分钟来处理一个大文本文件而不是 5 等时..
  • 你试过我的确切版本了吗?仅仅添加inline 是不够的。您必须摆脱整个字符串 ansistring 转换,等等。使用临时的s 变量和pointer(s) 应该会更快。
  • 是的,你的代码有点快,虽然我的例子中使用的常量只是为了显示代码,在我的程序中它都是变量。尽管如此,编译到 64 位仍然非常慢,2-3 倍。
【解决方案3】:

代码可以这样写,性能效果不错:

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

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

Intel(R) Pentium(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;

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

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

Intel(R) Pentium(R) D CPU 3.40GHz

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

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

【讨论】:

  • 这个有点奇怪:删除 0 以提高速度(以及之前的 nil 检查):32bit:811 vs 656,64bit:1108 vs 1654。所以,在 32 位这个例子速度较慢,但​​在 64 位中更快 oO
  • 保持 0 检查安全性:32bit: 1607ms, 64bit 1060
【解决方案4】:

测试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 例程进行 nil 检查,而 P := Pointer(S); 只是分配指针。

观察到这里的目标是测试一个字符串是否为数字,然后进行转换, 为什么不使用 RTL TryStrToInt(),它可以一步完成并处理符号和空格。

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

【讨论】:

  • 感谢您的加入。 TryStrToint 似乎比其他方法慢 8 倍左右。
  • Topicstarter 只需要检查数字,而 StrToInt 转换字符串总是会更慢。此外,在大量错误结果上,由于引发异常,Try* 例程更慢。
  • @Fr0sT,Try* 例程不会引发异常。这里的命中/未命中因素决定了调用 IsStrANumber 加上 IntToStr 与仅调用 TryStrToInt 的最佳结果。如果都是数字,后者的速度大约是后者的两倍,而在最坏的情况下则慢 20%。有点不清楚这里的目标是否只是检查字符串是数字还是实际将字符串转换为数字。无论如何,提议的 IsStrANumber 版本似乎满足 OP。
  • 是的,当然,我被“Try”前缀弄糊涂了
【解决方案5】:

64 位的好处在于地址空间,而不是速度(除非您的代码受到可寻址内存的限制)。

从历史上看,这种字符操作代码在更宽的机器上总是较慢。从 16 位 8088/8086 迁移到 32 位 386 确实如此。将 8 位字符放入 64 位寄存器是对内存带宽和缓存的浪费。

为了提高速度,您可以避免使用字符变量、使用指针、使用查找表、使用位并行(在一个 64 位字中处理 8 个字符)或使用 SSE/SSE2... 指令。显然,其中一些会使您的代码依赖于 CPUID。另外,在调试时打开 CPU 窗口,寻找编译器为你喜欢无声字符串转换(尤其是在调用周围)“为”做愚蠢的事情。

您可以尝试查看 FastCode 库中的一些本机 Pascal 例程。例如。 PosEx_Sha_Pas_2 虽然不如汇编版本快,但比 RTL 代码(32 位)快。

【讨论】:

  • PosEx_Sha_Pas_2 在 64 位中实际上似乎比 Pos 慢 60-70%(在 32 位中慢 10 倍)。
【解决方案6】:

这里有两个函数。一个只检查正数。第二个检查是否为阴性。并且不限于大小。第二个比普通的 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;

【讨论】:

  • 这似乎是迄今为止最快的方式,虽然如果你用 AnsiString 调用它会冻结。谢谢。
  • @hikari 尝试用S: String 和这个函数调用IsStrANumber。有IsStrANumber的字符串转换
猜你喜欢
  • 2012-05-09
  • 2012-10-01
  • 2011-12-25
  • 2011-11-22
  • 2011-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-20
相关资源
最近更新 更多