FIFOにはどのSTLコンテナを使用すればよいですか?


89

どのSTLコンテナが私のニーズに最適ですか?私は基本的に10要素幅のコンテナーを使用しており、最も古い要素(約100万回)を継続しpush_backながら新しい要素を継続的に追加していますpop_front

私は現在std::deque、タスクにa を使用していますが、std::listそれ自体を再割り当てする必要がないので(またはa std::dequeをaと間違えているので)、aの方が効率的かどうか疑問に思っていましたstd::vector。または、私のニーズにさらに効率的なコンテナはありますか?

PSランダムアクセスは必要ありません


5
両方で試してみて、どちらがよりニーズに合っているかを確認してください。
KTC

5
これをやろうとしていましたが、理論的な答えも探していました。
Gab Royer、

2
これstd::dequeは再割り当てされません。これはa std::listとaのハイブリッドであり、a std::vectorよりも大きなチャンクを割り当てますが、aのstd::listように再割り当てしませんstd::vector
マット価格

2
いいえ、これは標準からの関連する保証です:「両端キューの最初または最後に単一の要素を挿入すると、常に一定の時間がかかり、Tのコピーコンストラクターへの単一の呼び出しが発生します。」
マット価格

1
@ジョン:いいえ、それは再び割り当てます。たぶん、用語を混同しているだけかもしれません。再割り当てとは、古い割り当てを取得し、それを新しい割り当てにコピーして、古い割り当てを破棄することを意味すると思います。
GManNickG 2009

回答:


194

無数の答えがあるので、混乱するかもしれませんが、要約すると:

を使用しstd::queueます。これの理由は簡単です:それはFIFO構造です。FIFOが必要な場合は、std::queue

それはあなたの意図を他の誰にも、そしてあなた自身にも明らかにします。A std::listまたはstd::dequeしません。リストは、FIFO構造では想定されていない場所に挿入および削除dequeできます。また、FIFO構造では実行できない、両端から追加および削除できます。

このため、を使用する必要がありqueueます。

今、あなたはパフォーマンスについて尋ねました。まず、この重要な経験則を常に覚えておいてください:最初に良いコード、最後にパフォーマンス。

これの理由は単純です。清潔さと優雅さの前にパフォーマンスを追求する人は、ほとんどの場合最後に終了します。彼らは本当に何もないようにするために良いことをすべて放棄しているので、彼らのコードはドロドロになります。

最初に適切で読みやすいコードを書くことで、パフォーマンスの問題のほとんどは自動的に解決されます。そして、後でパフォーマンスが不足していることがわかった場合は、プロファイラーをきれいなコードに追加して、問題がどこにあるかを簡単に見つけることができます。

std::queueはいえ、アダプターにすぎません。安全なインターフェースを提供しますが、内部では別のコンテナーを使用します。この基礎となるコンテナーを選択できます。これにより、かなりの柔軟性が得られます。

それで、どの基本的なコンテナを使うべきですか?我々はことを知っているstd::liststd::dequeの両方が必要な機能を提供(push_back()pop_front()、およびfront())、私たちはどのように決めるのですか?

まず、メモリの割り当て(および割り当て解除)は、OSに行って何かをするように要求することを伴うため、一般的に、迅速に行うことではないことを理解してください。A listは、何かが追加されるたびにメモリを割り当て、それがなくなったら割り当てを解除する必要があります。

Aはdeque、他の一方で、チャンクで割り振ります。割り当て頻度が低くなりlistます。リストと考えてください。ただし、各メモリチャンクは複数のノードを保持できます。(もちろん、実際にどのように機能するかを学ぶことをお勧めします。)

したがって、それだけでdequeは、メモリを頻繁に処理しないため、aの方がパフォーマンスが向上します。一定サイズのデータ​​を処理しているという事実と混じり合って、おそらくデータの最初のパスの後で割り当てを行う必要はありませんが、リストは常に割り当てと割り当て解除を行います。

理解する必要がある2番目の事項は、キャッシュのパフォーマンスです。RAMへのアクセスは遅いため、CPUが本当に必要な場合は、メモリのチャンクをキャッシュに戻すことで、この時間を最大限に活用します。dequeはメモリチャンクに割り当てられるため、このコンテナ内の要素にアクセスすると、CPUが残りのコンテナも戻す可能性があります。これでdeque、データがキャッシュにあるため、への以降のアクセスはすべて高速になります。

これは、データが一度に1つずつ割り当てられるリストとは異なります。これは、データがメモリ内のあらゆる場所に分散する可能性があり、キャッシュのパフォーマンスが低下することを意味します。

