内存分配器建议

6
我有一个多线程部分,线程需要分配几个大的数据段,每个数据段大约100MB,用作缓冲区。此外,缓冲区可能需要在运行时多次调整大小。
自然的解决方案是使用realloc,但它可能会移动不需要的内存。我担心使用free/malloc对缓冲区进行调整大小会导致碎片化,并且提前保留内存会创建其他问题。
我应该使用什么来分配/重新分配内存?

提前预留内存会带来其他问题,因此池化分配器不太适合?您是想保持标准化,还是寻求自定义分配方案的方法? - Corbin
@Corbin 我认为在我的情况下池不是很好 - Linux 在 NUMA 域中有第一触碰策略。我想使用可移植的解决方案。 - Anycorn
有没有一种管理缓冲区数据的方法,可以避免调整大小的需要?甚至可以对缓冲区大小设置上限吗?您计划在硬件上拥有多少内存? - NG.
@Anycorn 啊,恐怕我不能提供太多帮助。在保持高效的调整大小(因此有时需要复制调整大小)的同时,我真的看不出有什么合理的方法可以避开池。我的内存管理技能最多也就是一般水平 :)。 - Corbin
3个回答

5
使用freemalloc。这不会引起碎片问题。
现代分配器对内存碎片化相当有抵抗力。如今,只有一个非常病态的程序才会引起碎片化问题。在我们的程序直接访问物理RAM时,碎片化是一个更严重的问题,但是使用虚拟内存后,程序堆中的大型“空洞”不需要消耗任何资源。
此外,由于缓冲区的大小,大多数分配器将为每个缓冲区请求来自内核的专用区域。在Linux / OS X / BSD上,这意味着每个缓冲区背后都有一个匿名的mmap。这可能会导致地址空间的碎片化,但在64位系统上,虚拟地址空间基本上是免费的,而在32位系统上,几百兆字节也不是问题。
因此,请使用freemalloc
替代方法:您可能会发现,使每个缓冲区比实际需要的更大会更快。在现代Unix上,malloc的工作方式是,您没有写入的页面不会占用内存。
因此,如果您malloc了一个500 MB的缓冲区,但仅使用了前100 MB,则您的程序实际上不会比您malloc一个100 MB的缓冲区并使用整个缓冲区占用更多的内存。这样做会导致更多的地址空间碎片化,但在64位系统上不是问题,而且您可以始终调整分配大小以使其在32位系统上运行。
至于建议使用mmap,只需将malloc/free视为mmap/munmap的简单接口即可,对于大型分配(1 MiB是常见的门槛)。

有没有参考资料描述了什么情况下会发生碎片化? - Anycorn
有一些奇怪的事情可以做,比如分配一堆16字节的块,释放奇数编号的块,然后分配一堆24字节的块,释放奇数编号的块,一直这样做。这基本上是malloc的最坏情况,但浪费的总内存百分比有一个界限。 - Dietrich Epp
1
@Anycorn:实际上,既然我们正在谈论这样大的缓冲区,那么真正的问题是地址空间碎片而不是内存碎片。如果你使用的是64位系统,那么地址空间碎片就不是你需要关心的问题了。 - Dietrich Epp

4

只需使用realloc。在现代系统上,即使缓冲区被移动到新地址,移动也将通过操作页面表(在Linux上是mremap;我相信其他系统有类似的机制),而不是通过复制数据来完成。(请注意,这里假设有大缓冲区;对于小缓冲区,通常少于几百kb,实际上会发生复制。)

如果您的目标是64位机器,则完全不需要担心内存碎片化。您永远不会因为内存碎片化严重而用完虚拟地址空间。如果您还需要处理32位机器,则只要线程不太多,你可能是安全的。只要总内存消耗小于1GB,在分片情况下由于分配问题而用完虚拟地址空间是非常困难的,假设您的使用模式正确。如果您担心它,请预先分配可能需要的最大大小。


1

1
对于100 MB缓冲区,您不会注意到jemalloc或tcmalloc之间的差异,因为它们都直接从内核请求页面以进行超过某个阈值(1 MiB是常见的)的分配。您系统上的默认malloc也会执行相同的操作。 - Dietrich Epp
@DietrichEpp 默认的malloc存在多线程应用程序中的锁竞争问题。不过,在使用之前,我肯定会对我的程序进行分析。 - Shayan Pooya
1
如果你正在谈论100 MB缓冲区,即使是高度争议的锁的成本也将是最小的,因为你必须进行系统调用。 - Dietrich Epp
确实。对于像这样的大缓冲区,花哨的分配器根本没有帮助。所有时间(包括全局vm锁的锁争用)都将在内核空间中消耗。 - R.. GitHub STOP HELPING ICE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接