我应该将哪个STL容器用于FIFO?

时间:2009-08-11 20:45:27

标签: c++ stl fifo

哪种STL容器最适合我的需求?我基本上有一个10个元素宽的容器,其中我不断push_back新元素,而pop_front最老元素(大约一百万次)。

我目前正在使用std::deque来完成任务,但是想知道std::list是否会更有效率,因为我不需要重新分配自己(或者我错了{{1}对于std::deque?)。或者是否有更高效的容器满足我的需求?

P.S。我不需要随机访问

8 个答案:

答案 0 :(得分:165)

由于有无数的答案,你可能会感到困惑,但总结一下:

使用std::queue。原因很简单:它是一个FIFO结构。你想要FIFO,你使用std::queue

它使你的意图对任何人,甚至你自己都清楚。 std::liststd::deque没有。列表可以在任何地方插入和删除,这不是FIFO结构所要做的,并且deque可以从任何一端添加和删除,这也是FIFO结构无法做到的。

这就是你应该使用queue

的原因

现在,您询问了性能。首先,要记住这个重要的经验法则:优先代码,性能最后。

原因很简单:在清洁和优雅之前追求表现的人几乎总是最后完成。他们的代码变得很糟糕,因为他们放弃了所有好的东西,以便真正从中获取任何东西。

首先编写好的,可读的代码,大多数性能问题都会自行解决。如果以后你发现你的性能不足,现在可以很容易地将一个分析器添加到你漂亮,干净的代码中,并找出问题所在。

大家都说,std::queue只是一个适配器。它提供了安全的界面,但内部使用了不同的容器。您可以选择此基础容器,这样可以提供很大的灵活性。

那么,您应该使用哪个底层容器?我们知道std::liststd::deque都提供了必要的功能(push_back()pop_front()front()),那么我们如何决定?

首先,要了解分配(和解除分配)内存并不是一件容易的事情,因为它涉及到操作系统并要求它做某事。 list每次添加内容时都必须分配内存,并在内存消失时释放内存。

另一方面,deque以块的形式分配。它的分配频率低于list。可以将其视为列表,但每个内存块可以容纳多个节点。 (当然,我建议你really learn how it works。)

因此,单凭deque应该会更好,因为它不会经常处理内存。与处理常量数据的事实相混合,它可能不必在第一次通过数据后分配,而列表将不断分配和解除分配。

要理解的第二件事是cache performance。走向RAM很慢,所以当CPU确实需要时,通过将一大块内存带回缓存,它可以最大限度地利用它。因为deque在内存块中分配,所以访问此容器中的元素可能会导致CPU恢复容器的其余部分。现在,对deque的任何进一步访问都将很快,因为数据在缓存中。

这与列表不同,列表中一次分配一个数据。这意味着数据可以遍布内存中的所有位置,并且缓存性能会很差。

因此,考虑到这一点,deque应该是更好的选择。这就是使用queue时它是默认容器的原因。总而言之,这仍然是一个(非常)受过教育的猜测:你必须分析这段代码,在一次测试中使用deque,在另一次测试中使用list来确定。< / p>

但请记住:让代码使用干净的界面,然后担心性能。

John提出担心包裹listdeque会导致效果下降。再一次,他或我可以肯定地说,而不是自己描述它,但很可能编译器将内联queue的调用。也就是说,当你说queue.push()时,它只会说queue.container.push_back(),完全跳过函数调用。

再一次,这只是一个有根据的猜测,但与使用底层容器raw相比,使用queue不会降低性能。就像我之前说过的那样,使用queue,因为它干净,易于使用且安全,并且如果它真的成为问题轮廓和测试。

答案 1 :(得分:27)

结帐std::queue。它包装了一个底层容器类型,默认容器是std::deque

答案 2 :(得分:10)

如果表现非常重要,请查看Boost circular buffer library

答案 3 :(得分:7)

  

我不断push_back新元素   而pop_front最古老的元素   (大约一百万次)。

百万在计算中确实不是一个大数字。正如其他人所建议的那样,使用std::queue作为您的第一个解决方案。万一它太慢,请使用分析器识别瓶颈(不要猜测!)并使用具有相同接口的不同容器重新实现。

答案 4 :(得分:5)

为什么不std::queue?它只有push_backpop_front

答案 5 :(得分:3)

queue可能是比deque更简单的界面,但对于如此小的列表,性能差异可能微不足道。

同样适用于list。这只是选择你想要的API。

答案 6 :(得分:0)

使用std::queue,但要知道两个标准Container类的性能折衷。

默认情况下,std::queuestd::deque之上的适配器。通常,在队列数量少且包含大量条目的队列中,这样可以提供良好的性能,这很常见。

但是,不要盲目实施std::deque。具体来说:

“ ...双端队列通常具有很大的最小内存开销;仅包含一个元素的双端队列必须分配其完整的内部数组(例如,在64位libstdc ++上是对象大小的8倍;在对象大小的16倍或在64位libc ++上为4096字节(以较大者为准)。”

要弄清这一点,假定队列条目是您要排队的东西,即大小相当小,那么如果您有4个队列,每个队列包含30,000个条目,则std::deque实现是选择的选择。相反,如果您有30,000个队列,每个队列包含4个条目,那么std::list实现将是最佳的选择,因为在这种情况下您将永远不会分摊std::deque的开销。

您会读到很多关于缓存如何为王,Stroustrup如何讨厌链表等的观点,在某些情况下,所有这些都是正确的。只是不要盲目地接受它,因为在第二种情况下,默认的std::deque实现不太可能会执行。评估您的用法和措施。

答案 7 :(得分:-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);}
};