为什么 std::vector 不保留 "double" 其容量,而 resize 保留?
Why does std::vector reserve not "double" its capacity, while resize does?
我刚刚发现 std::vector<T>::resize
"doubles" 即使调整到比当前大小高出一个元素,它的容量也是如此:
std::vector<int> v(50);
v.resize(51);
std::cout << v.capacity() << std::endl;
此程序使用 GCC 和 Clang 输出 100,使用 Visual C++ 输出 75。但是,当我从 resize
切换到 reserve
:
std::vector<int> v(50);
v.reserve(51);
std::cout << v.capacity() << std::endl;
所有三个编译器的输出都是 51。
我想知道为什么实现对 resize
和 reserve
使用不同的扩展策略。看起来不一致,我希望这里有相同的行为。
我只是在我的问题的动机中添加了一个 link,其中报告了对性能的影响:
添加来自 C++11 标准的引用以阐明 reserve
的要求; §23.3.6.3(2):
After reserve()
, capacity()
is greater or equal to the argument of reserve
if reallocation happens...
一些额外的想法:来自 C++11 标准:
Complexity: The complexity is linear in the number of elements inserted plus the distance to the end of the vector.
这实际上意味着在末尾插入单个元素的复杂性恒定(摊销)。但是,这仅适用于 矢量修饰符 ,例如 push_back
或 insert
(§23.3.6.5).
resize
未在修饰符中列出。它列在 §23.3.6.3 vector
容量部分。而且,resize
.
没有复杂性要求
但是,在 vector
概述部分 (§23.3.6.1) 中,写着:
it (vector
) supports (amortized) constant time insert and erase operations at the end
问题是resize(size()+1)
算不算"insertion at the end"。
当您 resize
超过容量时,您已经 "demonstrate" 不想保留正确的容量。另一方面,如果您使用 reserve
则您明确要求正确的容量。如果 reserve
将使用与 resize
相同的策略,则无法保留恰到好处的数量。
从这个意义上说,没有 reserve
的 resize
是为懒惰的人准备的,或者如果您不知道要保留的确切金额。如果您知道需要什么容量,您可以致电 reserve
。这是两种不同的情况。
PS:正如 StoryTeller 指出的那样,reserve
也不需要按照标准保留要求的确切数量。尽管如此,我认为我的主要论点仍然成立:resize
(没有 reserve
)和 reserve
用于不同的场景,您可以暗示您想要保留多少或不不关心实际容量,只想让容器的大小符合您的要求。
reserve
改变容量,而resize
改变size
。
capacity
是容器当前已经分配space的元素个数。
size
是容器中的元素个数。
当你分配一个空向量时,你会得到一个默认值 capacity
(又名 space)。大小仍然为 0,当您将元素添加到向量中时,它的大小会增加。当大小等于容量并且您添加更多项目时,容量必须增加(通常是自身的两倍)。
vector 的问题在于它确保顺序内存,这意味着每个新的分配增长也需要将先前分配的副本复制到新分配,以防新分配大小没有 space在旧分配的内存区域中。
此处 reserve
可以提供帮助,如果您知道向量中的最大元素。当你使用reserve
时,只有一次分配,没有内存复制,除非你传递保留项。
当您指定准确的预留计数时,您将获得您要求的准确内存。当你只是添加元素时(即使调整大小),你不会说你不会添加更多项目。
据我所知,resize
和 reserve
都不需要具有已证明的行为。然而,两者都允许这样的行为,尽管两者都可以分配确切的数量,并且就标准而言,两者都可以乘以先前的分配。
每种分配策略都有其优势。分配精确数量的优点是,当事先知道最大分配量时,它没有内存开销。乘法的优点是它在与末插入操作混合时保持恒定的摊销属性。
被测试的实现选择的方法的优点是它在调整大小时允许两种策略。要使用一种策略,可以保留然后调整大小。要使用另一个,只需调整大小。当然,必须意识到未指定的行为才能利用这一点。这个优势可能是也可能不是选择这些实现的原因。
人们可能会认为这是向量 API 的失败,正如标准中所指定的那样,表达预期的重新分配行为是不可能的(以标准保证的方式)。
您为什么期望他们的行为相同? reserve
用于预分配 space 您稍后将使用,期望用户对容器的预期最终大小有一个合适的处理。 resize
只是一个普通的分配,因此它遵循正常的、速度高效的方法,以几何方式增加容器的分配 space。
容器通过乘法步骤增加大小,以减少所需的分配数量,从而保持速度并减少内存碎片。加倍是最常见的,但一些实现使用 1.5 的步骤(例如 MSVC),以增加分配来换取每个容器中较低的浪费 space。
但是,如果用户已经告诉图书馆他们认为容器会变得多大 - 通过导致 reserve
- 没有必要分配多余的 space,他们可以信任用户用正确的号码打电话给它。有异常行为的是 reserve
,而不是 resize
。
resize
需要遵循指数重新分配策略来实现其复杂性保证(与插入的元素数量 呈线性关系)。这可以通过考虑 resize(size() + 1)
需要具有摊销常数复杂性来看出,因此必须遵循指数增长,原因与 push_back
(摊销常数复杂性)必须呈指数增长相同。
允许 reserve
的实现遵循它喜欢的任何分配策略,因为它唯一的复杂性要求是它在元素数量 present 上是线性的。但是,如果一个实现是例如要舍入到下一个 2 的幂,这将是 space-在用户确切知道需要多少内存的情况下效率低下(并且令人惊讶),并且如果用户开始依赖它可能会使移植复杂化行为。在没有 space 低效率的情况下,可以更好地行使标准中的自由度,例如如果分配器以该粒度运行,则通过将分配向上舍入到字大小。
我刚刚发现 std::vector<T>::resize
"doubles" 即使调整到比当前大小高出一个元素,它的容量也是如此:
std::vector<int> v(50);
v.resize(51);
std::cout << v.capacity() << std::endl;
此程序使用 GCC 和 Clang 输出 100,使用 Visual C++ 输出 75。但是,当我从 resize
切换到 reserve
:
std::vector<int> v(50);
v.reserve(51);
std::cout << v.capacity() << std::endl;
所有三个编译器的输出都是 51。
我想知道为什么实现对 resize
和 reserve
使用不同的扩展策略。看起来不一致,我希望这里有相同的行为。
我只是在我的问题的动机中添加了一个 link,其中报告了对性能的影响:
添加来自 C++11 标准的引用以阐明 reserve
的要求; §23.3.6.3(2):
After
reserve()
,capacity()
is greater or equal to the argument ofreserve
if reallocation happens...
一些额外的想法:来自 C++11 标准:
Complexity: The complexity is linear in the number of elements inserted plus the distance to the end of the vector.
这实际上意味着在末尾插入单个元素的复杂性恒定(摊销)。但是,这仅适用于 矢量修饰符 ,例如 push_back
或 insert
(§23.3.6.5).
resize
未在修饰符中列出。它列在 §23.3.6.3 vector
容量部分。而且,resize
.
但是,在 vector
概述部分 (§23.3.6.1) 中,写着:
it (
vector
) supports (amortized) constant time insert and erase operations at the end
问题是resize(size()+1)
算不算"insertion at the end"。
当您 resize
超过容量时,您已经 "demonstrate" 不想保留正确的容量。另一方面,如果您使用 reserve
则您明确要求正确的容量。如果 reserve
将使用与 resize
相同的策略,则无法保留恰到好处的数量。
从这个意义上说,没有 reserve
的 resize
是为懒惰的人准备的,或者如果您不知道要保留的确切金额。如果您知道需要什么容量,您可以致电 reserve
。这是两种不同的情况。
PS:正如 StoryTeller 指出的那样,reserve
也不需要按照标准保留要求的确切数量。尽管如此,我认为我的主要论点仍然成立:resize
(没有 reserve
)和 reserve
用于不同的场景,您可以暗示您想要保留多少或不不关心实际容量,只想让容器的大小符合您的要求。
reserve
改变容量,而resize
改变size
。
capacity
是容器当前已经分配space的元素个数。
size
是容器中的元素个数。
当你分配一个空向量时,你会得到一个默认值 capacity
(又名 space)。大小仍然为 0,当您将元素添加到向量中时,它的大小会增加。当大小等于容量并且您添加更多项目时,容量必须增加(通常是自身的两倍)。
vector 的问题在于它确保顺序内存,这意味着每个新的分配增长也需要将先前分配的副本复制到新分配,以防新分配大小没有 space在旧分配的内存区域中。
此处 reserve
可以提供帮助,如果您知道向量中的最大元素。当你使用reserve
时,只有一次分配,没有内存复制,除非你传递保留项。
当您指定准确的预留计数时,您将获得您要求的准确内存。当你只是添加元素时(即使调整大小),你不会说你不会添加更多项目。
据我所知,resize
和 reserve
都不需要具有已证明的行为。然而,两者都允许这样的行为,尽管两者都可以分配确切的数量,并且就标准而言,两者都可以乘以先前的分配。
每种分配策略都有其优势。分配精确数量的优点是,当事先知道最大分配量时,它没有内存开销。乘法的优点是它在与末插入操作混合时保持恒定的摊销属性。
被测试的实现选择的方法的优点是它在调整大小时允许两种策略。要使用一种策略,可以保留然后调整大小。要使用另一个,只需调整大小。当然,必须意识到未指定的行为才能利用这一点。这个优势可能是也可能不是选择这些实现的原因。
人们可能会认为这是向量 API 的失败,正如标准中所指定的那样,表达预期的重新分配行为是不可能的(以标准保证的方式)。
您为什么期望他们的行为相同? reserve
用于预分配 space 您稍后将使用,期望用户对容器的预期最终大小有一个合适的处理。 resize
只是一个普通的分配,因此它遵循正常的、速度高效的方法,以几何方式增加容器的分配 space。
容器通过乘法步骤增加大小,以减少所需的分配数量,从而保持速度并减少内存碎片。加倍是最常见的,但一些实现使用 1.5 的步骤(例如 MSVC),以增加分配来换取每个容器中较低的浪费 space。
但是,如果用户已经告诉图书馆他们认为容器会变得多大 - 通过导致 reserve
- 没有必要分配多余的 space,他们可以信任用户用正确的号码打电话给它。有异常行为的是 reserve
,而不是 resize
。
resize
需要遵循指数重新分配策略来实现其复杂性保证(与插入的元素数量 呈线性关系)。这可以通过考虑 resize(size() + 1)
需要具有摊销常数复杂性来看出,因此必须遵循指数增长,原因与 push_back
(摊销常数复杂性)必须呈指数增长相同。
允许 reserve
的实现遵循它喜欢的任何分配策略,因为它唯一的复杂性要求是它在元素数量 present 上是线性的。但是,如果一个实现是例如要舍入到下一个 2 的幂,这将是 space-在用户确切知道需要多少内存的情况下效率低下(并且令人惊讶),并且如果用户开始依赖它可能会使移植复杂化行为。在没有 space 低效率的情况下,可以更好地行使标准中的自由度,例如如果分配器以该粒度运行,则通过将分配向上舍入到字大小。