shrink_to_fit是将' std::vector '的容量缩减到它的大小的正确方法吗?

Is shrink_to_fit the proper way of reducing the capacity a `std::vector` to its size?

本文关键字:方法 容量 是将 fit to std vector shrink      更新时间:2023-10-16

在c++ 11中引入了shrink_to_fit来补充某些STL容器(如std::vector, std::deque, std::string)。

synpsizing,它的主要功能是请求与相关联的容器减小其容量以适应的大小。然而,这个请求是非绑定的,容器实现可以自由地进行优化,从而使vector的容量大于其大小。

此外,在之前的SO问题中,OP被劝阻使用shrink_to_fit来减少std::vector的容量到它的大小。不这样做的理由如下:

shrink_to_fit什么都不做,或者它给你缓存局部性问题,它是O(n)到执行(因为你必须把每件物品复制到它们新的、更小的地方)。通常在内存中保留空闲会更便宜。@Massa

谁能回答以下问题?

  • 引号中的参数是否成立?
  • 如果是,缩小STL容器容量的正确方法是什么(至少对于std::vector)。
  • 如果有更好的方法来收缩容器,shrink_to_fit存在的原因到底是什么?

引语中的参数是否成立?

测量一下,你就知道了。你的内存受限吗?你能预先算出正确的尺寸吗?使用reserve比使用收缩更有效。总的来说,我倾向于同意这样一个前提,即大多数用途可能对slack没有问题。

如果是,如何将STL容器的容量缩小到它的大小(至少对于std::vector)

注释不仅适用于shrink_to_fit,也适用于任何其他方式的收缩。考虑到你不能就地realloc,它涉及到获取一个不同的内存块并复制到那里,而不管你使用什么机制来收缩。

如果有更好的收缩容器的方法,那么shrink_to_fit存在的原因到底是什么呢?

请求是非绑定的,但是替代方案没有更好的保证。问题是收缩是否有意义:如果有意义,那么提供一个shrink_to_fit操作是有意义的,该操作可以利用对象被移动到到新位置的事实。也就是说,如果类型Tnoexcept(true) move构造函数,它将分配新的内存并移动元素。

虽然您可以在外部实现相同的功能,但此接口简化了操作。在c++ 03中相当于shrink_to_fit的是:

std::vector<T>(current).swap(current);

但是这种方法的问题是,当对临时对象进行复制时,它不知道current将被替换,没有任何东西告诉库它可以移动持有的对象。注意,使用std::move(current)不能达到预期的效果,因为它将移动整个缓冲区,保持相同的capacity()

在外部实现这个会有点麻烦:

{
   std::vector<T> copy;
   if (noexcept(T(std::move(declval<T>())))) {
      copy.assign(std::make_move_iterator(current.begin()),
                  std::make_move_iterator(current.end()));
   } else {
      copy.assign(current.begin(), current.end());
   }
   copy.swap(current);
}

假设我得到了正确的if条件…这可能不是您每次想要执行此操作时都想要编写的内容。

  • 参数是否成立?

由于这些论点最初是我的,如果我为它们辩护,请不要介意:

  1. shrink_to_fit不做任何事情(…)

    如前所述,标准说(很多次,但在vector的情况下是23.3.7.3节…)请求是非绑定的,以允许优化的实现自由度。这意味着实现可以shrink_to_fit定义为no-op

  2. (…)或者它给你缓存局部性问题

    shrink_to_fit 不是作为无操作实现的情况下,您必须分配一个容量为size()的新底层容器,复制(或者,在最好的情况下,移动)从旧容器中构造所有N = size()新项目,销毁所有旧项目(在移动情况下,这应该被优化,但这可能涉及到旧容器的循环),然后销毁旧容器本身。这是在libstdc++-4.9中完成的,正如David Rodriguez所描述的那样,通过

          _Tp(__make_move_if_noexcept_iterator(__c.begin()),
              __make_move_if_noexcept_iterator(__c.end()),
              __c.get_allocator()).swap(__c);
    

    libc++-3.5中,__alloc_traits中的一个函数做了大致相同的事情。

    哦,实现绝对不能依赖于realloc(即使它在::operator new中使用malloc来分配内存),因为realloc,如果它不能就地收缩,要么不占用内存(无操作情况),要么进行位复制(并错过重新调整指针的机会,等等,适当的c++复制/移动构造函数会给)。

    当然,可以编写一个可收缩内存分配器,并在其向量的构造函数中使用它。

    在向量大于缓存线的简单情况下,所有的移动都会给缓存带来压力。

  3. 是O(n)

    如果n = size(),我认为上面已经建立了,至少,你必须做一个n大小的分配,n复制或移动构造,n销毁,和一个old_capacity大小的释放。

  4. 通常把空闲时间留在内存中比较便宜

    显然,除非你真的需要空闲内存(在这种情况下,将数据保存到磁盘并在需要时重新加载它可能更明智…)

  • 如果是,如何将STL容器的容量缩小到其大小(至少对于std::vector)。

正确的方法仍然是shrink_to_fit…您只需要要么不依赖它,要么非常了解您的实现!

  • 如果有更好的收缩容器的方法,那么shrink_to_fit存在的原因是什么?

没有更好的方法,但是shrink_to_fit存在的原因是,AFAICT,有时你的程序可能会感到内存压力,这是一种处理它的方法。不是一个很好的方法,但仍然。

HTH !

  • 如果是,如何将STL容器的容量缩小到其大小(至少对于std::vector)。

'swap技巧'将把向量修剪到所需的确切大小(来自More Effective STL):

vector<Person>(persons).swap(persons);

在vector为空时特别有用,用于释放所有内存:

vector<Person>().swap(persons);

由于保留未使用空间的分配,向量不断地触发我的单元测试器的内存泄漏检测代码,并且这将它们完美地分类。

这是一个我并不关心运行时效率(大小或速度)的例子,但我确实关心确切的内存使用情况。

  • 如果有更好的收缩容器的方法,那么shrink_to_fit存在的原因是什么?

我真的不知道提供一个可以合法地做任何事情的函数有什么意义。当我看到它被引入时,我欢呼雀跃,当我发现它不可靠时,我绝望了。

也许我们会在下一个版本中看到maybe_sort()