【问题标题】:Why would I prefer using vector to deque为什么我更喜欢使用向量到双端队列
【发布时间】:2011-07-17 17:44:43
【问题描述】:

自从

  1. 它们都是连续的内存容器;
  2. 功能方面,deque 几乎具备 vector 的所有功能,但更多,因为插入到前面更有效。

为什么有人更喜欢std::vector 而不是std::deque

【问题讨论】:

  • std::deque 的 Visual C++ 实现具有非常小的最大块大小(大约 16 字节,如果我没记错的话;可能是 32),因此对于实际应用程序来说表现不佳。 deque<T> 其中sizeof(T) > 8(或16?这是一个小数字)具有与vector<T*> 大致相同的性能特征,其中每个元素都是动态分配的。其他实现具有不同的最大块大小,因此使用deque 在不同平台上编写具有相对相同性能特征的代码是很困难的。
  • Deque 不是一个连续的内存容器。
  • @ravil 不,那是重复的,指向这个问题。
  • 很难相信一个带有未修复的明显事实错误的问题在 34 票中处于平衡状态
  • @underscore_d 这就是为什么它是一个问题。如果是答案,那就另当别论了;)

标签: c++ stl vector deque


【解决方案1】:

std::deque 不能保证连续的内存 - 索引访问通常会慢一些。双端队列通常被实现为“向量列表”。

【讨论】:

  • 我不认为“向量列表”是正确的:我的理解是大多数实现都是“指向数组的指针向量”,尽管这取决于您对“列表”的定义(我将“列表”读作“链表”,不符合复杂性要求。)
【解决方案2】:

双端队列是一个序列容器,它允许随机访问其元素,但不保证具有连续存储。

