【问题标题】:Which C++ stl container should I use?我应该使用哪个 C++ stl 容器?
【发布时间】:2018-12-03 18:58:06
【问题描述】:

想象一下以下要求:

应该记录测量数据,并且用户应该能够遍历数据。

uint32_t timestamp;
uint16_t place;
struct SomeData someData;
  • 在结构中包含时间戳 (uint32_t)、地点 (uint16_t) 和一些数据

  • 具有恒定数量的数据集。如果有新的到来,最旧的就会被扔掉。

  • “place”的数量是动态的,用户可以在运行时插入新的

  • 应该可以将数据迭代到下一个较新或较旧的数据集,但前提是位置相同

  • 只需要在末尾插入

  • 内存应该在程序启动时分配一次

  • 插入不需要很快,但不应长时间阻塞其他可能正在迭代容器的线程

  • 内存要求应该很低

编辑: - 容器应该是所有未使用的内存,因此它可能很大。

我不确定我应该使用哪个容器。它是一个嵌入式系统,不应该使用 boost 等。

我看到以下可能性:

std::vector - 缺点:最后的插入要求复制所有对象,在此期间另一个线程无法访问该向量。编辑:这可以通过将其实现为循环缓冲区来避免 - 请参阅下面的 cmets。遍历向量时,我必须测试地点 ID。也许将大量内存分配为一个块也可能是一个问题 - 因为内存可以分段?

std::deque - 与std::vector 插入(和pop_back)相比更快,但内存要求?如果插入在末尾,迭代器不会变为无效。但我仍然需要迭代和测试第二个 ID(“地点”)。我认为它不需要像向量或数组那样在一个大块中分配所有内存。如果在前面添加一个元素,最后删除另一个元素(或先删除后添加),我猜不会发生内存分配?

std::queue - 我宁愿使用队列而不是双端队列?在许多实现中,队列是否真的像双端队列一样实现?

std::map - 与 deque 一样,任何现有元素的迭代器都不会变得无效。如果我将键设置为位置和时间戳的组合,那么通过地图的迭代可能会更快,因为它已经排序?地图的内存需求?

std::multimap - 由于地点的数量不是恒定的,我无法制作以“地点”为索引的多地图。

std::list - 这里比 deque 没有优势?

有些人建议使用循环缓冲区。如果我不想将内存分配为一个大块,我仍然必须使用容器,并且上面的大多数问题仍然有效。

更新: 我将按照这里的建议使用环形缓冲区,但使用双端队列作为底层容器。为了能够使用预选的“位置”快速滚动数据集,我最终将在数据结构中引入两个额外的索引,它们将指向具有相同位置的上一个和下一个索引。

将使用多少内存?在我的特殊情况下,结构的大小是 56 字节。 gnu lib 使用 512 字节作为最小块大小,IAR 编译器使用 16 字节。因此,使用的块大小将分别为 512 或 56 字节。除了两个迭代器(每个使用 4 个指针)和大小之外,还将为每个块存储一个指针。因此,与使用 std::vector 或数组相比,在 iar 编译器(块大小 56 字节)的实现中(在 32 位系统上)将有 7% 的开销。在 gcc 实现中,块中将容纳 9 个对象(504 个字节),而每个块需要 512 + 4 个字节,多出 2%。

块大小并不大,但指针数组所需的连续内存大小已经比较大了,特别是对于一个块为一个结构的实现。

一个 std::list 每个结构需要 2 个指针,在我的情况下,在 32 位系统上是 14% 的开销。

【问题讨论】:

  • 听起来你想要一本circular buffers的字典。
  • "最后的插入要求复制所有对象" 如果要保持恒定数量的元素,为什么还要插入东西?只需分配您需要的任意数量的插槽并像环形缓冲区一样使用向量。如果元素的数量是编译时间常数,您甚至可以只使用 std::array。
  • 您可以基于预分配的std::vector(参见std::vector::reserve())实现循环缓冲区。
  • 是的,你们都是对的。谢谢你。我没有考虑ringbuffer,因为一开始的分配是一个新的要求。
  • 使用循环缓冲区会导致快速访问的问题,我不确定是否应该使用数组或向量作为底层容器,因为大内存块可能不可用?

标签: c++ dictionary thread-safety containers deque


【解决方案1】:
  1. std::vector

    ...内存可以分段吗?

    不,std::vector 分配连续内存,如该链接中所述。数组也是连续的,但你也可以使用向量。

  2. std::deque 是分段的,你说你不想要。或者你想避免一个大的分配块?不清楚。

    无论如何,如果你真的想要一个循环缓冲区(因为你永远不会从前/后添加/删除元素),它对向量没有任何好处,而且你无法控制块大小。

  3. std::queue

    ...在许多实现中,队列是作为双端队列实现的,这是真的吗?

    是的,这是 all 实现中的默认设置。请参阅链接文档或任何体面的书籍。

    听起来您并不想要一个 FIFO 队列,所以我不知道您为什么要考虑这个 - 接口不符合您声明的要求。

  4. 'std::map`

    ...遍历地图可能更快,因为它已经排序?

    在大多数现代服务器/桌面架构上,map 会更慢,因为推进迭代器涉及指针追逐(这会损害流水线)和可能的缓存未命中。您的匿名嵌入式架构可能对这些影响不太敏感,因此 map 可能对您来说更快。

    ...地图的内存要求?

    更高。您为每个元素添加了节点大小(至少几个指针)。

【讨论】:

  • 我想避免使用一大块内存,因为我担心内存可能已经被分段了。我不确定这是否会成为问题,或者内存管理是否会处理此类事情。在这种特殊情况下,它将是唯一的应用程序,我想这根本不是问题。系统是ARM cortex M4,编译器是gcc或者iar。
  • 您写了“...而且您无法控制块大小。”。但我会假设该实现使用了一个合理的块大小,这比没有块大小要好。
  • 在嵌入式系统上,实现如何才能知道对于您的目的而言合理的块大小是多少?它使用 some 块大小,这在客观上可能并不愚蠢,但如果您担心分配大小,这仍然对您没有帮助。
  • 将用于缓冲区的内存大约为。 100 兆字节。因此,一个向量将一次分配 100 MB。据我所知,双端队列的块大小会小得多。在大多数实现中,它将是结构的大小,大约为 100 字节。
  • 双端队列的总分配大小也将更大,假设您的结构最终会填满(听起来会这样)。如果您需要分配大约 100 万个 100 字节的对象,那么无论您使用哪个容器,最终都会分配至少 100M,并且除了数组或向量之外的任何东西都会更大。也许您可以在问题中编辑细节,而不是在 cmets 中滴灌它们?
猜你喜欢
  • 2012-11-23
  • 2010-11-18
  • 1970-01-01
  • 2012-03-04
  • 1970-01-01
  • 2010-12-06
  • 2012-05-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多