両端キューにベクトルを使用する方がよいのはなぜですか


86

以来

  1. これらは両方とも連続したメモリコンテナです。
  2. 機能的には、dequeには、ベクトルが持つほとんどすべてのものがありますが、前面に挿入する方が効率的であるため、それ以上のものがあります。

なぜwhould誰もが好むstd::vectorstd::deque


2
のVisualC ++実装のstd::deque最大ブロックサイズは非常に小さいため(正しく思い出せば最大16バイト、おそらく32バイト)、現実的なアプリケーションではあまりうまく機能しません。A (または16?それは少数だが)と同じ性能特性に関する持つ各要素が動的に割り当てられ、。他の実装では最大ブロックサイズが異なるため、異なるプラットフォームで比較的同じパフォーマンス特性を持つコードを作成することは困難です。deque<T>sizeof(T) > 8vector<T*>deque
James McNellis 2011年

12
Dequeは連続メモリコンテナではありません。
Mohamed El-Nakib 2014

@ravilいいえ、それは重複であり、この質問を指しています。

1
修正されていない露骨な事実上の誤りを伴う質問が34票のバランスで座っていたとは信じがたい
underscore_d

2
@underscore_dそれが質問である理由です。それが答えだった場合は別の話;)
Assimilater 2016

回答:


115

aの要素は、メモリ内で連続しdequeいませんvector要素はであることが保証されています。したがって、連続した配列を必要とするプレーンCライブラリと対話する必要がある場合、または空間的局所性を(非常に)気にする場合は、をお勧めしますvector。さらに、いくつかの余分な簿記があるので、他の操作はおそらく(わずかに)同等のvector操作よりも高価です。一方、の多くの/大きなインスタンスを使用vectorすると、不要なヒープの断片化が発生する可能性があります(への呼び出しが遅くなりますnew)。

また、StackOverflowの他の場所で指摘されているように、ここにはもっと良い議論があります:http//www.gotw.ca/gotw/054.htm


3
「他の場所」へのリンクは現在無効になっているようです(モデレートのため?)。
esilk

37

違いを知るには、deque一般的にどのように実装されているかを知る必要があります。メモリは同じサイズのブロックに割り当てられ、それらは(配列または場合によってはベクトルとして)一緒にチェーンされます。

したがって、n番目の要素を見つけるには、適切なブロックを見つけて、その中の要素にアクセスします。これは常に正確に2回のルックアップであるため、一定の時間ですが、それでもベクトルを超えています。

vectorまた、C APIであるか、ポインターと長さを取得できるという点でより用途が広いため、連続したバッファーが必要なAPIでもうまく機能します。(したがって、下にベクトルまたは通常の配列を配置し、メモリブロックからAPIを呼び出すことができます)。

dequeその最大の利点はどこにありますか:

  1. コレクションをどちらかの端から拡大または縮小する場合
  2. 非常に大きなコレクションサイズを扱っている場合。
  3. boolsを扱うとき、ビットセットではなくboolsが本当に必要です。

これらの2つ目はあまり知られていませんが、コレクションサイズが非常に大きい場合:

  1. 再割り当てのコストは大きい
  2. 連続するメモリブロックを見つける必要があるオーバーヘッドは制限的であるため、メモリがより早く不足する可能性があります。

過去に大規模なコレクションを扱っていて、隣接モデルからブロックモデルに移行したとき、32ビットシステムでメモリが不足する前に、約5倍の大規模なコレクションを格納できました。これは、再割り当てするときに、要素をコピーする前に、実際には古いブロックと新しいブロックを保存する必要があったためです。

以上のことをすべて言っても、std::deque「楽観的な」メモリ割り当てを使用するシステムでは問題が発生する可能性があります。の再割り当てのために大きなバッファサイズを要求しようとするvectorと、ある時点でが拒否されるbad_alloc可能性がありますが、アロケータの楽観的な性質により、aによって要求された小さなバッファの要求が常に許可されるdeque可能性があります。いくつかのメモリを取得しようとするプロセスを強制終了するオペレーティングシステム。どちらを選んでも、あまり快適ではないかもしれません。

このような場合の回避策は、システムレベルのフラグを設定して楽観的な割り当てを上書きするか(常に実行可能とは限りません)、メモリ使用量などをチェックする独自のアロケータを使用するなど、メモリを手動で管理することです。明らかに理想的ではありません。(ベクトルを好むようにあなたの質問に答えるかもしれません...)


29

vectorとdequeの両方を複数回実装しました。dequeは、実装の観点からは非常に複雑です。この複雑さは、より多くのコードとより複雑なコードに変換されます。したがって、ベクトルではなく両端キューを選択すると、通常、コードサイズがヒットします。また、コードでベクトルが優れているもの(つまり、push_back)のみを使用している場合は、速度がわずかに低下する可能性があります。

両端キューが必要な場合は、dequeが明らかに勝者です。しかし、ほとんどの挿入と消去を後ろで行っている場合は、ベクトルが明らかに勝者になります。確信が持てない場合は、typedefを使用してコンテナーを宣言し(簡単に切り替えられるようにします)、測定します。