したがって、それを考慮すると、a dequeの方が適しています。これが、を使用する場合のデフォルトのコンテナである理由ですqueue。とは言っても、これはまだ(非常に)教育を受けた推測にすぎません。あるコードdequelist他のテストを使用して、このコードをプロファイリングし、確実に知る必要があります。

しかし、覚えておいてください。コードをクリーンなインターフェースで動作させてから、パフォーマンスについて心配してください。

ジョンは、listまたはをラップするdequeとパフォーマンスが低下するという懸念を提起します。もう一度言いますが、彼も私も自分でプロファイリングせずに確実に言うことはできませんが、コンパイラが行う呼び出しをインライン化する可能性がありqueueます。つまり、と言うとqueue.push()、実際にはとだけ言っqueue.container.push_back()て、関数呼び出しを完全にスキップします。

繰り返しになりますが、これはqueue経験に基づく推測にすぎませんが、基になるコンテナーrawを使用する場合と比較すると、a を使用してもパフォーマンスは低下しません。前に言ったように、を使用してくださいqueue。これは、クリーンで使いやすく、安全であり、本当に問題のあるプロファイルとテストになる場合に使用します。


10
+1-そして、boost :: circular_buffer <>が最高のパフォーマンスを発揮することが判明した場合は、それを基礎となるコンテナーとして使用します(必要なpush_back()、pop_front()、front()、およびback()も提供します) )。
マイケルバー

2
それを詳細に説明するために受け入れられました(これは私が時間を割いてくれたことに感謝しました)良いコードの最初のパフォーマンスについては、それが私の最大のデフォルトの1つであることを認めなければなりません。最初の実行では常に完璧に実行しようとします...私は最初にdequeを使用してコードを記述しましたが、私が思ったのと同じように(ほぼリアルタイムで行われるはずですが)パフォーマンスが向上したので、少し改善する必要があると思いました。ニールも言ったように、私はプロファイラーを実際に使用するべきだったのですが...それほど重要ではないのに、今はこれらの間違いを犯してよかったです。どうもありがとうございました。
Gab Royer、

4
-1は問題を解決せず、無駄な答えを膨らませます。ここでの正しい答えは短く、それはboost :: circular_buffer <>です。
Dmitry Chichkov 2014年

1
「最初に良いコード、最後にパフォーマンス」、それは素晴らしい引用です。誰もがこれを理解した場合のみ:)
thegreendroid '05

プロファイリングのストレスに感謝します。経験則を提供する1つの事で、その後のプロファイリングでそれを証明することは、より良いものです
talekeDskobeDa

28

チェックしてくださいstd::queue。基礎となるコンテナタイプをラップしますstd::deque。デフォルトのコンテナはです。


3
余分なレイヤーすべてコンパイラーによって削除されます。言語は邪魔になるシェルにすぎないので、あなたの論理では、私たちはすべてアセンブリでプログラムする必要があります。ポイントは、ジョブに正しいタイプを使用することです。そしてqueueそのタイプです。最初に良いコード、後でパフォーマンス。地獄、ほとんどのパフォーマンスはそもそも良いコードを使うことから生まれます。
GManNickG 2009

2
あいまいに申し訳ありません-私の要点は、キューはまさに質問が求めているものであり、C ++の設計者は、dequeがこのユースケースの基礎となる優れたコンテナーであると考えていたことです。
マークランサム

2
この質問には、彼がパフォーマンスの欠如を発見したことを示すものは何もありません。多くの初心者は、現在のソリューションが許容範囲内であるかどうかに関係なく、特定の問題に対する最も効率的なソリューションについて常に尋ねています。
2009

1
@ジョン、彼がパフォーマンスが不足していることを発見した場合、queue私が言ってきたように、安全の殻が提供するものを取り除いてもパフォーマンスは向上しません。あなたはを提案しましたがlist、おそらくパフォーマンスが低下します。
GManNickG 2009

3
std :: queue <>についての重要な点は、deque <>が(パフォーマンスまたは何らかの理由で)必要なものではない場合、std :: listをバッキングストアとして使用するように変更することが1行です-as GManはずっと前に言った。リストの代わりにリングバッファーを本当に使用したい場合は、boost :: circular_buffer <>がすぐにドロップされます... std :: queue <>は、ほぼ間違いなく、使用する必要がある 'インターフェイス'です。そのためのバッキングストアは自由に変更できます。
マイケルバー


7

最も古い要素(約100万回)を使用しpush_backながらpop_front、新しい要素を継続的に使用しています。

コンピューティングでは、100万人は本当に大きな数ではありません。他の人が示唆したstd::queueように、最初のソリューションとしてa を使用します。遅いと思われる場合は、プロファイラーを使用してボトルネックを特定し(推測しないでください)、同じインターフェースを持つ別のコンテナーを使用して再実装します。


