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

时间:2012-06-29 10:49:45

标签: 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;

6 个答案:

答案 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

  • x32-bit:2730 ms
  • x64-bit:3260 ms

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

  • x32位:2979 ms
  • x64位:1794 ms

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

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 ms
  • x64-bit:1934 ms

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

  • x32位:1170 ms
  • x64位:1279 ms

如果你也应用了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(),它一步完成并处理标志,空白。

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