高效的内存重新分配问题

时间:2010-06-29 12:19:47

标签: c memory-management performance malloc realloc

假设我有一个程序(例如C ++),它分配多个对象,永远不会超过给定的大小(我们称之为MAX_OBJECT_SIZE)。

我在堆上也有一个区域(我将其称为“页面”)(例如,分配给malloc(REGION_SIZE),其中REGION_SIZE> = MAX_OBJECT_SIZE)。
我一直在该页面中保留空间,直到填充的空格等于PAGE_SIZE(或者至少得到> PAGE_SIZE - MAX_OBJECT_SIZE)。

现在,我想分配更多内存。显然我以前的“页面”是不够的。所以我至少有两个选择:

  1. 使用realloc(page,NEW_SIZE),其中NEW_SIZE> PAGE_SIZE;
  2. 分配一个新的“页面”(第2页)并将新对象放在那里。
  3. 如果我想拥有自定义分配功能,那么:

    1. 使用第一种方法,我会看到我填充了多少,然后将我的新对象放在那里(并将对象的大小添加到我填充的内存变量中)。
    2. 使用第二种方法,我有一个页面列表(矢量?数组?),然后查找当前页面,然后在所选页面上使用类似于1的方法。
    3. 最终,我还需要一种释放内存的方法,但我可以找出那部分内容。

      所以我的问题是:解决这类问题的最有效方法是什么?它是选项1,选项2还是其他一些我没有考虑过的选项?是否需要/足以根据实际情况得出结论? 我知道不同的操作可能会有不同的表现,但我正在寻找一个整体指标。

6 个答案:

答案 0 :(得分:1)

根据我的经验,选项2更容易使用,开销最小。 Realloc 保证它会增加现有内存的大小。而在实践中它几乎从来没有。如果您使用它,则需要返回并重新映射所有旧对象。这将要求你记住分配的每个对象的位置......这可能是一个很大的开销。

但如果不确切知道您使用的指标,就很难获得“最有效”的资格。

这是我一直使用的内存管理器。它适用于整个应用程序,而不仅仅是一个对象。

allocs:

每个分配的

确定分配的对象的大小。

1查看该大小对象的frees链接列表,看看是否已释放任何内容(如果是这样的话)第一次免费

2在查找表中寻找,如果没有找到

2.1分配一个被分配大小的N个对象的数组。

3返回所需大小的下一个自由对象。

3.1如果数组已满,请添加新页面。

N个对象可以被程序员调整。如果你知道你有一百万个16字节的对象,你可能希望N略高一些。

对于某些大小为X的对象,不要保持数组只是分配一个新对象。

释放:

确定对象的大小,将其添加到frees的链接列表中。

如果分配的对象的大小小于指针的大小,则链接列表不需要产生任何内存开销。只需使用已分配的内存来存储节点。

此方法的问题是在应用程序退出或程序员决定对内存进行碎片整理之前,内存永远不会返回到操作系统。碎片整理是另一篇文章。可以办到。

答案 1 :(得分:0)

从您的问题中不清楚为什么需要提前分配大块内存而不是根据需要为每个对象分配内存。我假设你正在使用它作为一个连续的数组。否则,malloc在需要时对每个对象的内存更有意义。

如果它确实充当数组,malloc - 另一个块为您提供了另一块内存,您必须通过另一个指针访问(在您的情况下为page2)。因此,它不再是连续块,您不能将这两个块用作一个数组的一部分。

另一方面,

realloc分配一个连续的内存块。您可以将它用作单个数组,并且如果存在单独的块,则无法执行各种指针算法。 realloc在你真正想要缩小你正在使用的块时也很有用,但这可能不是你想要在这里做的。

因此,如果您将此作为数组使用,realloc基本上是更好的选择。否则,malloc没有任何问题。实际上,您可能希望对您创建的每个对象使用malloc,而不是必须跟踪和微观管理内存块。

答案 2 :(得分:0)

