【问题标题】:When does a std::vector enlarge itself when we push_back elements?当我们 push_back 元素时,std::vector 什么时候会自我放大?
【发布时间】:2018-01-06 06:26:13
【问题描述】:

根据以下测试,std::vector<int> 似乎是通过这种方式增加容量的:

  • 发生在我们push_back()并且容量已经满(即v.size() == v.capacity())的时候,需要注意一点之前不会发生 p>

  • 容量增加到之前容量的1.5倍

问题:为什么是这个 1.5 系数?它依赖于实现吗?是最优的吗?

另外,有没有办法在这段代码中分析重新分配的确切时间? (有时可能不需要移动数组的第一部分就可以增加容量)


vector<int> v;
int previouscapacity = 0;
for (unsigned int i = 0; i < 1000000; i++)
{
    v.push_back(i);
    if (v.capacity() != previouscapacity)
    {
        wcout << L"new capacity: " << v.capacity() << L" new size: " << v.size() << L" ratio: " << ((float) v.capacity()) / previouscapacity << '\n';
        previouscapacity = v.capacity();
    }
}

新容量:1 新尺寸:1 比率:1.#INF
新容量:2 新尺寸:2 比率:2
新容量:3 新尺寸:3 比率:1.5
新容量:4 新尺寸:4 比率:1.33333
新容量:6 新尺寸:5 比率:1.5
新容量:9 新尺寸:7 比率:1.5
新容量:13 新尺寸:10 比率:1.44444
新容量:19 新大小:14 比率:1.46154
新容量:28 新大小:20 比率:1.47368
新容量:42 新尺寸:29 比率:1.5
新容量:63 新尺寸:43 比率:1.5
新容量:94 新大小:64 比率:1.49206
新容量:141 新尺寸:95 比率:1.5
新容量:211 新大小:142 比率:1.49645
...
新容量:466609 新大小:311074 比率:1.5
新容量:699913 新大小:466610 比率:1.5
新容量:1049869 新大小:699914 比率:1.5


注意:我使用的是 VC++ 2013

【问题讨论】:

  • 增长率取决于 AFAIK 的实现。将尺寸翻倍很受欢迎。您可以要求capacity 了解在需要重新分配之前可以容纳多少元素。
  • 作为补充说明,如果您知道将有多少个元素,请在向量上使用reserve()
  • 不要看你正在打印的那个比例。这是因为容量增加1.5倍后发生了一些四舍五入。例如 3*1.5=4.5 但新容量仍然是 4。而且,在扩展过程中总是使用复制构造函数复制向量。这就是 O(1) 加法被摊销的原因。
  • @AakashVerma 比率舍入并不重要。当大小足够大时,这里约为 1.5。

标签: c++ arrays vector


【解决方案1】:

就像对链接问题What is the ideal growth rate for a dynamically allocated array? 的回答一样,总是将分配的大小加倍存在一个问题,即释放的内存对于下一次分配来说总是只是太小了。向量将在堆中“徘徊”,留下许多碎片。

最大化重用的“最佳”重新分配大小原来是 golden ratio,即 1.61803...

但是,1.5很多更容易计算为capacity() + capacity() / 2,并且在实践中足够接近。这使其成为现有实现的流行选择。

【讨论】:

  • 我不同意。黄金比例更容易计算。您只需获取斐波那契数列的两个连续元素,然后将它们相加得到下一个(或减去得到前一个),您很快就会得到黄金比例。只有求和,没有乘法。我什至用它来精确地实现它(何时增加向量的容量)。如果你划分两个相邻的斐波那契元素,你会得到一个近似值 phi
【解决方案2】:

另外,有没有办法在这段代码中分析重新分配的确切时间? 当您向后推并且没有足够的容量时会发生这种情况。没有什么可分析的。

为什么是这个 1.5 因子?它依赖于实现吗?是最优的吗?

增长因子必须是指数的。它不必是统一的。 2 很受欢迎,因为它导致“平均每个元素被复制一次”。

2 的问题在于,带有重新分配的行进向量永远不会为下一次重新分配留下足够的空间来适应,因为 1+2+4+8 7.7,所以从长远来看,以前释放的空间足以容纳新需要的空间。

如果您不断增长的向量是内存的主要消耗者,则在 2 倍缩放以下的一半内存将被丢弃剩余的旧向量缓冲区。在 1.5 以下,丢弃的缓冲区会增长,并最终变得足够大,可以回收用于新的分配(假设它们是连续的)。

【讨论】:

  • 我不太明白。我认为使用realloc() 可以最大限度地减少此类问题。如果当前块后面有空格,则直接放大,不复制。如果没有空间,realloc 调用当然会移动数据块。然而,被释放的块(如果这种情况发生多次)无论如何都不会是连续的。那么1.5或2是缓冲区扩大的因素都没有关系......内存无论如何都是碎片化的。
  • @andreas 向量通常不会重新分配。我相信他们可以使用可简单复制的类型,并且标准分配器和证明 new 在 as-if 规则下不会重载,但我不知道向量会这样做。 C++ 中的 realloc API 很复杂,因为大多数类型都很难复制。您实际上需要“尽可能扩展,否则失败”,因为复制字节是不够的。
  • 这没有任何意义。如果 realloc 成功,什么都不做,只需要构造新元素(但无论如何都需要完成)。在另一种情况下,这里讨论的 free 和 malloc 方案没有区别,
  • 经过一番谷歌搜索后,我发现了这个:stackoverflow.com/questions/3105001/…。 c++ 分配器中似乎没有realloc 函数。有人称之为设计缺陷,我倾向于同意。
  • @andreas realloc 函数扩展分配按字节将分配复制到新缓冲区并释放旧缓冲区。当你说 realloc 时,我假设你的意思是 realloc,而不是“realloc-like”。
【解决方案3】:

系数 1.5 由 Microsoft 的 STL 实施团队决定。根据他们的说法,1.5 到 2 的系数是速度和内存优化的最佳解决方案。
您可以查看说明here,从第15分钟开始。

【讨论】:

  • 在 1.5 之前增加内存容量或超时,或其他一些东西不是微软的发明。好的。现在 Basj 可以观看有关该内容的讲座了......是否有“有没有办法在这段代码中分析重新分配的确切时间?”的答案?
【解决方案4】:

我认为为什么因子 1.5 优于 2.0 的答案的一个重要方面是标准 c++ 分配器模型支持realloc 函数:

Realloc- in Standard allocator

如果它存在,并且将在 std::vector 中使用,那么在因子 1.5 优于例如2.0 倍的容量增加。

当缓冲区总是在每次缓冲区大小增加时重新定位(正如其他答案所解释的那样)时,1.5 的因子(或更准确地说是黄金分割率)是最佳的。

使用 realloc 并且如果当前内存块之后有空间,它只会被扩大而不复制。如果没有空间, realloc 调用当然会移动数据块。然而,被释放的块(如果这种情况发生多次)无论如何都不会是连续的。那么1.5或2是缓冲区扩大的因素都没有关系......内存无论如何都是碎片化的。

因此,std::vector 中的realloc 方法将在因子 1.5 比 2.0 具有优势的情况下有所帮助并且可以保存数据副本。

有些人将 c++ 分配器中缺少的 realloc 功能称为设计缺陷(请参阅链接),我倾向于在这里同意。

Why is there no reallocation functionality in C++ allocators?

【讨论】:

    猜你喜欢
    • 2022-11-16
    • 1970-01-01
    • 2013-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-31
    • 2013-07-16
    • 2013-12-20
    相关资源
    最近更新 更多