3

私は、基本的なことを本当に理解したり考えたりすることなく、Delphi を使いこなしてきた、いわゆる開発者の 1 人です。この場合、私は文字列について話しています。

メモリを事前に割り当てると速度が大幅に向上することは理解していますが。単純な実世界のケースでそれを使用する方法がわかりません (これは、TStringBuilder の場合はさらに当てはまります)。

たとえば、フォルダーを再帰的に検索し、結果をハッシュ リストに追加する次のコードがあるとします。

var
   FilesList : TDictionary<String, Byte>;  // Byte = (file = 0, folder = 1)

// ------------------------------------------------------------------------------ //
procedure AddFolder(const AFolderName : String);
var
   FileName : String;
   AHandle  : THandle;
   FindData : TWin32FindData;
begin
     AHandle := FindFirstFile(PChar(AFolderName + '*'), FindData);
     if (AHandle = INVALID_HANDLE_VALUE) then
        Exit;

     repeat
           if (FindData.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY = 0) then
           begin
                { Add a file. }
                FileName := FindData.cFileName;
                FilesList.Add(AFolderName + FileName, 0);
           end
           else if ((FindData.cFileName[0] <> '.') OR Not ((FindData.cFileName[1] = #0) OR (FindData.cFileName[1] = '.') And (FindData.cFileName[2] = #0))) then
           begin
                FileName := AFolderName + FindData.cFileName + '\';
                FilesList.Add(FileName, 1);
                AddFolder(FileName);
           end;
     until Not FindNextFile(AHandle, FindData);

     Windows.FindClose(AHandle);
end;

良い例かどうかはわかりませんが、この場合、変数にメモリを事前に割り当てるFileNameことが実行速度を向上させるのにどのように役立つかは明確ではありません。特に、その長さについては何もわかりません。これが可能であると仮定すると、どのように?

または、事前割り当て手法は、文字列を連結/構築する場合にのみ役立ちますか?


私の質問に関するメモ:

  1. 質問は主にXE2に関するものですが、他の開発者が知恵を共有することで恩恵を受けると確信しているため、他の Delphi バージョンを自由に参照してください (つまり、モッズがおしゃべりまたは主観的なものとして削除しないと仮定します)。

  2. 文字列メモリの事前割り当てを最適化することにより、非常に大きなループ/大量のデータでマイクロ最適化を行う必要がある単純な日常のケースにもっと興味があります。

4

3 に答える 3

9

文字列のメモリは、追加される各部分に再割り当てされるため、(たとえば)文字列の連結をまっすぐにするのは遅くなる可能性があります。新しいサイズを実際にその場に収容できる場合もありますが、データを新しい場所にコピーしたり、古いバッファを解放したりする必要がある場合もあります。これには時間がかかります。

ただし、一般的には、パフォーマンスプロファイラーまたは明示的なタイミングステートメントで実際にパフォーマンスの問題があることを確認していない限り、これは問題になりません。

于 2013-01-08T01:49:08.023 に答える
4

もちろん、文字列が実際にどのように機能するかを理解せずに逃げてしまいました.Delphiはその点で優れています.文字列操作は非常に効果的であり、メモリマネージャーは小さなメモリブロックに対しても非常に効果的です. DelphiでALOTを実行でき、文字列操作に問題はありません。

注意が必要なクラスの問題がいくつかあります。特に、参照しているルーチンが再利用される場合 (ライブラリ コード) はそうです。

たとえば、これは常にフラグを立てる必要があります。

Result := '';
for i:=1 to N do
  Result := Result + Something; // <- Recursively builds the string, one-char-at-a-time

それが頻繁に使用されない場合、または時間が重要でない場所で使用される場合でも、それは Delphi で飛ぶ可能性があります。とはいえ、そのようなコードは最適化する必要があるため、文字列の (おそらく) 全体の長さが事前に割り当てられ、トリミングされてから終了します。

SetLength(Result, Whatever);
for i:=1 to N do
  Result[i] := SomeChar;
SetLength(Result, INowKnowTheLength);

TStringBuilderが輝く例を次に示します。このようなものがある場合:

var Msg: string;
begin
  Msg := 'Mr ' + MrName + #13#10;
  if SomeCondition then
    Msg := Msg + 'We were unable to reach you'
  else
    Msg := Msg + 'We need to let you know';
  Msg := Msg + #13#10 
end;

つまり、1 つの複雑な (そしておそらく大きい) メッセージのビットを構築するコードは、以下を使用して簡単に最適化できTStringBuilderます。

var Msg: TStringBuilder;
begin
  Msg := TStringBuilder.Create;
  try
    Msg.Append('Mr ');
    Msg.Append(MrName);
    Msg.Append(#13#10);
    if SomeCondition then
      Msg.Append('We were unable to reach you')
    else
      Msg.Append('We need to let you know');
    Msg.Append(#13#10);
    ShowMessage(Msg.ToString); // <- Gets the whole string
  finally Msg.Free;
  end;
end;

とにかく、書きやすさ、メンテナンスのしやすさと、パフォーマンスの真の利点とのバランスを常にとってください。書き込むコードの自然な制限を超えないでください。文字列生成ルーチンを最適化して、HDD が書き込める速度よりも速くするのは無駄な作業です。メッセージを 1 ミリ秒 (20 ミリ秒ではなく) で生成するように GUI コードを最適化することも無駄な作業です。ユーザーは、コードが 20 倍高速であることを決して知りません。

于 2013-01-08T12:10:47.407 に答える
3

基本的に、あなたが話しているのはこれらの連結です:

AFolderName + '*'
AFolderName + FindData.cFileName
AFolderName + FindData.cFileName + '\'

最初のものは 1 回実行され、ループは 2 番目と 3 番目のいずれかを実行します。

System.pas のこれらのメソッドは、3 行で内部的に使用されます。

procedure _UStrCat3(var Dest: UnicodeString; const Source1, Source2: UnicodeString);
procedure _UStrCat3(var Dest: UnicodeString; const Source1, Source2: UnicodeString);
procedure _UStrCatN(var Dest: UnicodeString; ArgCnt: Integer; const Strs: UnicodeString); varargs;

3 つの値が異なるため、1 つの式のみを使用して最適化することはできません。

すべての関数は、最終的な長さを事前に計算し、必要に応じて適切な割り当て作業を行います。

ループ内で、AFolderName + FindData.cFileName + '\'自分自身の事前割り当てを行い、その部分をスナップアウトしようとするかもしれませんAFolderName + FindData.cFileNameが、その場合は 2 つの割り当てが必要になりますthen

したがって、コードをこれ以上最適化することはできないと思います (つまり、桁違いにパフォーマンスを向上させることはできません)。

于 2013-01-08T10:29:51.680 に答える