您尚未提供有关您正在尝试的平台的任何详细信息。例如,reallocLinux之间的Windows存在一些性能差异。

根据具体情况,realloc可能必须分配内存块,如果它不能增长当前内存块并且复制旧记忆到新的昂贵。 如果您确实不需要连续内存块,则应避免使用realloc

我的兴趣是使用第二种方法,或使用自定义分配器(您可以实现简单的buddy allocator [2])。

您还可以使用更高级的内存分配器,例如

答案 3 :(得分:0)

在最坏的情况下,选项1可能会导致原始内存的“移动”,这是一项额外的工作。如果没有移动内存,无论如何都会初始化“额外”大小,这也是其他工作。所以realloc会被malloc方法“击败”,但要说多少,你应该做一些测试(我认为在完成内存请求时系统是如何存在偏差的。)

根据您对realloc / malloc必须执行的次数的不同,这可能是一个有用的想法或一个无用的想法。无论如何我会使用malloc。

免费策略取决于实施。要整体释放所有页面,就足以“遍历”它们;而不是数组,我会使用链接的“页面”:将sizeof(void *)添加到“page”大小,并且您可以使用额外的字节将指针存储到下一页。

如果你必须释放位于其中一个页面中任何位置的单个对象,它会变得有点复杂。我的想法是保留一个非顺序的免费“块”/“槽”列表(适合保存任何对象)。当请求新的“块”时,首先从该列表中弹出一个值;如果它是空的,那么你将获得最后一个使用页面中的下一个“插槽”,并最终触发一个新页面。释放一个对象,意味着将空的插槽地址放在堆栈/列表中(无论你喜欢使用什么)。

答案 4 :(得分:0)

在linux(可能还有其他POSIX系统)中,还有第三种可能性,即使用带有shm_open的内存映射区域。一旦您访问它,这个区域就会被零初始化,但是如果它不仅仅是您保留的虚拟内存中的地址范围,那么您从未访问过的AFAIK页面没有任何成本。所以你可以在开始时保留一大块内存(比你需要的更多),然后从头开始逐步填充它。

答案 5 :(得分:0)

  

解决此类问题的最有效方法是什么?它是选项1,选项2还是其他一些我没有考虑过的选项?是否需要/足以得出现实世界情况的结论?

选项1.为了使其高效,NEW_SIZE必须非线性地依赖于旧尺寸。否则,由于冗余复制,您可能会遇到realloc()的O(n ^ 2)性能。我通常做new_size = old_size + old_size/4(增加25%),理论上最好new_size = old_size*2可能在最坏的情况下保留太多未使用的内存。

选项2.它应该更加优化,因为大多数现代操作系统(感谢C ++的STL)已经针对大量内存分配进行了很好的优化。小分配导致内存碎片的可能性较小。

最后,它取决于您分配新对象的频率以及如何处理释放。如果你在#1中分配很多,那么在扩展时会有一些冗余的复制,但是由于所有对象都在同一页面中,所以释放很简单。如果您需要释放/重用对象,使用#2,您将花费一些时间浏览页面列表。

根据我的经验,#2更好,因为移动大内存块可能会增加堆碎片的速度。 #2也允许使用指针,因为对象不会改变它们在内存中的位置(尽管对于某些应用程序,我更喜欢使用pool_id / index对而不是原始指针)。如果稍后遍历页面成为问题,则可能过于优化。

最后你还应该考虑选项#3:libc。我认为libc的malloc()对很多任务都足够有效。请在投入更多时间之前进行测试。除非你遇到一些向后* NIX,否则对每个小对象使用malloc()应该没有问题。我只在需要将对象放在异国情调的地方(例如shm或mmap)时才使用自定义内存管理。请记住多线程:malloc()/ realloc()/ free()通常已经过优化并且已经准备就绪;你必须重新重新实现优化,以避免线程在内存管理上不断发生冲突。如果你想拥有内存池或区域,那么也有很多库。