"reserve or shrink" 已知未来容量需求的向量的正确方法
Right way to "reserve or shrink" a vector to a known future capacity need
我围绕长寿命矢量的共同主题编写了无数软件模块,有时(以未指定的频率)必须更新其内容。
惯用实现:
void LongLived::reconfigure(const InputT& whatever_input)
{
m_vector.clear();
m_vector.reserve(whatever_input.size());
populate(m_vector, whatever_input);
}
请注意,惯用的实现将永远不会减少其内部缓冲区的容量。如果这不行怎么办?就用shrink_to_fit()
,我想:
void LongLived::reconfigure(const InputT& whatever_input)
{
m_vector.clear();
m_vector.reserve(whatever_input.size());
m_vector.shrink_to_fit(whatever_input.size()); // ← Here
populate(m_vector, whatever_input);
}
哦,那该多好……但令我惊讶的是,它无法编译,因为 shrink_to_fit()
不接受数字!
应该使用shrink_to_fit()
的方式,显然是先填充向量。然后,你调用 shrink_to_fit()
,这将在事后从向量中的元素数量中推导出容量需求,但如果我可以事先告诉它,那显然是次优的,因为现在,所有的内容必须移动。
Objective: 我想在这种情况下使用函数 vector_reserve_or_shrink()
:
void LongLived::reconfigure(const InputT& whatever_input)
{
m_vector.clear();
vector_reserve_or_shrink(m_vector, whatever_input.size()); // ← Implement this!
populate(m_vector, whatever_input);
}
我实际上并不痴迷于从向量中删除每个未使用的字节。相反,我很乐意将它留给像 shrink_to_fit()
这样定义的实现,它可能知道分配器的一些怪癖,并且可能选择什么也不做。这样,就不会冒着 abstraction inversion 否定任何较低级别优化的风险。例如,假设分配器的粒度是 16:然后,向量实现可能会在您要求一个时免费给您 15 个字节,据我所知,这只会适得其反。
template<class V>
void hard_reserve( V& v, std::size_t capacity ){
if (v.capacity() > capacity) v.shrink_to_fit();
v.reserve(capacity);
}
它在某些极端情况下并不完美,但那些将是已经存在的极端情况的极端情况,并且std
不提供适合多角度的容器。
shrink_to_fit
可以(但没有义务)将向量的容量减少到 size
。因此,如果您正在寻找以 "optimal" 方式减少向量容量的方法,shrink_to_fit
可能是正确的选择。
当然,shrink_to_fit
不带参数,因为向量的大小是作为参考的。所以你唯一需要做的就是 clear
,然后 reserve
,填充以填充所需的大小,最后 shrink_to_fit
:
void LongLived::reconfigure(const InputT& whatever_input)
{
m_vector.clear();
m_vector.reserve(whatever_input.size());
populate(m_vector, whatever_input);
m_vector.shrink_to_fit();
}
使用这样的东西:
template <typename VECTOR>
void setCapacity(VECTOR &vector, std::size_t capacity) {
if (capacity < vector.size()) capacity = vector.size();
if (vector.capacity() > capacity) {
VECTOR v;
v.reserve(capacity);
std::size_t size = vector.size();
for (std::size_t i = 0; i < size; i++) {
v.emplace_back(std::move(vector[i]));
}
vector.swap(v);
} else {
vector.reserve(capacity);
}
}
它将向量容量设置为capacity
(如果可能),同时保留元素。而且它只做一次分配。
void LongLived::reconfigure(const InputT& whatever_input)
{
m_vector.clear();
m_vector.shrink_to_fit();
m_vector.reserve(whatever_input.size());
populate(m_vector, whatever_input);
}
据我所知,shrink_to_fit
在空向量上对所有实现都非常有效。它不会导致新的内存分配。唯一的分配将由 reserve
完成。
不会复制任何元素T
并且避免了由于重新分配而保留两个内存块的内存峰值。
除了使用 shrink_to_fit
来释放内存之外,还可以通过交换一个额外的函数(或作用域)来清除向量。这也避免了持有两个缓冲区的内存峰值。
void free_vector(std::vector<T> & vec, std::size_t new_size)
{
std::vector<T> new_vec;
vec.swap(new_vec);
}
我围绕长寿命矢量的共同主题编写了无数软件模块,有时(以未指定的频率)必须更新其内容。
惯用实现:
void LongLived::reconfigure(const InputT& whatever_input)
{
m_vector.clear();
m_vector.reserve(whatever_input.size());
populate(m_vector, whatever_input);
}
请注意,惯用的实现将永远不会减少其内部缓冲区的容量。如果这不行怎么办?就用shrink_to_fit()
,我想:
void LongLived::reconfigure(const InputT& whatever_input)
{
m_vector.clear();
m_vector.reserve(whatever_input.size());
m_vector.shrink_to_fit(whatever_input.size()); // ← Here
populate(m_vector, whatever_input);
}
哦,那该多好……但令我惊讶的是,它无法编译,因为 shrink_to_fit()
不接受数字!
应该使用shrink_to_fit()
的方式,显然是先填充向量。然后,你调用 shrink_to_fit()
,这将在事后从向量中的元素数量中推导出容量需求,但如果我可以事先告诉它,那显然是次优的,因为现在,所有的内容必须移动。
Objective: 我想在这种情况下使用函数 vector_reserve_or_shrink()
:
void LongLived::reconfigure(const InputT& whatever_input)
{
m_vector.clear();
vector_reserve_or_shrink(m_vector, whatever_input.size()); // ← Implement this!
populate(m_vector, whatever_input);
}
我实际上并不痴迷于从向量中删除每个未使用的字节。相反,我很乐意将它留给像 shrink_to_fit()
这样定义的实现,它可能知道分配器的一些怪癖,并且可能选择什么也不做。这样,就不会冒着 abstraction inversion 否定任何较低级别优化的风险。例如,假设分配器的粒度是 16:然后,向量实现可能会在您要求一个时免费给您 15 个字节,据我所知,这只会适得其反。
template<class V>
void hard_reserve( V& v, std::size_t capacity ){
if (v.capacity() > capacity) v.shrink_to_fit();
v.reserve(capacity);
}
它在某些极端情况下并不完美,但那些将是已经存在的极端情况的极端情况,并且std
不提供适合多角度的容器。
shrink_to_fit
可以(但没有义务)将向量的容量减少到 size
。因此,如果您正在寻找以 "optimal" 方式减少向量容量的方法,shrink_to_fit
可能是正确的选择。
当然,shrink_to_fit
不带参数,因为向量的大小是作为参考的。所以你唯一需要做的就是 clear
,然后 reserve
,填充以填充所需的大小,最后 shrink_to_fit
:
void LongLived::reconfigure(const InputT& whatever_input)
{
m_vector.clear();
m_vector.reserve(whatever_input.size());
populate(m_vector, whatever_input);
m_vector.shrink_to_fit();
}
使用这样的东西:
template <typename VECTOR>
void setCapacity(VECTOR &vector, std::size_t capacity) {
if (capacity < vector.size()) capacity = vector.size();
if (vector.capacity() > capacity) {
VECTOR v;
v.reserve(capacity);
std::size_t size = vector.size();
for (std::size_t i = 0; i < size; i++) {
v.emplace_back(std::move(vector[i]));
}
vector.swap(v);
} else {
vector.reserve(capacity);
}
}
它将向量容量设置为capacity
(如果可能),同时保留元素。而且它只做一次分配。
void LongLived::reconfigure(const InputT& whatever_input)
{
m_vector.clear();
m_vector.shrink_to_fit();
m_vector.reserve(whatever_input.size());
populate(m_vector, whatever_input);
}
据我所知,shrink_to_fit
在空向量上对所有实现都非常有效。它不会导致新的内存分配。唯一的分配将由 reserve
完成。
不会复制任何元素T
并且避免了由于重新分配而保留两个内存块的内存峰值。
除了使用 shrink_to_fit
来释放内存之外,还可以通过交换一个额外的函数(或作用域)来清除向量。这也避免了持有两个缓冲区的内存峰值。
void free_vector(std::vector<T> & vec, std::size_t new_size)
{
std::vector<T> new_vec;
vec.swap(new_vec);
}