【问题标题】:deque::shrink_to_fit memory guaranteesdeque::shrink_to_fit 内存保证
【发布时间】:2014-07-21 17:51:36
【问题描述】:

我试图在从双端队列的开头删除一个范围后使用deque::shrink_to_fit,因为我的内存要求很紧。但是,它不起作用,我刚刚看到 libstdc++ 使用带有副本的交换技巧实现了shrink_to_fit。这实际上意味着不是更好的内存使用,而是在一段时间内获得 2 倍的使用量并因此受到 OOM 影响。

我认为这限制了shrink_to_fit 的可用性,我想知道标准中是否有/可以有任何保证?我在草稿 (N3035) 中查找它,只看到“这是一个不具约束力的请求......”。我意识到为什么它是非绑定的,以及为什么它不能为vector 完成,但根据我对deque 实现的了解,应该可以提供一些内存保证(看看 libc++,它似乎以更聪明的方式做到这一点)。是否没有保证,因为它们与特定的实现有关?

【问题讨论】:

  • “它没有用”是什么意思?
  • 我的意思是第一段的最后一句话。 shrink_to_fit 没有提高内存使用率,而是将其增加了两次,并导致我的进程被 OOM-ed(是的,我知道,我使用了太多的内存)。
  • 您如何看待 shrink_to_fit 使您的内存使用量翻倍?你的编译器是什么?如果是这样,那就是一个错误...
  • 您在deque中存储了什么样的项目?

标签: c++ c++11 deque


【解决方案1】:

shrink_to_fit 的 libstdc++ 和 libc++ 实现看起来都符合我的要求。但它们非常不同,主要是因为这两种实现遵循不同的类不变量。

首先,对于那些不知道的人,std::deque 是一个指向固定大小的T 数组的指针数组(通常是T*,但自定义分配器可以对其进行泛化)。指针数组可以被视为一个循环缓冲区,或者可以将其数据的开头从缓冲区的开头滑开的缓冲区。

这个答案将只关注双端队列的固定长度数组。

libstdc++

libstdc++ 实现有一个不变量,即deque 中永远不会有空数组。如果pop_frontpop_back 创建了一个空数组,则该数组在擦除期间被释放。这种设计减少了deque::shrink_to_fit 实际可以实现的功能,因为deque 始终处于或非常接近最小内存占用量。

libstdc++ shrink_to_fit 仅当它可以向自己证明它可以通过这样做来消除数组时才执行复制和交换。例如,deque 可以保存由三个固定长度数组支持的 1010 个值,每个数组的容量为 1000 个值。 deque 中的第一个元素可能从第一个数组中的位置 995 开始。因此,第一个和第三个数组中的大多数(但不是全部)都是空的。复制/交换将分配两个新数组,将 1010 个元素复制/移动到这两个数组中,然后释放旧的 3 个数组。

libc++

libc++ 实现遵循稍微不同的设计,旨在加速FIFO queues。当pop_front(或pop_back)清空一个数组时,除非前面(或后面)已经有一个空数组,否则该数组不会被释放。 IE。虽然 libstdc++ 不变量是在整个 deque 中永远不会有一个空数组,但 libc++ 不变量是在 deque 的前后可以有零个或一个空数组。这样做的基本原理是,如果push_back(或push_front)需要一个新数组,它首先在deque 的另一端寻找一个空数组并从那里加固,然后再分配一个新数组。给定一个大小近似恒定的 FIFO 队列,此设计将达到这样一种状态:pop_front/push_back 序列永远不会分配新数组。相反,数组从 deque 的前面循环到后面(反之亦然)。

libc++ shrink_to_fit 将丢弃 deque 两端的空数组(如果存在)。相反,libstdc++ 不需要这样做,因为空数组从不存在。 libc++ shrink_to_fit 不会尝试通过复制/交换操作将值“移动”到更接近块开头的位置来进一步减少内存占用。

结果是 libc++ shrink_to_fit 永远不会使引用无效,而 libstdc++ shrink_to_fit 通常会。请注意,shrink_to_fit 的规范旨在允许引用无效,尽管现在我看,我认为它错误地没有(它必须用于 vector::shrink_to_fit,并且deque 的措辞取自vector 的措辞)。

这两个实现也将shrink_to_fitT* 的底层缓冲区,尽管这具有相对较小的影响,因为T* 的缓冲区的内存占用量通常远小于数组的内存占用量。每个 libc++ 数组通常占用 4096 字节(1 页),而每个 libstdc++ 数组通常占用 512 字节。

我不知道 VC++ deque 做了什么。但它也会实现一个数组数组。

正如所见,虽然所有实现都在相同的数据结构上运行(为了符合复杂性和失效要求),但仍有一些重要的设计决策留给实现,每个实现都力求做到提供其认为最适合客户的产品。

【讨论】:

  • 感谢您的详细解释。我认为双端队列类似于向量,因为它从不返回分配的内存(尽管现在我想起来了,很明显双端队列可以有效地做到这一点,而向量则不能)。
猜你喜欢
  • 1970-01-01
  • 2011-09-24
  • 1970-01-01
  • 1970-01-01
  • 2015-08-27
  • 1970-01-01
  • 2012-07-18
  • 2011-05-04
  • 2013-01-25
相关资源
最近更新 更多