3
質問-委員会は、2つのハイブリッド(たとえば、「デッキ」)をC ++に追加することを検討しましたか?(つまり、両端がありvectorます。)以下にリンクされている実装を回答に記述しました。これは、同じくらい高速ですvectorが、はるかに広く適用できます(たとえば、高速キューを作成する場合)。
user541686 2013

5

std::deque継続的なメモリは保証されていません。また、インデックス付きアクセスの場合は多少遅くなることがよくあります。dequeは通常、「ベクトルのリスト」として実装されます。


14
「ベクトルのリスト」は正しいとは思いません。「リスト」の定義にもよりますが、ほとんどの実装は「配列へのポインタのベクトル」であると理解していました(「リスト」は「リンクリスト」と読みます)。 、」これは複雑さの要件を満たしていません。)
James McNellis 2011年

2

http://www.cplusplus.com/reference/stl/deque/によると、「ベクトルとは異なり、dequeはすべての要素が連続した格納場所にあるとは限らないため、ポインタ演算による安全なアクセスの可能性が排除されます。」

Dequeは、必ずしも連続したメモリレイアウトを持っているとは限らないため、もう少し複雑です。その機能が必要な場合は、両端キューを使用しないでください。

(以前、私の答えは標準化の欠如をもたらしました(上記と同じソースから、「両端キューは特定のライブラリによって異なる方法で実装される可能性があります」)が、実際にはほぼすべての標準ライブラリデータ型に当てはまります。)


5
std::dequeと同じくらい標準化されていstd::vectorます。の複雑さの要件std::dequeは、連続したストレージで満たすことができるとは思いません。
ジェリーコフィン

1
おそらく私の言い回しは貧弱でした。標準化が完全ではないことは事実ですが、私が理解しているように、ベクトルは連続したシーケンスとして標準化されており、両端キューはそうではありません。それが一つの決定要因のようです。
patrickvacek 2011年

1
@JerryCoffin:deque連続ストレージで満たすことができない複雑さの要件はどれですか?
user541686 2012年

1
@Mehrdad:正直なところ、私は自分が何を考えていたか覚えていません。私は最近、標準のその部分を十分に見ていないので、以前のコメントが間違っていたと断定的に述べるのは快適ですが、今それを見ると、それがどのように正しいかについても考えられません。
ジェリーコフィン

3
@JerryCoffin:複雑さの要件は実際には些細なものです。大きな配列を割り当ててシーケンスを中央から外側にプッシュし始め(これがMehrdad実装が行うことだと思います)、最後に到達したときに再割り当てできます。このアプローチの問題は、の要件の1つを満たさないことですdeque。つまり、両端に挿入しても、既存の要素への参照が無効にならないということです。この要件は、不連続なメモリを意味します。
Yakov Galka 2012年

0

dequeは、その要素へのランダムアクセスを許可するシーケンスコンテナですが、連続したストレージがあるとは限りません。


0

それぞれのケースの性能テストを行うのは良い考えだと思います。そして、このテストに基づいて決定を下します。

私はほとんどの場合std::dequeよりも好むでしょうstd::vector


1
問題は、事実の誤りや欠落している単語の中から抽出できるものがある場合、なぜ誰かが好むのかということvectorでした。なぜそうでないのかは当然のことと推測できます。deque理由は不明ですが、不特定のテストからあなたが好むと言うことは答えではありません。
underscore_d

0

これらのテスト結果(ソースを使用)によると、両端キューよりもベクトルを好まないでしょう

もちろん、アプリ/環境でテストする必要がありますが、要約すると次のようになります。

  • push_backは基本的にすべて同じです
  • dequeでの挿入、消去はリストよりもはるかに高速で、ベクトルよりもわずかに高速です

さらにいくつかの考察、およびcircular_bufferを検討するための注意。


0

一方では、vectorはdequeよりも単純に高速であることがよくあります。dequeのすべての機能が実際に必要でない場合は、ベクトルを使用してください。

一方、ベクターが提供しない機能必要な場合もあります。その場合は、両端キューを使用する必要があります。たとえば、dequeを使用せずに、またアルゴリズムを大幅に変更せずに、このコードを書き直そうとする人に挑戦します。


実際には、同じ一連のpush_backpop_back操作、deque<int>少なくとも20%の高速化よりも常にあるvector<int>私のテスト(O3とGCC)インチ 私はのはなぜ推測dequeのようなもののための標準的な選択肢ですstd::stack...
IGEL

-1

配列が大きくなるにつれて、ベクトルメモリが再割り当てされることに注意してください。ベクター要素へのポインタがある場合、それらは無効になります。

また、要素を消去すると、イテレータは無効になります(ただし、「for(auto ...)」は無効になります)。

編集:「deque」を「vector」に変更


-1:開始時または終了時に挿入および消去しても、他の要素への参照およびポインターは無効になりません。(途中で挿入と消去は行いますが)
Sjoerd

@Sjoerd「vector」に変更しました。
ピエール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.