推定できる場合、TList、TObjectList、およびplain arrayのパフォーマンスの違いはどれくらい顕著ですか?
-
27-10-2019 - |
質問
*要約:
Delphiの専門家からの知識豊富なコメントを確認してください。特に私のために、Davidが提案したように古いTList/TObjectListを使用し、ハードキャストとTObjectListを使用しようとします。A.Bouchezが提案したようにプロパティをリストします。将来的にリファクタリングするときにTDynArrayを試してみます。
=====================================================================
私が持っていると言う TAtom
次のコードで定義されているクラス。についてあります hundreds
まで thousands
実行時のTAtomインスタンスの, stored in a dynamic array
今のところ実行時に、私は単純な浮動小数点演算を行う必要があります TAtom.X/Y/Z
すべての既存のTAtomインスタンスのより多くの 30
秒あたりの時間。
今、私はの能力を追加する必要があります adding
, inserting
, deleting
実行時のTAtomインスタンスの。私の選択は(1)大きな配列を要求しているようです;(2)動的配列に固執し、手動でSetLength;(3)通常のTListに切り替える;(4)通常のTObjectListに切り替えます。
私はそれが必要でない限り(1)を避けたいのですが、私はかなり多くの関数シグネチャを変更しなければならないからです。(2)TList/TObjectListがこのタスクのために生まれたように見えるため、どちらも良くありません。しかし、通常のTList/TObjectListを使用して型キャストが必要なので、可能性のあるパフォーマンスヒットについてコメントすることはできますか?つまり、コードを書き換える前にパフォーマンスの負担を見積もることができれば最高です。パフォーマンスが著しく低下する場合、私が使用できる他の技術はありますか?
さらに、TListとTObjectListの使用にパフォーマンスの違いがあるかどうか疑問に思っていますか?
TAtom = class
public
ElementZ: Integer;
X, Y, Z: Extended;
other variables: other types;
end;
TAAtom = array of TAtom;
解決
Generics.Collections.TObjectList<TAtom>
を使用していて、キャストする必要がない場合。
説明する使用法では、パフォーマンスは問題ないはずです。挿入ポイントの後にアイテムをリストの上方に移動する必要があるため、挿入は最後に追加するよりも要求が厳しくなります。
SetLength(A, Length(A)+1)
を避け、より賢明な割り当て戦略を選択する限り、動的配列は、クラスのようなTList
のすべてと同等です。
大きなリストをメモリの連続ブロックとして維持しようとすると、パフォーマンスとメモリの断片化に問題が発生することがありました。それから私はサブアロケーションスキームに頼りました。ただし、リストには本質的にポインタであるオブジェクト参照が含まれているため、すでに暗黙的なサブ割り当てがあります。
それはすべていくぶん推測的であり、あなたは本当に測定する必要があります–そうでなければ、私たちは推測することしかできません。
他のヒント
リストに別の選択肢を追加してもよろしいですか?
TAtom
のデータに継承機能を使用しない場合は、record
の代わりにclass
を使用できます。各クラスインスタンスは、メモリに割り当てられ、ゼロで埋められ、個別に初期化される必要があります。 Getmem/Freemem
は常にコストがかかり、メモリの断片化が増加します。
事前に割り当てられた動的array of record
は、追加する個々のクラスインスタンスよりも高速になります。また、データはCPU L1 / L2キャッシュにより適しています。
挿入と削除の場合、削除/挿入するデータが増えるため、アイテムの数が多い場合、そのようなレコードの配列はTList
よりも遅くなります(TList/TObjectList
は両方ともポインターのリストのみを保持します)。挿入/削除をさらに高速化するには、リンクリストを使用することをお勧めします。
内部通知のため、TList/TObjectList
メカニズムにはいくらかのオーバーヘッドがあります。メカニズムそして、GetItem()
プロパティは、動的配列を直接使用するよりも(範囲チェックのために)少し遅くなる可能性があります。
ただし、 TDynArrayラッパーを使用すると、動的配列に固執することができます。それでも、優れたパフォーマンス、事前割り当て機能、およびTList
のようなメソッドがあります。また、SaveToStream, Slice, Reverse
、外部インデックスを使用した並べ替えなど、さらに多くのメソッドを利用できます...
ジェネラコディセタグプレ
XEまでのDelphi6で動作します。
ジェネリックスをサポートする新しいバージョンのDelphiを使用する場合は、この方向に進む必要があります。
ただし、型キャストは 通常の使用が必要 TList / TObjectList、誰かが 可能なパフォーマンスについてコメントする ヒット?
フォームを使用してキャストを入力した場合 ジェネラコディセタグプレ
小さなオーバーヘッドが追加されるため、状況に応じて実際に追加される可能性があります。ただし、ハードタイプキャストする場合 ジェネラコディセタグプレ
私の知る限り、タイプキャストはチェックなしで実行されるため、実際にはオーバーヘッドは発生しません。また、コンパイル時に実行されると思います。
あなたの質問の他の側面については、それらはすべてすでに適切にカバーされていると思います...
TObjectListはTListの直接の子孫であるため、パフォーマンスは非常に近くなります。
最初の質問:私たちはClasses.TList
とContnrs.TObjectList
について話しているのですか、それともそれぞれGenerics.Collections.TList
について話しているのですか?
ジェネリックスについて話している場合、TListとTObjectListの両方が動的配列を使用して実装されます。動的配列を直接使用する場合と、汎用コンテナーのより優れたインターフェースを使用する場合の間にパフォーマンスの違いがある場合、それは無視できる程度になります。
「古い」Generics.Collections.TObjectList
とTList
について話している場合、TObjectList
はTList
の子孫であり、パフォーマンス特性をすべて継承するため、TObjectList
と同等の動的配列のみを比較する必要があります。 TList
は、TList
を使用して割り当てられたメモリのブロックを使用します。動的配列は内部で同じことを行うので、大きな違いはないはずです!
結論
2つの間にパフォーマンスの違いがある場合、それはおそらく、動的配列の素朴な使用が恐ろしいジェネラコディセタグコードを使用しているのに対し、Delphiが提供するすべてのコンテナでのより良い実装はメモリをより大きなチャンクに事前に割り当てているためです。適切なコードがあれば、これらの選択肢の間に大きな違いはないはずです!
テストプロジェクトを作成し、4つの方法を使用して、何千ものTAtomインスタンスを追加、挿入、および削除する時間を測定します。次に、どちらを使用するかを決定します。
TListとTObjectListは、動的配列を常に再割り当てする必要があるため、追加、挿入、削除の際に動的配列よりもおそらく高速です。TListとTObjectListの実装はそれを行いません。
TList
など。メモリまたはdynarrayのチャンクで動作するコードが何をしなければならないかを正確に行いますが、その実装は一般的なケースに合わせてすでに最適化されており、メモリマネージャの動作に関する考慮事項が含まれています。
一つの基準は、シーケンスに対する読み取り/更新の比率である可能性があります。シーケンスが作成された後にまれに更新される場合、dynarraysでは、要素へのアクセスがあるため、dynarraysではより良い速度が知覚されるはずです TList
また、同類には、1つのメソッド呼び出しと境界チェックが必要であり、使用する場合は型チェックが必要です as
オペレーター。
最終的には、上で行わ算術のコスト TAtom
実行時間を支配し、dynarrayまたはdynarrayの選択を行う必要があります TListXXX
無関係だ
レコードの動的配列は、アイテムにアクセスする際の影響が最も少なくなります。アトムがオブジェクトの場合、すべてのリストはアクセス速度の点である程度同等になります。
ただし、それらの多くを実行する場合、重要な問題は挿入と削除であり、すべてのリストと配列のパフォーマンスが低下しますが、それがプロファイリングからわかります。 その場合は、次のことを検討してください。
- インデックスでアトムにアクセスする必要がない場合のリンクリスト
- ツリー。アトムのスペースパーティションを使用する場合は、配列ではなく、そのパーティションを使用してアトムを保持することもできます。
- 配列/リストでundefined / nil要素を許可し、undefined / nil要素のスタックを維持し、ソートされたリストが必要な場合はインデックスを維持します(最高のパフォーマンスのソリューションになる可能性がありますが、正しく実行するには最も複雑になる可能性があります)効率の観点から)