2016-08-17 16 views
4

は、私はサイズの異なる二つのレコードを持っているWindowsの10長い遅延が

にデルファイ10.1ベルリンを使用しています。私は、経過時間をテストするためにこれらのレコードの2つのループTList<T>をループするコードを書いた。大きなレコードのリストをループするのはずっと遅くなります。

誰もがその理由を説明し、ループをより速く走らせるソリューションを提供できますか?

type 
    tTestRecord1 = record 
    Field1: array[0..4] of Integer; 
    Field2: array[0..4] of Extended; 
    Field3: string; 
    end; 

    tTestRecord2 = record 
    Field1: array[0..4999] of Integer; 
    Field2: array[0..4999] of Extended; 
    Field3: string; 
    end; 

procedure TForm1.Button1Click(Sender: TObject); 
var 
    _List: TList<tTestRecord1>; 
    _Record: tTestRecord1; 
    _Time: TTime; 
    i: Integer; 
begin 
    _List := TList<tTestRecord1>.Create; 

    for i := 0 to 4999 do 
    begin 
    _List.Add(_Record); 
    end; 

    _Time := Time; 

    for i := 0 to 4999 do 
    begin 
    if _List[i].Field3 = 'abcde' then 
    begin 
     Break; 
    end; 
    end; 

    Button1.Caption := FormatDateTime('s.zzz', Time - _Time); // 0.000 

    _List.Free; 
end; 

procedure TForm1.Button2Click(Sender: TObject); 
var 
    _List: TList<tTestRecord2>; 
    _Record: tTestRecord2; 
    _Time: TTime; 
    i: Integer; 
begin 
    _List := TList<tTestRecord2>.Create; 

    for i := 0 to 4999 do 
    begin 
    _List.Add(_Record); 
    end; 

    _Time := Time; 

    for i := 0 to 4999 do 
    begin 
    if _List[i].Field3 = 'abcde' then 
    begin 
     Break; 
    end; 
    end; 

    Button2.Caption := FormatDateTime('s.zzz', Time - _Time); // 0.045 

    _List.Free; 
end; 
+0

容量を使用することを検討してください!あらかじめすべてのエントリのリストを事前に割り当てることは意味があります。 –

+0

@ ZENsanあなたが言っていることは一般的に真実ですが、時間を計られている時間には、リストを作成するのに費やされた時間は含まれず、それらをループする時間だけが含まれます。 –

+1

