Rust的内存管理是否会导致内存碎片化?

36

Rust编程语言的自动内存管理是否需要回收碎片化内存?如果是这样,它是如何实现的?

我的理解是,它的类型系统(所有权类型、借用、RcArc)允许在编译时确定何时可以释放一块已分配的内存。

但是,难道不可能出现内存块按一定顺序分配,但按不同顺序释放导致碎片化吗?如果要防止这种情况发生,应该如何做?如果发生了,如何高效地管理这些内存碎片?如果进行了碎片整理,使用的方法论是什么?

3个回答

68
大多数程序不必担心C、C++或Rust中的内存碎片问题。对于那些需要处理内存碎片的程序,它们必须自己处理。 Rust没有自动内存管理,而是有手动内存管理,编译器会检查其正确性。一般来说,语言需要具有压缩GC才能够压缩内存碎片。与C和C ++ 一样, Rust没有GC,因此其内存可能会根据使用情况而产生碎片,并且无法进行重定位,除非程序释放这些烦人的块。然而,在我们开始担心内存碎片之前,我们必须首先考虑它的含义。内存碎片会导致物理内存和地址空间浪费,使得程序占用的内存比实际使用的要多。在极端情况下,这种浪费可能会阻止分配请求,即使未使用的内存量足够满足它们也是如此。现代分配器并不是您父辈的空闲列表分配器。常见分配方案包括大块内存分配和小块内存分配,对于小块内存分配,使用了一系列大小类别进行管理,以保持内存碎片尽可能少。
+---+---+---+---+---+---+---+---+
| a | b | c | d | e | f | g | h |
+---+---+---+---+---+---+---+---+

有512字节的空间可用于分配,这些空间通过ag 标识,而最新的空间 h 则保留用于元数据(空闲空间、同一类别中下一页/上一页等)。请注意,类别尺寸越大,最后一个空间浪费的空间就越多,因此较大的分配使用不同的方案。

在释放时,页面保留在类别大小中,直到最后一个空间被释放,此时该页面再次为空白并可以用于另一组。


这对内存消耗意味着什么?

小块分配计划的最大内存消耗是操作系统页面数量,可以计算为每个桶大小所消耗的最大操作系统页面数之和,这本身是该大小的最大并发分配数乘以适合页面的分配数(向上取整)。

这是因为,如果您分配给定大小的 1000 个空间,以散乱的方式释放其中大部分空间,从而在操作系统页面中产生了空洞,然后重新分配相同大小的空间,直到再次达到 1000 ... 那么您的内存消耗是恒定的,因为分配器将使用已经部分填充的操作系统页面中的空闲空间来满足第二波分配。

这意味着小类大小的分配既快速,又不会对碎片化做出太大贡献。

当然,这忽略了一个程序的情况,该程序会进行 100 万个 1 字节分配,以一种方式释放其中大部分,从而使所有页面都被使用,然后再使用相同的方式进行 2 字节、3 字节等分配... 但这似乎是一种病态情况。

1 是的,我在说谎。您还需要考虑分配器内部结构的开销以及它可能缓存一些未使用的操作系统页面以准备将来的分配等因素, ... 但仍足以解释碎片化的影响。


那么,碎片化是否是问题?

嗯,仍然可能是问题。地址空间仍然可能发生碎片化,尽管粒度为操作系统页面。

使用虚拟内存,RAM 不需要连续,只要有足够的空间就可以使用页面。也就是说,地址空间使用户产生了连续内存的错觉,即使内存实际上分散在 RAM 中。

这就是问题所在:这种连续内存的错觉需要找到一个地址空间的连续区域,而这受到碎片化的影响。

这种碎片化并不会在小请求中显示出来,但对于超过页面大小的请求可能会成为一个问题。如今,使用64位指针,这在实践中已经不是什么大问题了(即使只使用47位用户空间),但在32位程序中,它更有可能浮出水面:例如,在32位地址空间中,非常难以mmap一个2GB文件,因为它立即就占用了其中一半空间......假设没有杂散的分配阻止它(在这种情况下,请求将失败)。


战斗是否已经失败?

嗯,系统编程的主要优势是......你可以说系统语言。

如果您的程序具有典型分配器无法处理的分配行为,您可以:

  • 控制导致问题的特定分配(使用sbrk/mmap自己来处理)
  • 或者重新编写一个专门调试的分配器

在过去的10年中,我个人从未需要这样做,只是在闲暇时间写分配器玩而已;但这是可能的。


1
“da's free-list allocators” - 什么是“da”? - Laevus Dexter
3
@LaevusDexter:这是一种口语用法。Your da(或者your pa)意思是“你的爸爸”,带有过时的含义。 - Matthieu M.

11

总结一下Matthieu的详细解释--

Rust、C和C++在使用标准内存管理时,确实会导致内存碎片。它们不会进行碎片整理。

但在绝大多数实际应用场景中,内存碎片化非常小,不是问题。

如果出现问题,可以自己编写分配器。


-3

Rust不会碎片整理。我认为谷歌在这个领域做得很好。tcmalloc对于C/C++是多线程友好的,并处理碎片问题,而Golang也从中受益。


目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

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