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

发布于 2024-08-01 18:22:12 字数 325 浏览 9 评论 0原文

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

我目前正在使用 std::deque 来完成任务,但想知道 std::list 是否会更有效,因为我不需要重新分配自身(或者也许我把 std::deque 误认为是 std::vector?)。 或者是否有更有效的容器可以满足我的需求?

PS我不需要随机访问

Which STL container would fit my needs best? I basically have a 10 elements wide container in which I continually push_back new elements while pop_front ing the oldest element (about a million time).

I am currently using a std::deque for the task but was wondering if a std::list would be more efficient since I wouldn't need to reallocate itself (or maybe I'm mistaking a std::deque for a std::vector?). Or is there an even more efficient container for my need?

P.S. I don't need random access

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

若水微香 2024-08-08 18:22:12

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

使用 std::queue。 原因很简单:它是一个 FIFO 结构。 如果您想要 FIFO,则可以使用 std::queue

它让其他人,甚至你自己都清楚你的意图。 std::liststd::deque 没有。 列表可以在任何地方插入和删除,这不是 FIFO 结构应该做的事情,而双端队列可以从任意一端添加和删除,这也是 FIFO 结构无法做到的。

这就是为什么您应该使用队列

现在,您询问了性能。 首先,永远记住这条重要的经验法则:首先是好的代码,最后是性能。

原因很简单:在干净和优雅之前追求性能的人几乎总是最后完成。 他们的代码变得一团糟,因为他们为了真正从中一无所获而放弃了所有好的东西。

通过首先编写良好的、可读的代码,大多数性能问题都会自行解决。 如果稍后您发现性能不足,现在可以轻松地将分析器添加到漂亮、干净的代码中,并找出问题所在。

总而言之,std::queue 只是一个适配器。 它提供安全接口,但内部使用不同的容器。 您可以选择这个底层容器,这提供了很大的灵活性。

那么,您应该使用哪个底层容器? 我们知道 std::liststd::deque 都提供了必要的函数(push_back()pop_front()< /code> 和 front()),那么我们如何决定呢?

首先,要了解分配(和释放)内存通常不是一件很快的事情,因为它涉及到操作系统并要求它执行某些操作。 列表每次添加内容时都必须分配内存,并在内容消失时释放内存。

另一方面,双端队列以块的形式进行分配。 它的分配频率低于列表。 将其视为一个列表,但每个内存块可以容纳多个节点。 (当然,我建议您真正了解它是如何工作的。)

所以,仅凭这一点,deque 应该表现得更好,因为它不那么频繁地处理内存。 再加上您正在处理恒定大小的数据,它可能不必在第一次传递数据后进行分配,而列表将不断分配和取消分配。

第二件事要了解的是缓存性能 。 访问 RAM 的速度很慢,因此当 CPU 真正需要时,它会通过将一块内存带回缓存中来充分利用这段时间。 由于deque 在内存块中分配,因此访问此容器中的元素可能会导致 CPU 也带回容器的其余部分。 现在,对双端队列的任何进一步访问都会很快,因为数据位于缓存中。

这与列表不同,列表一次分配一个数据。 这意味着数据可能会分散在内存中的各处,并且缓存性能会很差。

因此,考虑到这一点,双端队列应该是更好的选择。 这就是为什么它是使用队列时的默认容器。 总而言之,这仍然只是一个(非常)有根据的猜测:您必须分析此代码,在一个测试中使用 deque ,在另一个测试中使用 list确实知道。

但请记住:让代码以干净的界面运行,然后再担心性能。

John 担心包装 listdeque 会导致性能下降。 再说一遍,他和我都不能在不亲自分析的情况下肯定地说,但编译器很可能会内联队列进行的调用。 也就是说,当您说queue.push()时,它实际上只会说queue.container.push_back(),完全跳过函数调用。

再次强调,这只是一个有根据的猜测,但与使用底层容器原始相比,使用队列不会降低性能。 就像我之前说过的,使用队列,因为它干净、易于使用且安全,而且如果它真的成为问题分析和测试的话。

Since there are a myriad of answers, you might be confused, but to summarize:

Use a std::queue. The reason for this is simple: it is a FIFO structure. You want FIFO, you use a std::queue.

It makes your intent clear to anybody else, and even yourself. A std::list or std::deque does not. A list can insert and remove anywhere, which is not what a FIFO structure is suppose to do, and a deque can add and remove from either end, which is also something a FIFO structure cannot do.

This is why you should use a queue.

Now, you asked about performance. Firstly, always remember this important rule of thumb: Good code first, performance last.

The reason for this is simple: people who strive for performance before cleanliness and elegance almost always finish last. Their code becomes a slop of mush, because they've abandoned all that is good in order to really get nothing out of it.

By writing good, readable code first, most of you performance problems will solve themselves. And if later you find your performance is lacking, it's now easy to add a profiler to your nice, clean code, and find out where the problem is.

That all said, std::queue is only an adapter. It provides the safe interface, but uses a different container on the inside. You can choose this underlying container, and this allows a good deal of flexibility.

So, which underlying container should you use? We know that std::list and std::deque both provide the necessary functions (push_back(), pop_front(), and front()), so how do we decide?

First, understand that allocating (and deallocating) memory is not a quick thing to do, generally, because it involves going out to the OS and asking it to do something. A list has to allocate memory every single time something is added, and deallocate it when it goes away.