'TTime'は特にタイムコードの正確な方法ではありません。代わりに['TStopWatch'](http://docwiki.embarcadero.com/Libraries/en/System.Diagnostics.TStopwatch)を使用してください。しかし、45msはリスト内の5000個のアイテムをループするのに長い時間はありません。おそらく、ループの途中に予期しない休止があったかもしれません。たとえば、タスクスイッチ、キャッシュミスなどです。ループを1回タイミングを立てることは、ループの速さの非常に良い表現ではありません。同じループを何度も実行し、各ループの時間を平均します。それはあなたに良い絵を与えるでしょう。 –

答えて

8

まず第一に、私は、全体のコード、私はあなたがタイムアウトしていない実現しないリストを移入しても、コードを検討します。 2番目のレコードのサイズが大きいので、そのレコードタイプの割り当てを行うと、より多くのメモリをコピーする必要があります。さらに、リストから読み込むと、パフォーマンスに影響を与える小さなレコードよりも大きなレコードがキャッシュにやさしくなります。この後者の効果は、前者の効果よりも重要ではない可能性が高い。

関連する項目は、リストの内部配列のサイズを変更する必要があります。場合によっては、サイズ変更によって、インプレースで実行できない再割り当てが発生することがあります。それが起こると、新しいメモリブロックが割り当てられ、前のコンテンツがこの新しいブロックにコピーされます。そのコピーは明らかに大きなレコードのために鉱石の高価です。長さを知っていれば、一度前にアレイを割り当てることでこれを軽減できます。リストCapacityは、使用するメカニズムです。もちろん、必ずしも前もって長さを知っているとは限りません。

あなたのプログラムは、メモリ割り当てとメモリアクセスを遥かに超えています。したがって、これらのメモリ動作の性能が支配的である。

ここでタイミングは、リストから読み取るコードのみです。したがって、人口に対するメモリコピーのパフォーマンスの違いは、実行したベンチマークの一部ではありません。あなたのタイミングの違いは、以下で説明するように、読んでいるときは主に過剰なメモリコピーになります。 _List[i]全体レコードが暗黙的に隠され、ローカル変数にコピーされ、レコード、値型であるため

if _List[i].Field3 = 'abcde' then 

は、このコードを検討します。コードは実際には同等です:

var 
    tmp: tTestRecord2; 
... 
tmp := _List[i]; // copy of entire record 
if tmp.Field3 = 'abcde' then 

このコピーを回避するには、いくつかの方法があります。

  1. 変更参照型であるための基本となるタイプは。これにより、メモリ管理要件が変更されます。そして、あなたは値の型を使いたいという正当な理由があるかもしれません。
  2. アイテムのコピーではなくアイテムのアドレスを返すコンテナクラスを使用します。
  3. TList<T>からダイナミックアレイTArray<T>に切り替わります。この単純な変更により、コンパイラはレコード全体をコピーせずに個々のフィールドに直接アクセスすることができます。
  4. TList<T>.Listを使用して、データを保持するリストオブジェクトの基本配列にアクセスします。前の項目と同じ効果があります。

上記の項目4は、大きな違いを見るために行うことができる最も簡単な変更です。あなたは

if _List.List[i].Field3 = 'abcde' then 

if _List[i].Field3 = 'abcde' then 

に取って代わるだろうし、それがパフォーマンスの非常に重要な変化が得られるはずです。

{$APPTYPE CONSOLE} 

uses 
    System.Diagnostics, 
    System.Generics.Collections; 

type 
    tTestRecord2 = record 
    Field1: array[0..4999] of Integer; 
    Field2: array[0..4999] of Extended; 
    Field3: string; 
    end; 

procedure Main; 
const 
    N = 100000; 
var 
    i: Integer; 
    Stopwatch: TStopwatch; 
    List: TList<tTestRecord2>; 
    Rec: tTestRecord2; 
begin 
    List := TList<tTestRecord2>.Create; 
    List.Capacity := N; 

    for i := 0 to N-1 do 
    begin 
    List.Add(Rec); 
    end; 

    Stopwatch := TStopwatch.StartNew; 
    for i := 0 to N-1 do 
    begin 
    if List[i].Field3 = 'abcde' then 
    begin 
     Break; 
    end; 
    end; 
    Writeln(Stopwatch.ElapsedMilliseconds); 
end; 

begin 
    Main; 
    Readln; 
end. 

私はメモリ状態のアウトを避けるために、64ビットのためにそれをコンパイルする必要がありました:

は、このプログラムを考えてみましょう。私のマシンの出力は約700です。 List[i].Field3List.List[i].Field3に変更してください。出力は1桁です。タイミングはやや粗いですが、私はこれがポイントであると考えています。


キャッシュフレンドリーではない大きなレコードの問題が残ります。それは対処するのがより複雑であり、実際のコードがデータ上でどのように動作しているかを詳細に分析する必要があります。


パフォーマンスを気にする人は、別に、Extendedは使用しません。 2の累乗ではなくサイズ10であるため、メモリアクセスは頻繁に誤って整列されます。 DoubleまたはReal(別名:Double)を使用してください。

+0

彼はループの時間だけを計算し、質問は遅いループに関するものです。あなたが質問に答えなかったという事実の他に、すべての考慮事項は正しいです:) –

+0

@AndreiGalatyn私は包括的であり、尋ねられた以上のものをカバーしようとしました、そして私はその答えが現在質問の答えになっていると思います –

+0

今はあります。 –

関連する問題