shrink_to_fit是减少`std :: vector`到其大小的容量的正确方法吗?

时间:2014-05-06 18:31:40

标签: c++ c++11 vector stl

在C ++ 11中引入shrink_to_fit来补充某些STL容器(例如std::vectorstd::dequestd::string)。

Synopsizing,其主要功能是请求与关联的容器,以减少其容量以适应其大小。但是,此请求是非绑定的,并且容器实现可以自由地进行优化,并使向量的容量大于其大小。

此外,在之前的SO问题中,OP不鼓励使用shrink_to_fitstd::vector的容量降低到其大小。不这样做的原因如下:

  

shrink_to_fit什么都不做,或者它给你缓存局部性问题,它是O(n)到   执行(因为你必须将每个项目复制到他们新的,较小的家庭)。   通常,将松弛留在内存中会更便宜。 @Massa

有人可以如此善意地解决以下问题:

  • 报价中的参数是否成立?
  • 如果是,将STL容器的容量缩小到适当大小的正确方法是什么(至少对std::vector而言)。
  • 如果有更好的方法来缩小容器,那么shrink_to_fit之后存在的原因是什么?

3 个答案:

答案 0 :(得分:14)

  

报价中的参数是否成立?

措施,你会知道的。你是否受限于记忆?你能预先确定正确的尺寸吗? reserve比事后收缩更有效。总的来说,我倾向于同意这样的前提,即大多数用途可能都很好用。

  

如果是,那么缩小STL容器容量的正确方法是什么(至少对于std :: vector)。

评论不仅适用于shrink_to_fit,也适用于任何其他缩小方式。鉴于您不能realloc到位,它涉及获取不同的内存块并在那里进行复制,无论您使用什么机制进行收缩。

  

如果有一个更好的方法来缩小容器,那么后来存在shrink_to_fit的原因是什么?

请求不具有约束力,但替代方案没有更好的保证。问题是收缩是否有意义,如果确实如此,那么提供一个shring_to_fit操作是有意义的,该操作可以利用对象被移动的事实到一个新的位置。即如果类型T具有noexcept(true)移动构造函数,它将分配新内存并移动元素。

虽然您可以在外部实现相同的功能,但此界面可简化操作。相当于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 :(得分:14)

  
      
  • 论证会举行吗?
  •   

由于争论最初是我的,不要介意我一个接一个地为他们辩护:

  1. shrink_to_fit不执行任何操作(...)

    正如提到的那样,标准说(很多次,但在vector的情况下,它的第23.3.7.3节......)请求是非绑定的,以允许实现纬度为优化。这意味着实现可以shrink_to_fit定义为无操作。

  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(即使它在malloc内使用::operator new进行内存分配),因为{{1}如果它不能就地收缩,将要么单独留下内存(无操作的情况),要么进行按位复制(并错过重新调整指针的机会,等等,正确的C ++复制/移动构造函数会给出)。

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

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

  3. 它是O(n)

    如果realloc,我认为它已在上面确定,至少,您必须执行一个n = size()大小的分配,n复制或移动构造,{{1} } destructions,以及一个n大小的释放。

  4. 通常只是将松弛留在内存中会更便宜

    显然,除非你真的要求免费内存(在这种情况下,将数据保存到磁盘并在以后按需重新加载它可能更明智......)

  5.   
        
    • 如果是,那么将STL容器的容量缩小到适当大小的正确方法是什么(至少对于std :: vector)。
    •   

    正确的方法仍然是n ...你必须要么不依赖它,要么非常了解你的实施!

      
        
    • 如果有一个更好的方法来缩小容器,那么后来存在shrink_to_fit的原因是什么?
    •   

    没有更好的方法,但{A}的存在的原因是,AFAICT,有时你的程序可能会感到记忆压力,这是治疗它的一种方法。不是很好的方式,但仍然。

    HTH!

答案 2 :(得分:1)

  
      
  • 如果是,那么将STL容器的容量缩小到适当大小的正确方法是什么(至少对于std :: vector)。
  •   

'swap trick'会将向量修剪为所需的确切大小(来自更有效的SQL):

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

当向量为空时特别有用,释放所有内存:

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

由于保留了未使用空间的分配,矢量不断地绊倒我的单元测试器的内存泄漏检测代码,并且这完全排除了它们。

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

  
      
  • 如果有一个更好的方法来缩小容器,那么后来存在shrink_to_fit的原因是什么?
  •   

我真的不知道提供一个可以合法地完成任何功能的功能的重点是什么。 当我看到它被引入时,我欢呼,然后当我发现它无法依赖时,我感到绝望。

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