realloc调用会引入多少开销?

时间:2011-03-29 11:06:01

标签: c++ c memory memory-management realloc

我在realloc循环的每次迭代中使用for,迭代次数超过10000次。

这是一个好习惯吗? realloc如果被多次调用会导致错误吗?

8 个答案:

答案 0 :(得分:13)

除非你的内存不足(任何其他分配器都会发生),否则它不会失败 - 但如果你设法预先估算所需的存储空间,你的代码通常运行得更快。

通常最好只执行额外的循环运行以确定存储要求。

我不会说realloc是禁止的,但这也不是好习惯。

答案 1 :(得分:7)

我最近偶然发现了这个问题,虽然它已经很老了,但我觉得这些信息并不完全准确。

关于预先确定需要多少字节内存的额外循环,

使用额外的循环并不总是或甚至更好。预先确定需要多少内存需要什么?这可能会产生额外的I / O,这是非常昂贵且不需要的。

关于一般使用realloc,

alloc函数系列(malloc,calloc,realloc和free)非常有效。底层alloc系统从OS分配一个大块,然后根据请求将部分传递给用户。对realloc的连续调用几乎肯定只会增加当前内存位置的额外空间。

如果系统从一开始就为您提供更有效和正确的帮助,您就不希望自己维护堆池。

答案 2 :(得分:3)

如果这样做,您可能会损坏内存。这会导致性能下降,对于32位系统,由于缺少大量连续内存块,可能会导致内存短缺。

我猜你每次都会将数组的长度增加1。如果是这样,那么您可以更好地跟踪容量和长度,并且只在需要超过当前容量的长度时才增加容量。当您增加容量时,请大于1。

当然,标准容器会为您做这类事情,所以如果您可以使用它们,最好这样做。

答案 3 :(得分:3)

除了之前所说的内容之外,还有一些事情需要考虑:

realloc(<X-sized-buf>, X + inc)的效果取决于两件事:

  1. malloc(N + inc)的速度,O(N)通常会因分配块的大小而向memcpy(newbuf, oldbuf, N)降级
  2. O(N)的速度,realloc()的速度与块的大小相同
  3. 这意味着对于增量但现有块,O(N^2)性能相对于现有数据块的大小为inc。想想bubblesort与quicksort ......

    如果你从一个小块开始,它相对便宜,但如果要重新分配的块很大,会显着惩罚你。为了缓解,您应该确保相对于现有尺寸addr = malloc(N); addr = realloc(addr, N + inc); ;以恒定量重新分配是性能问题的一个方法。

    此外,即使你以较大的增量增长(比如,将新大小扩展为旧的大小的150%),内存使用量峰值会重新分配大缓冲区;在复制现有内容期间,您使用两倍的内存量。一系列:

    addr[0] = malloc(N);
    addr[1] = malloc(inc);
    
    因此,

    早于(很多)失败:

    realloc()

    有些数据结构不需要vector<>增长;链接列表,跳过列表,间隔树都可以附加数据而无需复制现有数据。 C ++ realloc()以这种方式增长,它从一个初始大小的数组开始,如果你超过它,则继续追加,但它不会{{1}}(即复制)。考虑实现(或使用预先存在的实现)这样的东西。

答案 4 :(得分:2)

在C:

使用得当,realloc没有任何问题。也就是说,它很容易错误地使用它。请参阅Writing Solid Code,深入讨论调用realloc的所有方法以及调试时可能导致的其他复杂情况。

如果您发现自己一次又一次地重新分配相同的缓冲区只有一个小的增量大小的凹凸,请注意分配比您需要的空间更多的空间通常更有效,然后跟踪实际使用的空间。如果超出分配的空间,请分配较大的新缓冲区,复制内容并释放旧缓冲区。

在C ++中:

你可能应该避免realloc(以及malloc和free)。尽可能使用标准库中的容器类(例如,std :: vector)。它们经过了充分测试和优化,可以减轻您正确管理内存(如处理异常)的许多内务管理细节的负担。

C ++没有重新分配现有缓冲区的概念。而是以新大小分配新缓冲区,复制内容,并删除旧缓冲区。这就是当realloc无法满足现有位置的新大小时所做的事情,这使得C ++的方法看起来效率较低。但realloc很少能够实际利用就地重新分配。标准C ++容器非常聪明,可以最大限度地减少碎片分配,并在许多更新中分摊成本,因此,如果您的目标是提高性能,那么通常不值得努力追求重新分配。

答案 5 :(得分:1)

你应该重新分配给2的幂的大小。这是stl使用的策略,并且由于管理内存的方式很好。 realloc dones不会失败,除非你的内存不足(并将返回NULL),但会复制新位置的现有(旧)数据,这可能是性能问题。

答案 6 :(得分:0)

如果你是realloc() - 在循环中使用相同的缓冲区,只要你有足够的内存来恐吓额外的内存请求,我就看不出任何问题:)

通常realloc()会扩展/缩小你正在使用的现有已分配空间,并会返回相同的指针;如果它没有在原地进行,则涉及副本和免费,因此在这种情况下,realloc()会变得昂贵;你也得到一个新指针:)

答案 7 :(得分:0)

我想我会在这次讨论中添加一些经验数据。

一个简单的测试程序:

#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上的音乐:

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为您完成繁重的工作。