哪个STL容器最适合我的需求?我基本上有一个10个元素宽的容器,在其中我会不断添加push_back
新元素,同时pop_front
选择最旧的元素(大约一百万次)。
我目前正在使用a std::deque
来执行任务,但想知道a std::list
是否会更高效,因为我不需要重新分配自身(或者我误以为a std::deque
了std::vector
)。还是有一个我需要的效率更高的容器?
PS我不需要随机访问
哪个STL容器最适合我的需求?我基本上有一个10个元素宽的容器,在其中我会不断添加push_back
新元素,同时pop_front
选择最旧的元素(大约一百万次)。
我目前正在使用a std::deque
来执行任务,但想知道a std::list
是否会更高效,因为我不需要重新分配自身(或者我误以为a std::deque
了std::vector
)。还是有一个我需要的效率更高的容器?
PS我不需要随机访问
std::deque
不会重新分配。它是a std::list
和a 的混合体std::vector
,它分配的块比a大,std::list
但不会像a那样重新分配std::vector
。
Answers:
由于有无数的答案,您可能会感到困惑,但总结一下:
使用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()
),所以我们如何决定?
首先,要了解分配(和取消分配)内存通常不是一件容易的事,因为这涉及到操作系统并要求其做某事。A list
必须在每次添加某项内容时分配一次内存,并在内存消失后对其进行分配。
deque
另一方面,A分块分配。它分配的频率要低于list
。可以将其视为一个列表,但是每个内存块可以容纳多个节点。(当然,我建议您真正了解它的工作原理。)
因此,仅凭这一项deque
,它就应该会表现更好,因为它不会经常处理内存。加上您正在处理恒定大小的数据这一事实,它可能在第一次传递数据后就不必分配,而列表将不断地分配和释放。
要了解的第二件事是缓存性能。进入RAM的速度很慢,因此当CPU确实需要时,通过将一部分内存带回缓存来充分利用这段时间。由于a deque
在内存块中进行分配,因此访问此容器中的元素很可能会导致CPU也带回容器的其余部分。现在,对的任何进一步访问deque
都会很快,因为数据在缓存中。
这与列表不同,在列表中一次分配一个数据。这意味着数据可能会散布到内存中的所有位置,并且缓存性能会很差。
因此,考虑到这一点,a deque
应该是一个更好的选择。这就是使用时默认容器的原因queue
。综上所述,这仍然只是(非常)有根据的猜测:您必须使用deque
一个测试和另一个测试来概要分析此代码,list
才能真正确定。
但是请记住:让代码使用干净的接口,然后担心性能。
John提出了一个问题,即将list
或换行deque
会导致性能下降。再一次,他或我也可以肯定地说而不自己分析,但是编译器可能会内联make的调用queue
。也就是说,当您说时queue.push()
,它实际上只是说queue.container.push_back()
,完全跳过了函数调用。
再一次,这只是一个有根据的猜测,但queue
与使用基础容器raw相比,使用a 不会降低性能。就像我之前说过的那样,请使用queue
,因为它很干净,易于使用且安全,并且确实会成为问题档案和测试。
结帐std::queue
。它包装了基础容器类型,默认容器为std::deque
。
queue
是那种类型。好的代码首先,性能更高。糟糕,大多数性能首先来自使用好的代码。
queue
性能,那么像我刚才所说的那样,剥去安全壳将无法提高性能。您建议使用list
,可能会导致效果更差。
在性能真正重要的地方,请查看Boost循环缓冲区库。
我在选择最旧的
push_back
元素的同时不断添加新元素pop_front
(大约一百万次)。
在计算中,一百万确实不是很大的数字。正如其他人建议的那样,请使用a std::queue
作为您的第一个解决方案。在不太可能的情况下,如果它太慢,请使用探查器确定瓶颈(不要猜测!),并使用具有相同接口的其他容器重新实现。
使用std::queue
,但是要知道两个标准Container
类的性能折衷。
默认情况下,std::queue
是上的适配器std::deque
。通常,在队列数量少且包含大量条目的队列中,这样做会带来良好的性能,这在通常情况下是可以肯定的。
但是,不要盲目实现std :: deque。特别:
“ ...双端队列通常具有很大的最小内存开销;仅包含一个元素的双端队列必须分配其完整的内部数组(例如,64位libstdc ++的对象大小的8倍;对象大小的16倍或4096字节,以较大者为准) ,在64位libc ++上)。”
为了消除这种情况,假设队列条目是您要排队的东西,即大小相当小,那么如果您有4个队列,每个队列包含30,000个条目,std::deque
则将选择实现。相反,如果您有30,000个队列,每个队列包含4个条目,则std::list
实现最佳方案的可能性将更大,因为您将永远不会分摊std::deque
这种情况下的开销。
您会读到很多关于缓存如何为王,Stroustrup如何讨厌链表等的观点,在某些情况下,所有这些都是正确的。只是不要盲目地接受它,因为在第二种情况下,默认std::deque
实现不太可能执行。评估您的用法和度量。
这种情况很简单,您可以自己编写。对于使用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);}
};