【讨论】:

    【解决方案3】:

    deque 中的元素在内存中连续; vector 元素保证是。因此,如果您需要与需要连续数组的普通 C 库进行交互,或者如果您(非常关心)空间局部性,那么您可能更喜欢vector。此外,由于有一些额外的簿记,其他操作可能(稍微)比它们等效的 vector 操作更昂贵。另一方面,使用vector 的许多/大型实例可能会导致不必要的堆碎片(减慢对new 的调用)。

    另外,正如elsewhere on StackOverflow 所指出的,这里有更多好的讨论:http://www.gotw.ca/gotw/054.htm

    【讨论】:

    • 似乎指向“其他地方”的链接现在已失效(由于审核?)。
    【解决方案4】:

    根据http://www.cplusplus.com/reference/stl/deque/,“与向量不同,双端队列不能保证其所有元素都位于连续的存储位置,因此消除了通过指针算法进行安全访问的可能性。”

    双端队列稍微复杂一些,部分原因是它们不一定具有连续的内存布局。如果您需要该功能,则不应使用双端队列。

    (以前,我的回答导致缺乏标准化(来自与上述相同的来源,“deques 可能由特定库以不同的方式实现”),但这实际上适用于几乎任何标准库数据类型。)

    【讨论】:

    • std::deque 的标准化程度不亚于std::vector。我不相信std::deque 的复杂性要求可以通过连续存储来满足。
    • 也许我的措辞很糟糕:虽然标准化并不彻底,但据我所知,向量被标准化为连续序列,而双端队列则不是。这似乎是一个决定因素。
    • @JerryCoffin:连续存储无法满足deque 的哪些复杂性要求?
    • @Mehrdad:说实话,我不记得我在想什么。我最近对标准的那一部分还没有足够的了解,以至于我很乐意明确地指出我之前的评论是错误的,但是现在看它,我也想不出它是正确的。
    • @JerryCoffin:复杂性要求实际上是微不足道的:您可以分配一个大数组并开始从中间向外推动您的序列(我想这就是 Mehrdad 实现所做的),然后在您到达时重新分配结束。这种方法的问题在于它不满足deque 的要求之一,即在末端插入不会使现有元素的引用 无效。这个要求意味着不连续的内存。
    【解决方案5】:

    我已经多次实现了向量和双端队列。从实现的角度来看,双端队列要复杂得多。这种复杂性转化为更多代码和更复杂的代码。因此,当您选择双端队列而不是向量时,通常会看到代码大小受到影响。如果您的代码仅使用向量擅长的东西(即 push_back),您也可能会遇到小速度影响。

    如果您需要一个双端队列,那么 deque 无疑是赢家。但是,如果您在后面进行大部分插入和擦除操作,vector 将是明显的赢家。当你不确定时,用 typedef 声明你的容器(这样很容易来回切换),然后测量。

    【讨论】:

    • 问题——委员会是否考虑过在 C++ 中添加两者的混合体(比如“甲板”)? (即双端的vector。)我在my answer 中编写了一个链接到下面的实现。它可以和vector 一样快,但适用范围更广(例如,在创建快速队列时)。
    【解决方案6】:

    我认为对每个案例进行性能测试是个好主意。并根据这些测试做出决定。

    在大多数情况下,我更喜欢std::deque 而不是std::vector

    【讨论】:

    • 问题是,如果可以从事实错误和遗漏的单词中提炼出任何问题,那就是为什么有人更喜欢vector。我们可以推断 why not 是一个推论。说你更喜欢deque,出于未知原因,来自未指定的测试,这不是答案。
    【解决方案7】:

    要知道区别,应该知道deque 通常是如何实现的。内存分配在大小相等的块中,它们链接在一起(作为数组或可能是向量)。

    因此,要找到第 n 个元素,您需要找到适当的块,然后访问其中的元素。这是一个常数时间,因为它总是恰好 2 次查找,但这仍然比向量多。

    vector 也适用于需要连续缓冲区的 API,因为它们要么是 C API,要么在能够获取指针和长度方面更加通用。 (因此您可以在下面有一个向量或一个常规数组,并从您的内存块中调用 API)。

    deque最大的优势在于:

    1. 从任一端扩大或缩小集合时
    2. 当您处理非常大的集合时。
    3. 在处理 bool 时,您确实需要 bool 而不是 bitset。

    其中第二个鲜为人知,但适用于非常大的集合:

    1. 重新分配的成本很大
    2. 必须找到连续内存块的开销是有限的,因此您可以更快地耗尽内存。

    当我过去处理大型集合并从连续模型转移到块模型时,在 32 位系统中内存不足之前,我们能够存储大约 5 倍大的集合。这部分是因为在重新分配时,它实际上需要在复制元素之前存储旧块以及新块。

    说了这么多,你可能会在使用“乐观”内存分配的系统上遇到std::deque 的麻烦。虽然它为重新分配vector 请求较大缓冲区大小的尝试可能会在某个时候被bad_alloc 拒绝,但分配器的乐观性质可能总是会批准由a 请求的较小缓冲区的请求。 deque 这可能会导致操作系统终止进程以尝试获取一些内存。无论它选择哪一个都可能不会太令人愉快。

    在这种情况下,解决方法是设置系统级标志以覆盖乐观分配(并不总是可行)或更加手动地管理内存,例如使用您自己的分配器来检查内存使用情况或类似情况。显然不理想。 (这可能会回答您更喜欢矢量的问题...)

    【讨论】:

      【解决方案8】:

      根据these test results (with source).,你不会更喜欢向量而不是双端队列

      当然,您应该在您的应用/环境中进行测试,但总而言之:

      • push_back 基本上都是一样的
      • 在双端队列中插入、擦除比列表快得多,比向量快一点

      Some more musings, and a note to consider circular_buffer.

      【讨论】:

        【解决方案9】:

        一方面,vector 经常比 deque 快。如果您实际上并不需要双端队列的所有功能,请使用向量。

        另一方面,有时您确实需要向量无法提供的特征,在这种情况下,您必须使用双端队列。例如,我挑战任何人尝试重写this code,而不使用双端队列,并且不大幅改变算法。

        【讨论】:

        • 实际上,在我的测试中,deque<int> 在同一系列的push_backpop_back 操作上总是至少比vector<int> 快20%(gcc 和O3)。我想这就是为什么dequestd::stack 之类的标准选择...
        【解决方案10】:

        请注意,向量内存会随着数组的增长而重新分配。如果你有指向向量元素的指针,它们就会失效。

        另外,如果你删除一个元素,迭代器就会变得无效(但不是“for(auto...)”)。

        编辑:将“双端队列”更改为“向量”

        【讨论】:

        • -1:在开始或结束处插入和擦除不会使指向其他元素的引用和指针无效。 (虽然中间插入和擦除可以)
        • @Sjoerd 我把它改成了“向量”。
        • push_back()emplace_back()std::deque指针稳定性 是我在std::vector 上考虑它的原因之一,例如在插入指向这些元素的指针时作为值进入地图。
        猜你喜欢
        • 2013-12-13
        • 2010-10-29
        • 2020-04-19
        • 1970-01-01
        • 2015-05-01
        • 1970-01-01
        相关资源
        最近更新 更多