【发布时间】: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