哪种STL容器最适合我的需求?我基本上有一个10个元素宽的容器,其中我不断push_back
新元素,而pop_front
最老元素(大约一百万次)。
我目前正在使用std::deque
来完成任务,但是想知道std::list
是否会更有效率,因为我不需要重新分配自己(或者我错了{{1}对于std::deque
?)。或者是否有更高效的容器满足我的需求?
P.S。我不需要随机访问
答案 0 :(得分:165)
由于有无数的答案,你可能会感到困惑,但总结一下:
使用std::queue
。原因很简单:它是一个FIFO结构。你想要FIFO,你使用std::queue
。
它使你的意图对任何人,甚至你自己都清楚。 std::list
或std::deque
没有。列表可以在任何地方插入和删除,这不是FIFO结构所要做的,并且deque
可以从任何一端添加和删除,这也是FIFO结构无法做到的。
这就是你应该使用queue
。
现在,您询问了性能。首先,要记住这个重要的经验法则:优先代码,性能最后。
原因很简单:在清洁和优雅之前追求表现的人几乎总是最后完成。他们的代码变得很糟糕,因为他们放弃了所有好的东西,以便真正从中获取任何东西。
首先编写好的,可读的代码,大多数性能问题都会自行解决。如果以后你发现你的性能不足,现在可以很容易地将一个分析器添加到你漂亮,干净的代码中,并找出问题所在。
大家都说,std::queue
只是一个适配器。它提供了安全的界面,但内部使用了不同的容器。您可以选择此基础容器,这样可以提供很大的灵活性。
那么,您应该使用哪个底层容器?我们知道std::list
和std::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提出担心包裹list
或deque
会导致效果下降。再一次,他或我可以肯定地说,而不是自己描述它,但很可能编译器将内联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_back
和pop_front
。
答案 5 :(得分:3)
答案 6 :(得分:0)
使用std::queue
,但要知道两个标准Container
类的性能折衷。
默认情况下,std::queue
是std::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);}
};