是否需要将动态阵列的容量加倍?

时间:2013-12-07 23:30:49

标签: c

在C中自动扩展数组(如C ++的std :: vector)时,每次填充数组时,通常会(或至少是常见的建议)将数组的大小加倍,以限制对{的调用量{1}}以避免尽可能复制整个数组。

EG。我们首先为8个元素分配空间,插入8个元素,然后我们为16个元素分配空间,插入8个元素,我们分配32 ..等等。

realloc如果可以扩展现有的内存分配,则不必实际复制数据。例如,以下代码仅在我的系统上执行1次复制(初始NULL分配,因此它不是真正的副本),即使它调用realloc 10000次:

realloc

我意识到这个例子非常临床 - 一个真实的应用程序可能会有更多的内存碎片,并会做更多的副本,但即使我在#include <stdlib.h> #include <stdio.h> int main() { int i; int copies = 0; void *data = NULL; void *ndata; for (i = 0; i < 10000; i++) { ndata = realloc(data, i * sizeof(int)); if (data != ndata) copies++; data = ndata; } printf("%d\n", copies); } 循环之前做了一堆随机分配,它只会做相反,稍微更糟糕的是2-4份。

那么,“倍增方法”真的有必要吗?每次将一个元素添加到动态数组时调用realloc会不会更好?

3 个答案:

答案 0 :(得分:3)

与几乎所有其他类型的操作相比,malloccalloc,尤其是realloc的内存非常昂贵。我个人对10,000,000个reallocs进行了基准测试,这需要花费大量时间。

即使我同时进行了其他操作(在两个基准测试中),我发现通过使用max_size *= 2代替max_size += 1,我可以在运行时间内减少HOURS。

答案 1 :(得分:3)

你必须从代码中退一步,抽象地抽象。种植动态容器的成本是多少?程序员和研究人员并没有考虑“这需要2ms”,而是考虑到渐近复杂性:鉴于我已经拥有n,因此增加一个元素的成本是多少?要素;当n增加时,这会如何变化?

如果您只是以恒定(或有限)的数量增长,那么您将定期移动所有数据,因此增长的成本将取决于容器的大小,并随之增长。相比之下,当您以几何方式增长容器时,即将其大小乘以一个固定因子,每次填充时,预期插入成本实际上是元素数量的独立,即常量

当然不是总是常量,但它是摊销的常量,这意味着如果你继续插入元素,那么每个元素的平均成本是不变的。时不时地你必须成长和移动,但是当你插入越来越多的元素时,这些事件变得越来越罕见。

我曾以realloc的方式问whether it makes sense for C++ allocators to be able to grow。我得到的答案表明,realloc的非移动增长行为实际上是一种红色鲱鱼,当你渐近思考时。最终你将无法再成长,你将不得不移动,所以为了研究渐近成本,realloc有时候是无操作是否无关紧要。 (此外,不动的增长似乎打乱了现代,基于竞技场的分配器,它们期望所有分配都具有相似的大小。)

答案 2 :(得分:2)

问:&#39;将动态阵列的容量增加一倍&#34;
答:没有。一个人只能在必要的程度上成长。但是,您可能会真正复制数据多次。它是内存和处理器时间之间的经典折衷。一个好的增长算法会考虑到对程序数据需求的了解,也不会过度考虑这些需求。指数增长2倍是一个愉快的妥协。

但是现在你的声明&#34;以下代码只能复制一份#34;。

使用高级内存分配器进行复制的数量可能不是OP认为的。获取相同的地址并不意味着底层内存映射没有执行重要的工作。各种各样的活动都在幕后进行。

对于增长和增长的内存分配在代码的生命周期中缩小了很多,我喜欢增长和缩小几何上彼此分开的阈值。

const size_t Grow[]   = {1, 4, 16, 64, 256, 1024, 4096, ... };
const size_t Shrink[] = {0, 2,  8, 32, 128,  512, 2048, ... };

通过在变大时使用增长阈值并在收缩时缩小增量阈值,可以避免在边界附近晃动。有时使用因子1.5。