25

多くのテキスト処理を行うアプリケーションを 32 ビットから 64 ビットの Delphi に移植していて、処理速度が極端に変化していることに気付きました。いくつかの手順でいくつかのテストを行いました。たとえば、これは32にコンパイルするよりも64ビットですでに200%以上の時間がかかります(〜900と比較して2000 +ミリ秒)

これは正常ですか?

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;
4

6 に答える 6

35

これに対する現在の解決策はありません。これは、64 ビットのほとんどの文字列ルーチンのコードがPUREPASCAL定義済みの IOW でコンパイルされているためです。これはプレーンな Delphi であり、アセンブラはありませんが、多くの重要なコードは32 ビットの文字列ルーチンは、FastCode プロジェクトによってアセンブラーで作成されました。

現在、64 ビットには FastCode に相当するものはありません。開発者チームは、特により多くのプラットフォームに移行しているため、とにかくアセンブラーを排除しようとするだろうと思います。

これは、生成されたコードの最適化がますます重要になることを意味します。発表された LLVM バックエンドへの移行により、多くのコードが大幅に高速化されることを願っています。そのため、純粋な Delphi コードはもはやそれほど問題ではありません。

申し訳ありませんが、解決策はありませんが、おそらく説明です。

アップデート

XE4 の時点で、かなりの数の FastCode ルーチンが、上記の段落で説明した最適化されていないルーチンに取って代わりました。それらは通常はまだPUREPASCALですが、適切な最適化を表しています。そのため、状況は以前ほど悪くはありません。およびプレーン文字列ルーチンには、 OS XTStringHelperでいくつかのバグといくつかの非常に遅いコードがまだ表示されます(特に Unicode から Ansi へ、またはその逆の変換が関係する場合) が、RTL のWin64部分ははるかに優れているようです。

于 2012-06-29T11:48:42.710 に答える
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ビットバージョンと比較すると、FPC64ビットコンパイラの方がさらに悪いことに注意してください...Delphiコンパイラだけではないようです。64ビットは、マーケティングの内容が何であれ、「高速」を意味するものではありません。それは逆の場合もあります(たとえば、JREは64ビットで低速であることが知られており、ポインターサイズに関しては新しいx32モデルがLinuxに導入される予定です)。

于 2012-06-29T15:40:05.393 に答える
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 ビット: 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 が言ったことも適用すると、さらに高速化できます。

于 2012-06-29T22:25:35.643 に答える
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;

ベンチマーク結果:

        x32     x64
--------------------
hikari  1420    3963
LU RD   1029    1060

32ビットでは、主な速度の違いはインライン化でありP := PAnsiChar(S);、ポインター値を割り当てる前にnilチェックのために外部RTLルーチンを呼び出しますが、ポインターをP := Pointer(S);割り当てるだけです。

ここでの目標は、文字列が数値であるかどうかをテストしてから変換することであることに注意してください。 RTLTryStrToInt()を使用しないでください。これは、すべてを 1 つのステップで実行し、符号、空白も処理します。

ルーチンのプロファイリングと最適化を行う場合、最も重要なことは、問題に対する適切なアプローチを見つけることです。

于 2014-02-22T00:03:39.397 に答える
1

ここに 2 つの機能があります。1 つは正の数のみをチェックします。2 番目のチェックも同様に否定的です。また、サイズに制限はありません。2 つ目は通常の 4 倍高速ですVal

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;
于 2014-02-21T13:40:37.997 に答える
1

64 ビットの利点は、速度ではなくアドレス空間にあります (コードがアドレス可能なメモリによって制限されている場合を除きます)。

歴史的に、この種の文字操作コードは、幅の広いマシンでは常に低速でした。16 ビットの 8088/8086 から 32 ビットの 386 への移行は事実でした。8 ビットの文字を 64 ビットのレジスタに入れると、メモリ帯域幅とキャッシュが無駄になります。

速度のために、char 変数を避ける、ポインターを使用する、ルックアップ テーブルを使用する、ビット並列処理を使用する (1 つの 64 ビット ワードで 8 文字を操作する)、または SSE/SSE2... 命令を使用することができます。明らかに、これらのいくつかはコードを CPUID に依存させます。また、デバッグ中にCPUウィンドウを開き、サイレント文字列変換(特に呼び出しの周り)が好きな「ために」愚かなことをしているコンパイラを探します。

FastCode ライブラリにあるネイティブ Pascal ルーチンのいくつかを調べてみてください。EG PosEx_Sha_Pas_2 は、アセンブラー バージョンほど高速ではありませんが、RTL コード (32 ビット) より高速です。

于 2012-06-30T06:58:21.687 に答える