简体   繁体   English

realloc 调用引入了多少开销?

[英]How much overhead do realloc calls introduce?

I am using realloc in every iteration of a for loop that iterates more that 10000 times.我在迭代超过 10000 次的for循环的每次迭代中都使用realloc

Is this a good practice?这是一个好习惯吗? Will realloc cause an error if it was called a lot of times?如果realloc被多次调用会导致错误吗?

It won't fail unless you've run out of memory (which would happen with any other allocator as well) - but your code will usually run much quicker if you manage to estimate the required storage upfront.它不会失败,除非您的内存用完(任何其他分配器也会发生这种情况) - 但如果您设法预先估计所需的存储空间,您的代码通常会运行得更快。

Often it's better to do an extra loop run solely to determine the storage requirements.通常最好单独运行一个额外的循环来确定存储要求。

I wouldn't say that realloc is a no-go, but it's not good practice either.我不会说realloc是不行的,但这也不是一个好习惯。

I stumbled upon this question recently, and while it is quite old, I feel the information is not entirely accurate.我最近偶然发现了这个问题,虽然它已经很老了,但我觉得信息并不完全准确。

Regarding an extra loop to predetermine how many bytes of memory are needed,关于一个额外的循环来预先确定需要多少字节的内存,

Using an extra loop is not always or even often better.使用额外的循环并不总是甚至通常更好。 What is involved in predetermining how much memory is needed?预先确定需要多少内存涉及什么? This might incur additional I/O that is expensive and unwanted.这可能会导致额外的昂贵且不需要的 I/O。

Regarding using realloc in general,关于一般使用 realloc ,

The alloc family of functions (malloc, calloc, realloc, and free) are very efficient. alloc 系列函数(malloc、calloc、realloc 和 free)非常有效。 The underlying alloc system allocates a big chunk from the OS and then passes parts out to the user as requested.底层分配系统从操作系统分配一个大块,然后根据请求将部分传递给用户。 Consecutive calls to realloc will almost certainly just tack on additional space to the current memory location.对 realloc 的连续调用几乎肯定只会为当前内存位置增加额外的空间。

You do not want to maintain a Heap Pool yourself if the system does it for you more efficiently and correctly from the start.如果系统从一开始就更有效和正确地为您维护堆池,您就不想自己维护堆池。

You run the risk of fragmenting your memory if you do this.如果您这样做,您将面临内存碎片化的风险。 This causes performance degredation and for 32 bit systems can lead to memory shortages due to lack of availability of large contiguous blocks of memory.这会导致性能下降,对于 32 位系统,由于缺乏大的连续内存块的可用性,可能会导致内存短缺。

I'm guessing you are increasing the length of an array by 1 each time round.我猜您每次都将数组的长度增加 1。 If so then you are far better keeping track of a capacity and length and only increasing the capacity when you need a length that exceeds the current capacity.如果是这样,那么您最好跟踪容量和长度,并且仅在需要超过当前容量的长度时才增加容量。 When you increase the capacity do so by a larger amount than just 1.当您增加容量时,请增加比 1 更大的数量。

Of course, the standard containers will do this sort of thing for you so if you can use them, it's best to do so.当然,标准容器会为你做这种事情,所以如果你可以使用它们,最好这样做。

In addition to what's being said before, there's a few more things to consider:除了之前所说的,还有一些事情需要考虑:

Performance of realloc(<X-sized-buf>, X + inc) depends on two things: realloc(<X-sized-buf>, X + inc)取决于两件事:

  1. the speed of malloc(N + inc) which usually degrades towards O(N) with the size of the allocated block malloc(N + inc)的速度通常会随着分配块的大小降低到O(N)
  2. the speed of memcpy(newbuf, oldbuf, N) which is also O(N) with the size of the block memcpy(newbuf, oldbuf, N)的速度也是O(N)与块的大小

That means for small increments but large existing blocks, realloc() performance is O(N^2) with respect to the size of the existing data block.这意味着对于增量但大的现有块, realloc()性能相对于现有数据块的大小是O(N^2) Think bubblesort vs. quicksort ...想想冒泡排序和快速排序……

It's comparatively cheap if you start with a small block but will significantly punish you if the to-be-reallocated block is large.如果您从一个小块开始,它相对便宜,但如果要重新分配的块很大,则会严重惩罚您。 To mitigate, you should make sure that inc is not small relative to the existing size;为了缓解,您应该确保inc相对于现有大小不小 realloc'ing by a constant amount is a recipe for performance problems.以恒定量重新分配会导致性能问题。

Additionally, even if you grow in large increments (say, scale the new size to be 150% of the old), there's the memory usage spike from realloc'ing a large buffer;此外,即使您以较大的增量增长(例如,将新大小缩放为旧大小的 150%),重新分配大缓冲区也会导致内存使用量激增 during the copy of the existing contents you use twice the amount of memory.在复制现有内容的过程中,您使用了两倍的内存量。 A sequence of:一个序列:

addr = malloc(N);
addr = realloc(addr, N + inc);

therefore fails (much) sooner than:因此失败(远)早于:

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

