在储备()中出现大幅高估是否存在缺点?

时间:2017-07-26 15:44:27

标签: c++ performance c++11

假设我们有一个创建和使用可能非常大vector<foo>的方法。 已知最大元素数为maxElems

从C ++ 11开始的标准练习是我所熟知的:

vector<foo> fooVec;
fooVec.reserve(maxElems);
//... fill fooVec using emplace_back() / push_back()

但是如果我们有一个场景,在我们的方法的大多数调用中元素的数量会明显减少会怎样?

保守的reserve调用是否有任何不利,除了超额分配的内存(必要时可以shrink_to_fit()可以释放)?

5 个答案:

答案 0 :(得分:11)

摘要

使用过大的保留可能存在某些缺点,但多少取决于reserve()的大小和上下文以及您的特定分配器,操作系统及其配置。

您可能已经意识到,在Windows和Linux等平台上,大型分配通常不会在首次访问之前分配任何物理内存或页表条目,因此您可能会想象大量未使用的分配是“免费”的。有时这被称为“保留”内存而不“提交”它,我将在这里使用这些术语。

以下是一些可能不像您想象的那样自由的原因:

页面粒度

上述延迟提交仅发生在页面粒度上。如果您使用(典型的)4096字节页面,则意味着如果您通常为通常包含占用100个字节的元素的向量保留4,000个字节,则延迟提交不会为您购买任何内容!至少必须提交整个4096字节的页面,并且不保存物理内存。因此,重要的不仅仅是预期大小与保留大小之间的比例,而是保留大小的绝对大小决定了您将看到多少浪费。

请记住,许多系统现在都在透明地使用“大页面”,因此在某些情况下,粒度将大约为2 MB或更多。在这种情况下,您需要大约10或100 MB的分配才能真正利用延迟分配策略。

更糟糕的分配表现

C ++的内存分配器通常尝试在类Unix平台上分配大块内存(例如,通过sbrkmmap),然后有效地将其分成应用程序请求的小块。通过诸如mmap之类的系统调用来获取这些大块内存可能比分配器内的快速路径分配慢几个数量级,这通常只有十几个指令左右。当你要求大部分不会使用的大块时,你就会失败,你会经常走慢路。

作为一个具体的例子,假设你的分配器要求mmap提供128 KB的块,它可以用来满足分配。您在典型的vector中分配了大约2K的内容,但是reserve 64K。现在,您将为每个其他mmap电话付费reserve,但如果您刚要求获得最终需要的2K,那么您的约有32 次{ {1}}来电。

依赖过度使用处理

当您要求大量内存并且不使用它时,您可能会遇到需要更多内存而不是系统支持的情况(例如,超过RAM +交换)。这是否是允许的取决于您的操作系统及其配置方式,如果您随后只是通过编写提交更多内存,无论您采取什么样的有趣行为。我的意思是任意进程可能会被杀死,或者您可能会在任何内存写入时遇到意外错误。由于overcommit tunables不同,在一个系统上起作用的可能会在另一个系统上失败。

最后,由于监控工具报告的“虚拟机大小”指标与您的流程最终可能提交的内容没有太大关系,因此管理流程变得更加困难。

更糟糕的地方

分配的内存超出了您的需求,这可能会使您的工作集在虚拟地址空间中更加稀疏地分散。总体效果是参考局部性的减少。对于非常小的分配(例如,几十个字节),这可以减少内部相同的高速缓存行位置,但是对于较大的大小,主要影响可能是将数据扩展到更多物理页面上,从而增加TLB压力。确切的阈值将在很大程度上取决于是否启用了大页面等细节。

答案 1 :(得分:1)

你所说的标准C ++ 11练习几乎不是标准的,甚至可能都不是很好的练习。

这些天我倾向于不鼓励使用.card-animation-enter-active, .card-animation-leave-active { transition: transform 0.25s ease-out, opacity 0.25s ease-out; } .card-animation-enter, .card-animation-leave-to { transform: scale(0); opacity: 0; } ,并让你的平台(即针对你的平台优化的C ++标准库)处理它认为合适的重新分配。

也就是说,过量使用reserve也可能实际上是良性的,因为现代操作系统只有在你真正使用时才给你内存(Linux特别擅长那)。但是,如果您移植到不同的操作系统,依赖于此可能会导致您遇到麻烦,而忽略reserve则不太可能。

答案 2 :(得分:0)

您有两个选择:

你不要打电话给reserve并让矢量的默认实现计算出使用指数增长的大小。

之后您致电reserve(maxElems)shrink_to_fit()

第一个选项不太可能给你一个std::bad_alloc(即使现代操作系统可能永远不会抛出这个,如果你不触及预留内存的最后一个块)

第二个选项不太可能调用reserve的多个来电,第一个选项最有可能有两个来电:reserveshrink_to_fit()(可能不是op取决于实现,因为它不具有约束力,而选项2可能具有更多。较少的电话=更好的表现。

答案 3 :(得分:0)

  

但是如果我们有一个场景,在我们的方法的大多数调用中元素的数量会明显减少会怎样?

分配的内存仍未使用。

  

保留区()中存在显着高估的不利因素吗?

是的,至少有一个潜在的缺点:为矢量分配的内存不能用于其他对象。

这在通常没有虚拟内存的嵌入式系统中尤其成问题,并且需要很少的物理内存。

关于在操作系统内运行的程序,如果操作系统没有&#34; over commit&#34;在内存中,这仍然可以很容易地导致程序的虚拟内存分配达到给予进程的限制。

即使在过度提交的系统中,特别是无偿的过高估计理论上也会导致虚拟地址空间耗尽。但是你需要相当大的数字来实现64位架构。

  

除了超额分配的内存之外,保守的预留呼叫是否有任何缺点(必要时可以使用shrink_to_fit()来释放?)

嗯,这比最初分配完全正确的内存量慢,但差异可能很小。

答案 4 :(得分:0)

如果你在linux上reserve将调用malloc,它只分配虚拟内存,而不是物理内存。实际将元素插入vector时,将使用物理内存。这就是为什么你可以大大高估reserve尺寸的原因。

如果你可以估计最大vector大小,你可以{j}开始一次,以避免重新分配,不会浪费任何物理内存。