我在一个迭代次数超过10000次的for
循环中的每次迭代中都使用realloc
。
这是一个好的做法吗?如果多次调用realloc
,它会引起错误吗?
我在一个迭代次数超过10000次的for
循环中的每次迭代中都使用realloc
。
这是一个好的做法吗?如果多次调用realloc
,它会引起错误吗?
只有当你的内存耗尽时,它才会失败(使用其他分配器也会出现这种情况) - 但是如果您成功预估所需存储空间,您的代码通常会运行得更快。
通常最好再多做一次循环仅仅为了确定存储要求。
我不会说realloc
不能用,但这也不是一个好的实践。
realloc
(即在David描述的情况下省略明显的C++替代方案),请确保小心使用它。为每个循环迭代重新分配是一个坏主意。但我认为寻找数组最佳增长因子是一个不同的话题,在SO上已经进行了很多讨论。 - Alexander Gesslerrealloc(<X-sized-buf>, X + inc)
的性能取决于两个因素:malloc(N + inc)
的速度通常随着分配块的大小而降低,趋向于O(N)
;
2. memcpy(newbuf, oldbuf, N)
的速度也是O(N)
。realloc()
的性能与现有数据块的大小成O(N^2)
。可以将其视为冒泡排序和快速排序的区别...inc
相对于现有大小不小。通过固定数量重新分配是性能问题的根源。addr = malloc(N);
addr = realloc(addr, N + inc);
addr[0] = malloc(N);
addr[1] = malloc(inc);
有一些数据结构不需要使用realloc()
来扩展; 链表、跳表、区间树都可以在不复制现有数据的情况下追加数据。C++ vector<>
以这种方式增长,它从一个数组开始作为初始大小,并且如果你将其增长到超过该大小,它会继续追加,但它不会realloc()
(即复制)。考虑实现(或使用现有实现)类似于此的东西。
realloc
用法之一就是调整缓冲区大小,而不是释放它并分配一个新的缓冲区,尤其是当你不打算使用其内容时。 - R.. GitHub STOP HELPING ICErealloc(buf, size++)
魔法之后,有无尽的糟糕想法。 - FrankH.realloc
的时间复杂度为O(N^2)的?每个具有O(N)复杂度的独立操作总体上仍被看作是O(N)。要获得O(N^2),您需要对于N中的每个项n,执行另一个具有O(N)复杂度的操作。 - Jason(i + k)*O(N)
,其中 i
是 malloc()
的份额,k
是 memcpy()
的份额,那么对于大内存块,你仍然会得到 k >> i
的代价-这可能是你不想承担的。我的关于 C++ 的 vector<>
的陈述也不再正确;行为在 C++11 之前的确被允许,但是 C++11 要求向量内容连续的内存,因此不能在调整大小时避免复制。 - FrankH.在C中:
如果使用得当,realloc没有什么问题。但是,使用不当会导致问题。请参考《编写可靠代码》,深入讨论了调用realloc时出现问题的各种方式以及在调试过程中可能引起的额外复杂性。
如果您发现自己一遍又一遍地重新分配相同的缓冲区,并且只增加了很少的空间,请注意通常更有效的方法是分配比您需要的更多的空间,然后跟踪实际使用的空间。如果超出了分配的空间,请分配一个新的较大尺寸的缓冲区,复制内容并释放旧的缓冲区。
在C ++中:
您应该避免使用realloc(以及malloc和free)。尽可能使用标准库中的容器类(例如std :: vector)。它们经过充分测试和优化,并减轻了管理内存正确性的许多繁琐细节(如处理异常)。
C ++没有重新分配现有缓冲区的概念。相反,在新大小处分配新缓冲区,复制内容,然后删除旧缓冲区。当realloc无法满足现有位置的新大小时,这就是realloc所做的,这使得C ++的方法似乎不太有效。但是,realloc实际上很少能够利用原地重新分配。标准C ++容器非常聪明地分配以最小化碎片,并在许多更新中分摊成本,因此如果您的目标是提高性能,则通常不值得追求realloc。
我想为这个讨论添加一些实证数据。
一个简单的测试程序:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
void *buf = NULL, *new;
size_t len;
int n = 0, cpy = 0;
for (len = 64; len < 0x100000; len += 64, n++) {
new = realloc(buf, len);
if (!new) {
fprintf(stderr, "out of memory\n");
return 1;
}
if (new != buf) {
cpy++;
printf("new buffer at %#zx\n", len);
}
buf = new;
}
free(buf);
printf("%d memcpys in %d iterations\n", cpy, n);
return 0;
}
在x86_64上,GLIBC会产生以下输出:
new buffer at 0x40
new buffer at 0x80
new buffer at 0x20940
new buffer at 0x21000
new buffer at 0x22000
new buffer at 0x23000
new buffer at 0x24000
new buffer at 0x25000
new buffer at 0x26000
new buffer at 0x4d000
new buffer at 0x9b000
11 memcpys in 16383 iterations
x86_64上的musl:
new buffer at 0x40
new buffer at 0xfc0
new buffer at 0x1000
new buffer at 0x2000
new buffer at 0x3000
new buffer at 0x4000
new buffer at 0xa000
new buffer at 0xb000
new buffer at 0xc000
new buffer at 0x21000
new buffer at 0x22000
new buffer at 0x23000
new buffer at 0x66000
new buffer at 0x67000
new buffer at 0xcf000
15 memcpys in 16383 iterations
看起来通常可以依赖libc来处理不跨页边界的调整大小而无需复制缓冲区。
在我看来,除非你能找到一种避免完全复制的数据结构,否则在应用程序中跳过轨道容量并进行2的幂次方调整大小的方法,并让libc为您完成繁重的工作。
你应该将realloc分配的大小设置为2的幂次方。这是stl使用的策略,因为内存管理的方式很好。 除非内存用尽(此时会返回NULL),否则realloc不会失败但会将你现有的(旧)数据复制到新位置,这可能会影响性能。
realloc
以避免O(n^2)
循环性能问题,但基数可以是大于1的任何值,不一定是2。许多人喜欢1.5(每次空间不足时将缓冲区增加50%)。 - R.. GitHub STOP HELPING ICE如果您在循环中重新分配相同的缓冲区,只要您有足够的内存来处理额外的内存请求,我就看不到任何问题 :)
通常realloc()会扩展/缩小您正在操作的已分配空间,并将相同的指针还给您;如果它无法原地执行,则会涉及复制和释放操作,因此在这种情况下,realloc()会变得昂贵;并且您也会得到一个新指针 :)