【问题标题】:What happens under the hood of vector::push_back memory wise?在 vector::push_back 内存明智的情况下会发生什么?
【发布时间】:2023-03-20 22:15:02
【问题描述】:

我的问题是关于vector::push_back 的效果,我知道它在向量的末尾添加了一个元素,但引擎盖下会发生什么?

IIRC 内存对象是按顺序分配的,所以我的问题是vector::push_back 是否只是在向量之后立即分配更多内存,如果是这样,如果该位置没有足够的可用内存会发生什么情况?或者也许在“结束”中添加了一个指针以使向量“跳跃”到它继续的位置?或者它只是通过将其复制到另一个有足够空间的位置而重新分配并且旧副本被丢弃?还是别的什么?

【问题讨论】:

    标签: c++ vector push-back


    【解决方案1】:

    如果已经分配了足够的空间,则对象是从就地参数复制构造的。当内存不足时,向量将按照某种几何级数增长其内部数据缓冲区(每次新大小将是 k*old_sizek > 1[1])并且所有对象都存在然后将在原始缓冲区中移动到新缓冲区。操作完成后,旧缓冲区将释放给系统。

    在上一句中,move 不是在技术上使用 move-constructor/move-assignment,它们可能是 移动复制或任何等效操作。

    [1] 增长因子k > 1 确保push_back 的摊销成本不变。实际常量因一种实现而异(Dinkumware 使用 1.5,gcc 使用 2)。摊销成本意味着即使经常有一个push_back 会非常昂贵(O(N) 在当时的向量大小上),这些情况很少发生,以至于整个插入集的所有操作的成本插入次数是线性的,因此每次插入平均成本不变)

    【讨论】:

    • 好吧,在那种情况下,通过使用指向其片段的指针对矢量进行“片段化”以便它仍然可以迭代,这不是更有效吗,这是巨大矢量的情况,谁的重新分配会导致明显的瓶颈?是否有这样的选项,或者我将不得不实现我自己的向量版本?丢失几个 int 值的指针和几个跃点会超过重新分配的代价吗?
    • @ddriver:那是一根绳子。经过一番反复,STL 实现者同意 std::vector 保证连续内存。绳索也没有按索引进行 O(1) 访问,具体取决于段数。
    • @ddriver:这是Deque 通常实现(在数据块之间使用指针)。
    • @Vlad:这会使参考无效。您可以参考,但您必须注意容器的失效规则。
    • 对象未就地构造。它被复制到位并且复制操作可以被编译器省略。这是非常不同的。 vector::emplace 的 C++11 结构。
    【解决方案2】:

    当vector空间不足时,它将使用它的分配器来预留更多空间。

    由分配器决定如何实现。

    但是,向量决定了要保留多少空间:标准保证向量容量至少增长 1.5 倍1几何上(见评论) ,从而防止由于重复的“小”分配而导致糟糕的性能。

    关于元素的物理移动/复制:

    • 符合 c++11 的实现将移动元素,如果它们支持移动分配和构造
    • 我所知道的大多数实现(尤其是 g++)只会将 std::copy 用于 POD 类型; POD 类型的算法特化确保它编译成(本质上)一个 memcpy 操作。这反过来会在您系统上最快的 CPU 指令中编译(例如 SSE2 指令)

    1 我尝试从 n3242 标准草案文档中找到它的参考引用,但我此时无法找到它

    【讨论】:

    • 该标准仅保证(通过复杂性要求间接地)增长是几何的,而不是算术的。它没有对乘数设置任何最低要求;增长了 1.1 倍的实现将是一致的。
    • @JamesKanze:啊,这说明我找不到它。当我发现 push_back 的复杂性要求时,我有点好奇(让我想起了stackoverflow.com/questions/7554039/…
    【解决方案3】:

    向量保证所有元素在内存中都是连续的。

    在内部你可以认为它定义为三个指针(或类似指针的行为):

    start:     Points at the beginning of the allocated block.
    final:     Points one past the last element in the vector.
               If the vector is empty then start == final 
    capacity:  Points one past the end of allocated memory.
               If final == capacity there is no room left.
    

    当你向后推时。

    1. 如果 final 小于容量:
      • 新元素被复制到final指向的位置
      • final 递增到下一个位置。
    2. 如果 final 与容量相同,则向量已满
      • 必须分配新内存。
      • 然后编译器将分配X*(capacity - start)*sizeof(t) 字节。
      • 其中 X 通常是介于 1.5 和 2 之间的值。
      • 然后它将所有值从旧内存缓冲区复制到新内存缓冲区。
      • 新值被添加到缓冲区中。
      • 传输开始/最终/容量指针。
      • 释放旧缓冲区

    【讨论】:

      【解决方案4】:

      vector 空间不足时,它会被重新分配,所有元素都被复制到新数组中。然后销毁旧数组。

      为避免过多的分配并将平均push_back() 时间保持在O(1),重新分配要求大小至少增加一个常数因子。 (1.5和2很常见)

      【讨论】:

      • @James Kanze:感谢您的回复。我会相应地进行编辑。
      • 嗯。我正在查看相同的规格。无论如何,“它被重新分配”至少掩盖了分配器的概念。我不觉得事情可能是正确的地方是它说'旧数组然后被销毁'无条件。如果分配器,例如,这似乎是非常低效的事情。使用类似于 realloc() 的东西来实现它的目标。恢复我的 -1 直到有人发现更多的光,虽然
      • @sehe:realloc() 的好点子。我也不确定这个的状态。
      • 标准容器使用的Allocator接口中没有类似realloc的操作。 vector 必须分配一个新数组,复制内容,然后释放旧数组。同意,这是低效的,因为分配器可能已经能够就地扩展现有的分配,但它没有机会。在使用默认分配器的向量的情况下,我必须仔细查看标准来判断允许实现的位置,以便在那里引入重新分配样式的优化。
      • @SteveJessop 总是有“好像”的规则。该标准确实要求默认分配器使用operator new 来获取内存,这意味着用户定义的operator new 可以看到,但明确表示“未指定此函数何时或多久执行一次[operator new]叫做。”这听起来有足够的余地来进行这种优化。
      【解决方案5】:

      当您调用vector::push_back 时,end 指针与 capacity 指针进行比较。如果有足够的空间容纳新对象placement new,则调用在可用空间中构造对象,并增加end指针。

      如果没有足够的空间,vector 调用它的 allocator 至少为现有元素和新元素分配足够的连续空间(不同的实现可能会通过不同的乘数增加分配的内存) .然后将所有现有元素加上新元素复制到新分配的空间。

      【讨论】:

        【解决方案6】:

        std::vector overallocates - 它通常会自动分配比需要更多的内存。 size不受此影响,但您可以通过capacity进行控制。

        如果额外容量不足,

        std::vector 将复制所有内容

        std::vector 分配的内存是原始的,没有按需调用构造函数,使用placement new。

        所以,push_back 可以:

        • 如果容量不足以容纳新元素,它将
          • 分配一个新块
          • 复制所有现有元素(通常使用复制构造函数)
        • 将大小增加一
        • 将新元素复制到新位置

        【讨论】:

          【解决方案7】:

          如果您对数组的最终大小有所了解,请先尝试vector::reserve 内存。请注意,reservevector::resize 不同。使用reserve,您的数组的vector::size() 不会更改

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-05-19
            • 2011-07-23
            • 2021-06-30
            • 1970-01-01
            • 1970-01-01
            • 2012-03-22
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多