1
実は、やりたいことはリアルタイムだと思っているので、数が多いです。プロファイラーを使用して原因を特定するべきだったのはあなたの言うとおりですが、
Gab Royer '12

問題は、私はプロファイラーを使用することに慣れていないということです(クラスの1つでgprofを少し使用しましたが、実際には詳しくはありません...)。いくつかのリソースを紹介していただければ幸いです。PS。私はVS2008を使用しています
Gab Royer

@Gab:どのVS2008を持っていますか(Express、Pro ...)?一部にはプロファイラーが付属しています。
sbi 2009

@Gab申し訳ありませんが、これ以上VSを使用しないので、アドバイスはできません

@Sbi、私が見ているものから、それはチームシステムエディション(私がアクセスできる)にのみあります。これを調べます。
Gab Royer、

5

なんでstd::queue?それが持っているすべてがあるpush_backpop_front


3

キューは、おそらくよりも単純なインタフェースである両端キューが、そのような小さなリストについては、パフォーマンスの違いは、おそらくごくわずかです。

リストについても同様です。必要なAPIの選択次第です。


しかし、私は定数push_backがキューを作成するのか、または両端キューが自分自身を再割り当てするのかと疑問に思いました
Gab Royer

std :: queueは別のコンテナのラッパーであるため、両端キューをラップするキューは、未両端キューよりも効率が悪くなります。
ジョンミリキン、

1
10項目の場合、パフォーマンスは非常に小さな問題である可能性が高く、「効率」はコード時間よりもプログラマー時間で測定した方がよい場合があります。そして、適切なコンパイラ最適化によるキューからデキューへの呼び出しは、何にもなりません。
lavinio 2009

2
@ジョン:このようなパフォーマンスの違いを示す一連のベンチマークを見せてください。それは生の両端キューと同じくらい効率的です。C ++コンパイラーは非常に積極的にインライン化します。
2009

3
私はそれを試しました。:DAクイック&ダーティ10エレメントコンテナー、100,000,000のpop_front()とpush_back()rand()整数(VC9での速度を上げるためのビルド)のリスト:list(27)、queue(6)、deque(6)、array(8) 。
KTC

0

を使用しますstd::queueが、2つの標準Containerクラスのパフォーマンスのトレードオフに注意してください。

デフォルトでstd::queueは、は上のアダプタですstd::deque。通常、これは、おそらく一般的なケースである、多数のエントリを含む少数のキューがある場合に良好なパフォーマンスを提供します。

ただし、std :: dequeの実装に目を向けないでください。具体的には:

"... dequeは通常、最小のメモリコストが大きくなります。1つの要素のみを保持するdequeは、完全な内部配列を割り当てる必要があります(たとえば、64ビットlibstdc ++のオブジェクトサイズの8倍、オブジェクトサイズの16倍、または4096バイトのいずれか大きい方) 、64ビットlibc ++の場合)。」

それを排除するために、キューエントリがキューに入れたいもの、つまりサイズがかなり小さいと仮定すると、4つのキューがあり、それぞれに30,000のエントリが含まれている場合、std::deque実装は選択肢となります。逆に、それぞれが4つのエントリを含む30,000のキューがある場合、そのシナリオでstd::liststd::dequeオーバーヘッドを償却しないため、実装はおそらく最適です。

キャッシュがどのように重要であるか、Stroustrupがリンクされたリストをどのように嫌うかなど、多くの意見を読むでしょう、そしてそれらのすべては特定の条件下で本当です。盲目的にそれを受け入れないでください。そこにある2番目のシナリオでは、デフォルトのstd::deque実装が実行される可能性はほとんどないからです。使用状況を評価して測定します。


-1

このケースは非常に単純なので、独自に作成できます。これは、STLの使用がスペースを取りすぎるマイクロコントローラーの状況でうまく機能するものです。割り込みハンドラからメインループにデータと信号を渡すのに最適な方法です。

// FIFO with circular buffer
#define fifo_size 4

class Fifo {
  uint8_t buff[fifo_size];
  int writePtr = 0;
  int readPtr = 0;
  
public:  
  void put(uint8_t val) {
    buff[writePtr%fifo_size] = val;
    writePtr++;
  }
  uint8_t get() {
    uint8_t val = NULL;
    if(readPtr < writePtr) {
      val = buff[readPtr%fifo_size];
      readPtr++;
      
      // reset pointers to avoid overflow
      if(readPtr > fifo_size) {
        writePtr = writePtr%fifo_size;
        readPtr = readPtr%fifo_size;
      }
    }
    return val;
  }
  int count() { return (writePtr - readPtr);}
};

しかし、どのように/いつそれが起こるのでしょうか?
user10658782

なんとかなると思いました。気にしないで!
Ry-
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.