There are data structures out there which do not require realloc() to grow;有一些数据结构不需要realloc()来增长; linked lists, skip lists, interval trees all can append data without having to copy existing data.链表、跳过列表、区间树都可以追加数据而无需复制现有数据。 C++ vector<> grows in this fashion, it starts with an array for the initial size, and keeps on appending if you grow it beyond that, but it won't realloc() (ie copy). C++ vector<>以这种方式增长,它从一个初始大小的数组开始,如果超过这个大小,它会继续追加,但它不会realloc() (即复制)。 Consider implementing (or using a preexisting implementation of) something like that.考虑实现(或使用预先存在的实现)类似的东西。

In C:在 C 中:

Used properly, there's nothing wrong with realloc.使用得当,realloc 没有任何问题。 That said, it's easy to use it incorrectly.也就是说,很容易错误地使用它。 See Writing Solid Code for an in-depth discussion of all the ways to mess up calling realloc and for the additional complications it can cause while debugging.请参阅编写可靠代码以深入讨论所有混淆调用 realloc 的方法以及它在调试时可能导致的其他复杂情况。

If you find yourself reallocating the same buffer again and again with only a small incremental size bump, be aware that it's usually much more efficient to allocate more space than you need, and then keep track of the actual space used.如果您发现自己一次又一次地重新分配相同的缓冲区,并且只增加了很小的增量大小,请注意分配比您需要的更多空间通常会更有效率,然后跟踪实际使用的空间。 If you exceed the allocated space, allocate a new buffer at a larger size, copy the contents, and free the old buffer.如果超出分配的空间,请分配一个更大的新缓冲区,复制内容并释放旧缓冲区。

In C++:在 C++ 中:

You probably should avoid realloc (as well as malloc and free).您可能应该避免 realloc (以及 malloc 和 free)。 Whenever possible, use a container class from the standard library (eg, std::vector).尽可能使用标准库中的容器类(例如,std::vector)。 They are well-tested and well-optimized and relieve you of the burden of a lot of the housekeeping details of managing the memory correctly (like dealing with exceptions).它们经过充分测试和优化,减轻了您正确管理内存(例如处理异常)的许多内务细节的负担。

C++ doesn't have the concept of reallocating an existing buffer. C++ 没有重新分配现有缓冲区的概念。 Instead, a new buffer is allocated at the new size, contents are copied, and the old buffer is deleted.相反,以新大小分配新缓冲区,复制内容,并删除旧缓冲区。 This is what realloc does when it cannot satisfy the new size at the existing location, which makes it seem like C++'s approach is less efficient.这就是 realloc 在现有位置无法满足新大小时所做的事情,这使得 C++ 的方法看起来效率较低。 But it's rare that realloc can actually take advantage of an in-place reallocation.但是 realloc 实际上可以利用就地重新分配的情况很少见。 And the standard C++ containers are quite smart about allocating in a way that minimizes fragmentation and about amortizing the cost across many updates, so it's generally not worth the effort to pursue realloc if you're goal is to increase performance.并且标准 C++ 容器在以最小化碎片的方式进行分配以及在许多更新中分摊成本方面非常聪明,因此如果您的目标是提高性能,那么追求 realloc 的努力通常是不值得的。

you should realloc to sizes that are power of 2. This is the policy used by stl and is good because of the way memory is managed.您应该重新分配为 2 的幂的大小。这是 stl 使用的策略,并且由于内存管理方式而很好。 realloc donesn't fail except when you run out of memory (and will return NULL) but will copy your existing (old) data in the new location and that can be a performance issue. realloc donesn 不会失败,除非内存不足(并且将返回 NULL),但会将现有(旧)数据复制到新位置,这可能是性能问题。

I thought I would add some empirical data to this discussion.我想我会在这个讨论中添加一些经验数据。

A simple test program:一个简单的测试程序:

#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;
}

GLIBC on x86_64 yields this output: 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

musl on x86_64: 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

So it looks like you can usually rely on libc to handle resizes that do not cross page boundaries without having to copy the buffer.因此,看起来您通常可以依靠 libc 来处理不跨越页面边界的调整大小,而无需复制缓冲区。

The way I see it, unless you can find a way to use a data structure that avoids the copies altogether, skip the track-capacity-and-do-power-of-2-resizes approach in your application and let your libc do the heavy-lifting for you.在我看来,除非您能找到一种使用完全避免副本的数据结构的方法,否则请跳过应用程序中的 track-capacity-and-do-power-of-2-resizes 方法,让您的 libc 执行为你举重。

if you're realloc()-ing the same buffer in the loop I see no problems as long as you have enough memory to horror the additional memory requests :)如果您在循环中使用 realloc()-ing 相同的缓冲区,我认为没有问题,只要您有足够的内存来应对额外的内存请求:)

usually realloc() will extend/shrink the existent allocated space you're working against and will give you back same pointer;通常 realloc() 会扩展/缩小您正在处理的现有分配空间,并将返回相同的指针; if it fails to do so inplace then a copy and free are involved so in this case the realloc() gets to be costly;如果它不能就地这样做,则涉及复制和免费,因此在这种情况下 realloc() 变得昂贵; and you also get a new pointer :)你也会得到一个新的指针:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM