【问题标题】:What container to choose for fast search/insert with huge amounts of data?选择什么容器来快速搜索/插入大量数据?
【发布时间】:2015-02-02 01:09:29
【问题描述】:

所以这是一个思想实验。我想要大量的结构,例如:

struct
{
    KeyType key;
    ValueType value;
}

而且我需要通过键快速访问并快速插入新值。

我不会使用 std::map 因为它对于一个结构有太大的内存开销,而对于大量数据来说它可能是巨大的。对吧?

所以接下来我会考虑使用排序的 std::vector 和 binary_search。搜索很好,但是向向量添加新值会太慢。想象一下,您需要在排序数组的开头添加一个新值,您必须将数据向右移动 aaaaaAAAALOT!

如果我使用双端队列会怎样?据我所知,push_back/push_front 有 O(1),但插入仍然有 O(n)(因为无论如何它都必须移动数据,但数据更少)。

问题是:

1) 在实际情况下,在双端队列中插入数据的 O(n) 是否比在向量中的 O(n) 快得多?

2) 当你向 Deque 插入一个值并且它应该进入的存储桶已满时会发生什么?

3) 如果您需要存储大量数据并需要两个快速操作:搜索和插入,是否还有另一种更可取的容器类型?

谢谢!

【问题讨论】:

  • 首先按类型指定搜索类型以及预期成员和操作的计数:插入、删除、搜索(不同类型 - 第九旧、精确键、最近键...)
  • 虽然时间复杂度很重要,但内存层次结构也可以支配性能。理论分析只能缩小我们的选择范围。测量是确定选择哪一个的唯一实用方法。

标签: c++ algorithm vector deque


【解决方案1】:

我不会使用 std::map 因为它对于一个结构有太大的内存开销,而对于大量数据来说它可能是巨大的。对吧?

这取决于你的结构体的大小……它们越大,开销在总内存使用中所占的比例就越少。例如,std::map 实现可能平均每个元素包含 20 个字节的内务数据(我只是编造的 - 在您自己的系统上测量),所以如果您的结构大小为数百字节 - 谁在乎......?但是,如果结构中包含 2 个ints,那么它的比例很大......

所以接下来我会考虑使用排序的 std::vector 和 binary_search。搜索很好,但是向向量添加新值会太慢。想象一下,您需要在排序数组的开头添加一个新值,您必须将数据向右移动 aaaaaAAAALOT!

完全不合适....

1) 在实际情况下,在双端队列中插入数据的 O(n) 是否比在向量中的 O(n) 快得多?

由于deque 很可能实现为固定大小数组的向量,因此插入意味着将所有元素改组到容器的最近端。改组的缓存效率可能会稍低一些,但如果插入更靠近容器的前端,它可能仍然会更快。

2) 当你向 Deque 插入一个值并且它应该进入的存储桶已满时会发生什么?

如上所述,它需要洗牌,溢出:

  • 最后一个元素成为下一个“桶”的第一个元素,将所有这些元素移动并溢出到下一个桶中,等等。

  • 第一个元素成为前一个桶的最后一个元素,将所有这些元素移动并溢出到下一个桶中,等等。

3) 如果您需要存储大量数据并需要两个快速操作:搜索和插入,是否还有另一种更可取的容器类型?

unordered_map,实现为哈希映射。如果您有小对象(例如小于 20 或 30 字节)或元素数量有严格的上限,您通常可以使用自定义代码轻松超越 unordered_map,但除非表访问支配您的应用程序性能,否则几乎不值得付出努力,而这种性能至关重要。

