【问题标题】:FIFO type of container - Which STL Container is most suitable and why?FIFO 类型的容器 - 哪种 STL 容器最适合,为什么?
【发布时间】:2011-11-04 20:25:36
【问题描述】:

我最近的任务是实现一个缓冲区,该缓冲区将被日志类用作临时存储。日志记录类本身是一个单例,并且使用了观察者侦听器模式。您可以期望使用此类记录数以千计的消息。

现在问题出在这里:

我们有一个用于调试目的的跟踪日志记录选项。启用此选项后,每秒消息数会以指数方式增加。在发布代码中,跟踪记录被禁用,但是可以存储固定数量的消息的缓冲区,例如10000 被转储到日志中如果发生错误,以便开发人员找出问题的根源。

如果缓冲区已满,则删除最旧的消息,以便为最新消息释放空间。

void Log::storeToBuffer(const LogType_E & type_in, const LogDomain_E & domain_in,const int & id_in, const char * msg_in)
{
    if(this->myEnableTraceBuffer)
    {
        if(static_cast<std::list<Message> * >(this->myRingBuffer)->size() < this->myRingBufferMaxSize)
        {
            static_cast<std::list<Message> * >(this->myRingBuffer)->push_back(Message(type_in, domain_in, id_in, msg_in));
        }
        else
        {
            //buffer full so remove oldest element and add new
            if(static_cast<std::list<Message> * >(this->myRingBuffer)->size() > 0) static_cast<std::list<Message> * >(this->myRingBuffer)->pop_front();
            static_cast<std::list<Message> * >(this->myRingBuffer)->push_back(Message(type_in, domain_in, id_in, msg_in));
        }
    }
}

我使用 std::list 实现了这一点,非常简单地使用 push_back/pop_front 来利用恒定的删除/插入执行时间。 (不要要求空投,不是我的决定)。

但是由于缓冲区大小是固定的,并且在对象的生命周期内不太可能改变,也许带有显式索引操作的向量更合适?例如,可以有两个 indeces , start/current 都从位置 0 开始。当向量已满时,我们添加一些东西 start 移动到位置 1 和 current 到位置 0,因此在打印结果时我们有正确的顺序。

也许另一个 STL 容器更适合这种东西?

感谢您耐心阅读这长长的文字墙。我在这里回答任何问题。

【问题讨论】:

  • 这实际上是 FIFO 而不是 FILO/LIFO。
  • @ChristianRau 是的,你完全正确。
  • 只是好奇,你为什么要把日志保存在内存中?如果发生崩溃,您的日志会在您最需要的时候消失。
  • @AndersK。我们只是将跟踪消息保存在内存中,而不是日志本身。当您有数百万条消息时,它会严重影响性能,因此跟踪消息仅用于调试目的。
  • 是的,感谢您提供的信息,祝您愉快。

标签: c++ stl containers


【解决方案1】:

std::deque 可以有效地在开头或结尾插入或删除,使其成为一个出色的缓冲区结构。它可以用作std::queue 的模板类型,非常适合您的应用程序 - 每次执行push 时检查队列的大小,如果大小超过最大值,则检查pop

【讨论】:

    【解决方案2】:

    听说过std::deque吗? :)
    它允许在双方进行摊销的恒定时间随机访问和恒定时间推送/弹出操作。因此,它可以很容易地用作 FIFO 队列。

    也就是说,因为它可以很容易地使用,所以有一个容器适配器std::queue,尽管它不提供随机访问和迭代器接口。

    【讨论】:

    • 是的,我考虑过这个选项。这与当前的 std::list 实现有什么不同吗?我会看到任何性能提升吗?
    • @FailedDev, std::list 为每个元素保留一对链接指针。如果这些指针相对于您要存储的项目很大,则可能会产生影响。 std::deque 也允许有效的随机访问,但它似乎不是你使用的要求。
    • 哦,是的,非常如此。 std::deque 实际上是展开的 std::list
    • @MarkRansom 是的,我根本不需要随机访问。
    • It (std::queue) 不仅不提供随机访问,而且不提供迭代,我认为这可能是输出逻辑所需要的。当然你可以不断地弹出,但这会破坏容器。
    【解决方案3】:

    虽然 std::list 的 push_back/pop_front 是常数时间,但它们将使用堆分配,这可能是您的瓶颈。

    您使用 std::vector 描述的方法将消除堆分配,因此可能会更快。 使用你描述的 std::vector 是一个循环缓冲区,如果你可以使用 boost 那么这里有一个兼容 stl 的缓冲区:http://www.boost.org/doc/libs/1_47_0/libs/circular_buffer/doc/circular_buffer.html

    【讨论】:

    • 感谢您的宝贵时间。不幸的是,我不能在这个项目中使用 boost。
    • 很遗憾 - 我认为 std::deque 最终仍会分配大量新块并释放旧块,即使您将获得更好的内存局部性,因为它将是以块的形式分配,并且不会一直维护前向/后向指针。如果性能对您来说真的很重要,那么使用您所描述的向量实现循环缓冲区可能是可行的方法。但是,根据您的 malloc() 实现和应用程序中正在进行的其他分配,好处可能非常微不足道。
    【解决方案4】:

    您正在寻找的是一个双端队列,其中可以从前/后添加元素,并且 C++ 标准库的版本是 std::deque。它在功能上类似于std::vector(例如通过索引访问,但要注意,不能保证连续存储),但支持前后快速插入和删除。我认为在内部它实际上是作为一个普通数组实现的,就像一个环形缓冲区。

    我认为,我在这里看到的唯一问题是它没有提供reserve 方法。所以你不能从一开始就说它应该为你潜在的 10000 个元素腾出空间,并且必须考虑一些重新分配。但是这些应该摊销,就像向量一样。仍然比 std::list 的 10000 或更多小分配要好。一旦它的大小达到 10000 个元素,就不应该再进行任何分配,而 std::list 会不断地解除分配并分配新的对象。

    实际上你也可以使用std::queue,它默认使用std::deque作为它的底层容器类型,并且只提供FIFO队列所需的少数功能。但在这种情况下,您将无法访问单个元素或迭代所有元素,这可能是您的输出逻辑所需要的。

    【讨论】:

      猜你喜欢
      • 2010-11-18
      • 2021-12-04
      • 2010-11-23
      • 2010-10-16
      • 2011-09-12
      • 2010-12-06
      • 2011-06-23
      • 2010-10-17
      • 2017-05-30
      相关资源
      最近更新 更多