2つの違いは何ですか?つまり、方法はすべて同じです。したがって、ユーザーにとっては、同じように機能します。
あれは正しいですか??
違いをリストアップしましょう:
複雑
Insert/erase at the beginning in middle at the end
Deque: Amortized constant Linear Amortized constant
List: Constant Constant Constant
(日付はあるがまだ非常に便利な)SGI STLの要約からdeque
:
dequeはベクトルに非常によく似ています。ベクトルと同様に、要素へのランダムアクセス、シーケンスの最後での要素の一定時間の挿入と削除、および中央での要素の線形時間の挿入と削除をサポートするシーケンスです。
dequeがvectorと異なる主な方法は、dequeがシーケンスの先頭での要素の一定時間の挿入と削除もサポートすることです。さらに、dequeには、vectorのcapacity()およびreserve()に類似したメンバー関数がなく、これらのメンバー関数に関連付けられているイテレーターの有効性を保証するものではありません。
list
同じサイトからの要約は次のとおりです。
リストは二重にリンクされたリストです。つまり、これは、順方向と逆方向の両方のトラバーサル、および(償却された)一定時間の要素の最初または最後、あるいは途中での挿入と削除をサポートするシーケンスです。リストには、挿入とスプライシングによってリスト要素へのイテレータが無効にならないという重要な特性があり、削除しても、削除された要素を指すイテレータのみが無効になります。イテレータの順序は変更される可能性があります(つまり、list :: iteratorは、リスト操作後に以前とは異なる先行または後続を持つ可能性があります)が、その無効化がない限り、イテレータ自体が無効になったり、異なる要素を指すようになったりすることはありません。または突然変異が明示的です。
要約すると、コンテナには共有ルーチンがある場合がありますが、それらのルーチンの時間保証はコンテナごとに異なります。これは、これらのコンテナーのどれをタスクに使用するかを検討するときに非常に重要です。コンテナーが最も頻繁に使用される方法(たとえば、挿入/削除よりも検索に多く)を考慮することは、適切なコンテナーに誘導するのに大いに役立ちます。 。
std::list
基本的には二重にリンクされたリストです。
std::deque
一方、は、のように実装されますstd::vector
。インデックスごとのアクセス時間が一定で、最初と最後に挿入と削除が行われるため、リストとは劇的に異なるパフォーマンス特性が得られます。
もう1つの重要な保証は、それぞれの異なるコンテナがデータをメモリに格納する方法です。
dequeは、それぞれの欠点なしに、vectorとlistの両方の利点のバランスをとろうとするように設計されていることに注意してください。これは、マイクロコントローラーなどのメモリが制限されたプラットフォームで特に興味深いコンテナーです。
メモリストレージ戦略は見過ごされがちですが、特定のアプリケーションに最適なコンテナを選択することが最も重要な理由の1つであることがよくあります。
いいえ。両端キューは、前面と背面でのO(1)の挿入と削除のみをサポートします。たとえば、ラップアラウンドを使用してベクターに実装できます。また、O(1)ランダムアクセスが保証されるため、二重にリンクされたリストを(単に)使用していないことを確認できます。
C++クラスの生徒のためにイラストを作成しました。これは、GCC STL実装( https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/)の実装(私の理解)に(大まかに)基づいてい ますstl_deque.hおよびhttps://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/stl_list.h)
コレクション内の要素はメモリブロックに保存されます。ブロックあたりの要素数は、要素のサイズによって異なります。要素が大きいほど、ブロックあたりの要素は少なくなります。根底にある希望は、要素のタイプに関係なくブロックが同じサイズである場合、それがほとんどの場合アロケータに役立つはずであるということです。
メモリブロックを一覧表示する配列(GCC実装ではマップと呼ばれます)があります。最初に空きがある可能性がある最初のブロックと最後に空きがある可能性がある最後のブロックを除いて、すべてのメモリブロックがいっぱいです。マップ自体は、中心から外側に向かって塗りつぶされます。これは、逆に、std::vector
両端での挿入を一定時間で行うことができる方法です。と同様にstd:::vector
、ランダムアクセスは一定時間で可能ですが、1つではなく2つの間接アクセスが必要です。と同様にstd::vector
、または逆にstd::list
、中央の要素を削除または挿入すると、データ構造の大部分を再編成する必要があるため、コストがかかります。
二重リンクリストはおそらくもっと普通です。すべての要素は、他の要素から独立して割り当てられた独自のメモリブロックに格納されます。各ブロックには、要素の値と2つのポインターがあります。1つは前の要素へ、もう1つは次の要素へです。これにより、リスト内の任意の位置に要素を挿入したり、要素のサブチェーンをあるリストから別のリストに移動したりすることが非常に簡単になります(スプライシングと呼ばれる操作)。挿入の最初と最後でポインターを更新するだけです。点。欠点は、インデックスで1つの要素を見つけるには、ポインターのチェーンをたどる必要があるため、ランダムアクセスでは、リスト内の要素の数に線形のコストがかかることです。
との間の顕著な違いの中deque
でlist
の場合deque
:
並べて保管されたアイテム。
2つの側面(前面、背面)からのデータを追加するために最適化されています。
数値(整数)でインデックス付けされた要素。
イテレータ、さらには要素のインデックスで参照できます。
データへの時間アクセスはより高速です。
にとってlist
メモリに「ランダムに」保存されたアイテム。
イテレータのみが閲覧できます。
中央での挿入と取り外しに最適化されています。
データへの時間アクセスは、空間的な局所性が非常に低いため、反復が遅くなります。
非常に大きな要素をうまく処理します
次のリンクも確認できます。このリンクは、2つのSTLコンテナー間のパフォーマンスを比較しています(std :: vectorを使用)。
私がいくつかの有用な情報を共有したことを願っています。
パフォーマンスの違いは他の人によってよく説明されています。オブジェクト指向プログラミングでは、類似または同一のインターフェースが一般的であるということを付け加えたかっただけです。これは、オブジェクト指向ソフトウェアを作成する一般的な方法論の一部です。2つのクラスが同じインターフェイスを実装しているという理由だけで同じように機能するとは限りません。また、attack()とmake_noise()を実装しているため、馬は犬のように機能すると想定する必要があります。
これは、O(1)ルックアップとO(1)の正確なLRUメンテナンスを提供する、リスト、コード化されていないマップの概念実証コードの使用です。消去操作を生き残るために(消去されていない)イテレータが必要です。GPUメモリ上のCPUポインタ用にO(1)の任意の大きさのソフトウェア管理キャッシュで使用することを計画します。Linux O(1)スケジューラーにうなずきます(プロセッサーごとにLRU <->実行キュー)。unordered_mapは、ハッシュテーブルを介して一定時間アクセスできます。
#include <iostream>
#include <list>
#include <unordered_map>
using namespace std;
struct MapEntry {
list<uint64_t>::iterator LRU_entry;
uint64_t CpuPtr;
};
typedef unordered_map<uint64_t,MapEntry> Table;
typedef list<uint64_t> FIFO;
FIFO LRU; // LRU list at a given priority
Table DeviceBuffer; // Table of device buffers
void Print(void){
for (FIFO::iterator l = LRU.begin(); l != LRU.end(); l++) {
std::cout<< "LRU entry "<< *l << " : " ;
std::cout<< "Buffer entry "<< DeviceBuffer[*l].CpuPtr <<endl;
}
}
int main()
{
LRU.push_back(0);
LRU.push_back(1);
LRU.push_back(2);
LRU.push_back(3);
LRU.push_back(4);
for (FIFO::iterator i = LRU.begin(); i != LRU.end(); i++) {
MapEntry ME = { i, *i};
DeviceBuffer[*i] = ME;
}
std::cout<< "************ Initial set of CpuPtrs" <<endl;
Print();
{
// Suppose evict an entry - find it via "key - memory address uin64_t" and remove from
// cache "tag" table AND LRU list with O(1) operations
uint64_t key=2;
LRU.erase(DeviceBuffer[2].LRU_entry);
DeviceBuffer.erase(2);
}
std::cout<< "************ Remove item 2 " <<endl;
Print();
{
// Insert a new allocation in both tag table, and LRU ordering wiith O(1) operations
uint64_t key=9;
LRU.push_front(key);
MapEntry ME = { LRU.begin(), key };
DeviceBuffer[key]=ME;
}
std::cout<< "************ Add item 9 " <<endl;
Print();
std::cout << "Victim "<<LRU.back()<<endl;
}