A deque, on the other hand, allocates in chunks. It will allocate less often than a list. Think of it as a list, but each memory chunk can hold multiple nodes. (Of course, I'd suggest that you really learn how it works.)

So, with that alone a deque should perform better, because it doesn't deal with memory as often. Mixed with the fact you're handling data of constant size, it probably won't have to allocate after the first pass through the data, whereas a list will be constantly allocating and deallocating.

A second thing to understand is cache performance. Going out to RAM is slow, so when the CPU really needs to, it makes the best out of this time by taking a chunk of memory back with it, into cache. Because a deque allocates in memory chunks, it's likely that accessing an element in this container will cause the CPU to bring back the rest of the container as well. Now any further accesses to the deque will be speedy, because the data is in cache.

This is unlike a list, where the data is allocated one at a time. This means that data could be spread out all over the place in memory, and cache performance will be bad.

So, considering that, a deque should be a better choice. This is why it is the default container when using a queue. That all said, this is still only a (very) educated guess: you'll have to profile this code, using a deque in one test and list in the other to really know for certain.

But remember: get the code working with a clean interface, then worry about performance.

John raises the concern that wrapping a list or deque will cause a performance decrease. Once again, he nor I can say for certain without profiling it ourselves, but chances are that the compiler will inline the calls that the queue makes. That is, when you say queue.push(), it will really just say queue.container.push_back(), skipping the function call completely.

Once again, this is only an educated guess, but using a queue will not degrade performance, when compared to using the underlying container raw. Like I've said before, use the queue, because it's clean, easy to use, and safe, and if it really becomes a problem profile and test.

呆° 2024-08-08 18:22:12

我不断push_back新元素
while pop_front ing 最旧的元素
(大约一百万次)。

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

I continually push_back new elements
while pop_front ing the oldest element
(about a million time).

A million is really not a big number in computing. As others have suggested, use a std::queue as your first solution. In the unlikely event of it being too slow, identify the bottleneck using a profiler (do not guess!) and re-implement using a different container with the same interface.

枫林﹌晚霞¤ 2024-08-08 18:22:12

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

Check out std::queue. It wraps an underlying container type, and the default container is std::deque.

迎风吟唱 2024-08-08 18:22:12

如果性能确实很重要,请查看 Boost 循环缓冲区库。

Where performance really matters, check out the Boost circular buffer library.

往昔成烟 2024-08-08 18:22:12

使用 std::queue,但要认识到两个标准 Container 类的性能权衡。

默认情况下,std::queuestd::deque 之上的适配器。 通常,当您有少量队列包含大量条目(这可以说是常见情况)时,这将提供良好的性能。

但是,不要对 std::deque 的实现视而不见。 具体来说:

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

为了解决这个问题,假设队列条目是您想要排队的内容,即大小相当小,那么如果您有 4每个队列包含 30,000 个条目,std::deque 实现将是首选选项。 相反,如果您有 30,000 个队列,每个队列包含 4 个条目,那么 std::list 实现很可能是最佳的,因为您永远不会摊销 std::deque code> 在这种情况下的开销。

你会读到很多关于缓存为何为王、Stroustrup 如何讨厌链表等的观点,在某些条件下,所有这些都是正确的。 只是不要盲目相信它,因为在我们的第二种情况下,默认的 std::deque 实现不太可能执行。 评估您的使用情况并进行测量。

Use a std::queue, but be cognizant of the performance tradeoffs of the two standard Container classes.

By default, std::queue is an adapter on top of std::deque. Typically, that'll give good performance where you have a small number of queues containing a large number of entries, which is arguably the common case.

However, don't be blind to the implementation of std::deque. Specifically:

"...deques typically have large minimal memory cost; a deque holding just one element has to allocate its full internal array (e.g. 8 times the object size on 64-bit libstdc++; 16 times the object size or 4096 bytes, whichever is larger, on 64-bit libc++)."

To net that out, presuming that a queue entry is something that you'd want to queue, i.e., reasonably small in size, then if you have 4 queues, each containing 30,000 entries, the std::deque implementation will be the option of choice. Conversely, if you have 30,000 queues, each containing 4 entries, then more than likely the std::list implementation will be optimal, as you'll never amortize the std::deque overhead in that scenario.

You'll read a lot of opinions about how cache is king, how Stroustrup hates linked lists, etc., and all of that is true, under certain conditions. Just don't accept it on blind faith, because in our second scenario there, it's quite unlikely that the default std::deque implementation will perform. Evaluate your usage and measure.

英雄似剑 2024-08-08 18:22:12

这个案例很简单,您可以自己编写。 对于 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);}
};

This case is simple enough that you can just write your own. Here is something that works well for micro-conroller situations where STL use takes too much space. It is nice way to pass data and signal from interrupt handler to your main loop.

// 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);}
};
鱼忆七猫命九 2024-08-08 18:22:12

queue 可能是比 deque 但对于这么小的列表,性能差异可能可以忽略不计。

list 也是如此。 只需选择您想要的 API 即可。

A queue is probably a simpler interface than a deque but for such a small list, the difference in performance is probably negligible.

Same goes for list. It's just down to a choice of what API you want.

野の 2024-08-08 18:22:12

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

Why not std::queue? All it has is push_back and pop_front.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文