【讨论】:

    【解决方案2】:

    3) 如果您需要存储大量数据并需要两个快速操作:搜索和插入,是否还有另一种更可取的容器类型?

    考虑使用std::unordered_map,它是哈希映射的一种实现。在一般情况下,插入、查找和删除都是 O(1)。这假设您只会根据其确切的密钥来查找项目;如果您的搜索可能有不同的约束,那么您要么需要不同的结构,要么需要多个映射来将要搜索的各种键映射到相应的对象。

    这要求KeyType 有一个可用的哈希函数,作为标准库的一部分或由您提供。

    【讨论】:

      【解决方案3】:

      没有任何容器可以为您提供世界上最好的东西。就像您说的那样,您希望在存储元素所需空间最少的情况下进行最佳查找/插入。

      如果您可以考虑实施的容器列表如下:-

      矢量:-

      优势:-

      1) Space is allocated only for holding data. 
      2) Good for random access.
      3) Container of choice if insertions/deletions are not in the middle of the container.
      

      弱点:-

      1) poor performance if insertions/deletions are at the middle.
      2) rellocations happen if reserve is not used properly.
      

      双端队列:-

      如果插入/删除在容器的开头和结尾,请选择双端队列而不是向量。

      地图:-

      相对于矢量的缺点:-

      1) more space is allocated for holding pointers.
      

      相对于矢量的优势:-

      1) better insertions/deletions/lookup as compared to vector.
      

      如果使用std::unordered_map,那么这些字典操作将摊销 O(1)。

      【讨论】:

      • 与向量 imho 相比,deque 的另一个 大 优势是避免了重新分配。
      • @geoalgo:你确定吗?据我所知,在双端队列中插入和删除会使指针和迭代器失效,因此允许重新分配。
      • @MikeMB:geoalgo 夸大了好处,但在向前或向后推动时,在不移动现有元素的情况下,重新分配的效果可能不那么显着。即使没有重新分配,指针/迭代器也可能无效,因为元素被洗牌到已分配内存中的不同插槽中(就像向量插入使指向插入点之后的元素的指针/迭代器无效,即使没有重新分配)。
      【解决方案4】:

      首先,为了直接回答您的问题:

      1) 在实际情况下,在双端队列中插入数据的 O(n) 是否快得多 比向量中的 O(n)?

      与向量相比,必须移动的元素数量(平均)只有一半。但是,由于数据存储在不连续的内存中,它实际上性能会更差,因此复制/移动相同数量的元素效率要低得多(例如,它不能通过单个 memcopy 操作来实现)。

      2) 当你将一个值插入到 Deque 和存储桶时会发生什么 应该进去是满的吗?

      至少对于 gnu gcc Libstdc++ 实现,除了第一个和最后一个之外的每个桶总是满的。我相信,在中间插入意味着所有元素都被移动/复制一个插槽到更近的一端(前端或后端),效果会波及所有存储桶,直到到达第一个或最后一个。

      总而言之,std::deque 始终优于 vector 的唯一情况是,如果您将其用作(惊喜)队列(仅从前端或末端插入和删除元素),这就是优化的实现为了。它没有针对中间插入进行优化。

      3) 如果您需要,是否还有另一种更可取的容器类型 存储大量数据,需要两个快速操作:搜索和插入?

      正如其他人已经说过的:像 std::unordered_map 这样的哈希表是您正在寻找的数据结构。

      然而,据我所知,std::unordered_map 是一个稍微次优的实现,因为它使用桶来解决哈希冲突,并且这些桶被实现为链表(here 是一个非常有趣的谈话来自 Chandler Carruth 关于不同数据结构性能的一般主题)。对于大数据结构上的随机访问,缓存局部性应该不那么重要,所以在你的情况下这可能不是一个大问题。

      最后我想提一下,如果您的值和键类型是小型 POD,并且取决于您的庞大集合有多大(我们谈论的是几百万或数十亿个元素)以及您实际必须插入的频率/remove 元素,可能仍然存在简单的 std::vector 优于任何其他 STL 容器的情况。一如既往:如果您的思想实验成为现实,请尝试并衡量。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-11-10
        • 2019-11-24
        • 2011-06-28
        • 2019-10-04
        • 2013-05-03
        • 1970-01-01
        • 2010-09-23
        • 2012-08-06
        相关资源
        最近更新 更多