realloc在常见实现中真的会缩小缓冲区吗?
在常见的系统实现中,比如Linux/Glibc、Windows/MSVC和BSD/Mac OS X,
void *p = malloc(N + M); // assume this doesn't fail
p = realloc(p, N); // nor this
对于N
和M > 0
的情况,调用realloc
时,实际上会缩小malloc
返回的缓冲区,也就是说,最多可以将M
字节返回给空闲列表吗?更重要的是,有可能会重新分配这个缓冲区吗?
我想知道这个问题,因为我刚刚在numpy.ndarray
的基础上实现了动态数组,现在我在进行resize
操作,这个操作会调用realloc
,以确保最终的大小是正确的。我可能可以跳过最后一次resize
,作为一种优化(虽然这样会导致永久性过度分配),我想知道这样做是否值得尝试。
3 个回答
是的,它们确实会这样做。不过标准并没有强制要求这一点。所以你需要根据你使用的 libc
来检查这一点。你可以通过查看代码(如果有的话)或者写一个测试程序来进行检查。这个测试程序的思路是这样的:你先分配一个比较大的内存块(比如说10K),然后尝试用 realloc
把它缩小一半,再用 malloc
分配一些小的内存。如果新返回的地址在你第一次分配的范围内,那么说明你的 realloc
确实缩小了;否则就说明没有缩小。
是否值得这样做,主要看这个对象会存在多久,以及减少它占用的内存对应用程序有多重要。没有一个通用的标准答案。
一般的内存分配器通常会假设调用者知道之前这个内存块的大小,只有在他们确实想要缩小这个内存块时才会调用 realloc
。我最近看过的一个内存分配器,如果这个内存块超过128字节,并且重新分配后能释放至少1KB的内存,或者释放的字节数至少是当前分配大小的1/4,它就会愿意缩小这个内存块。这个分配器是为高流量的服务器应用设计的,因为在这种情况下,对象通常不会存在太久,同时它还提供了一种特别的“合适大小”操作,适用于那些知道会存在很长时间的对象。
我可以说说关于Linux和glibc的事情。在它的源代码中,有这样的注释:
如果
n
的字节数比p
当前持有的字节数少,那么多出来的空间会被去掉,并且如果可能的话会被释放掉。
如果你查看glibc的代码,会看到这样的行:
remainder_size = newsize - nb;
if (remainder_size < MINSIZE) { /* not enough extra to split off */
set_head_size(newp, newsize | (av != &main_arena ? NON_MAIN_ARENA : 0));
set_inuse_bit_at_offset(newp, newsize);
}
else { /* split remainder */
remainder = chunk_at_offset(newp, nb);
set_head_size(newp, nb | (av != &main_arena ? NON_MAIN_ARENA : 0));
set_head(remainder, remainder_size | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
/* Mark remainder as inuse so free() won't complain */
set_inuse_bit_at_offset(remainder, remainder_size);
#ifdef ATOMIC_FASTBINS
_int_free(av, remainder, 1);
#else
_int_free(av, remainder);
#endif
}
nb
是你想要的字节数,而这里的 newsize
应该叫 oldsize
。所以它会尽量释放多余的内存。
关于Mac OSX,更准确地说是关于 magazine_malloc
,这是苹果当前的 malloc
实现。想了解更多细节,可以查看这个链接:http://cocoawithlove.com/2010/05/look-at-how-malloc-works-on-mac.html。
realloc
调用的是区域重新分配的方法,目前的实现是 szone_realloc
。对于不同的分配大小,有不同的代码,但算法总是一样的:
if (new_good_size <= (old_size >> 1)) {
/*
* Serious shrinkage (more than half). free() the excess.
*/
return tiny_try_shrink_in_place(szone, ptr, old_size, new_good_size);
} else if (new_good_size <= old_size) {
/*
* new_good_size smaller than old_size but not by much (less than half).
* Avoid thrashing at the expense of some wasted storage.
*/
return ptr;
}
所以你可以看到,它的实现会检查 new_size <= old_size / 2
,如果是这样,就会释放内存;如果不是,就什么也不做。