Rust编程语言的自动内存管理是否需要回收碎片内存?如果是这样,它是如何做到的?
我的理解是它的类型系统(所有权类型,借用,Rc
,Arc
)允许它在编译时确定性地知道何时可以释放一大块已分配的内存。
但是,内存块是否可能在一个订单中分配,并以不同的顺序释放,从而导致碎片化?如果这被阻止了,怎么样?如果确实发生了这种情况,那么如何有效地管理内存片段?如果他们进行了碎片整理,那么使用的方法是什么?
答案 0 :(得分:35)
TL; DR:大多数程序永远不必担心C,C ++或Rust中的碎片。那些必须自己处理的人。
Rust编程语言的自动内存管理是否需要回收碎片内存?
Rust没有自动内存管理功能;它具有手动内存管理,编译器会检查其是否正确。差异可能听起来很理论,但这很重要,因为它意味着内存操作直接映射到源代码,幕后没有任何魔力。
通常,语言需要具有压缩GC才能压缩碎片存储器。 Rust,就像C和C ++一样,没有GC,所以它的内存可能会根据使用情况而碎片化,并且不能进行碎片整理而不会让程序释放烦人的块,因为重定位是不可能的。
然而,在我们开始担心碎片化之前,我们必须先考虑它的含义。
碎片有什么影响?
碎片会浪费物理内存和地址空间:您的程序占用的空间超过了它的使用量。在极端情况下,这种浪费可能会阻止分配请求,即使未使用的内存量足以授予它们。
与GC语言并行时,了解大多数GC语言 会导致一些浪费,这一点非常重要。
事实上,值得注意的是,碎片化不是废物的唯一来源;过度分配是一个常见的问题"太:
Vec
将分配2个幂的元素,但您可能只使用2^N + 1
,浪费2^N - 1
个插槽BTreeMap
或HashMap
分配的空间比实际使用的空间大并且这甚至不计算管理这个内存所带来的浪费,因为内存分配器保持一些状态以知道它有哪些页面,以及它们中使用了什么。
这突出了一个非常重要的事实:如果因为你的分配方案规定你会遇到同样的问题而导致记账/开销占用大量内存,那么就可以摆脱碎片。
现代分配器如何管理内存?
现代分配器不是你的自由列表分配器。
典型的分配方案相对简单,但非常擅长保持小型请求的碎片化:
对于小平板,许多类由大小定义。例如:(0, 8]
,(8, 12]
,(12, 16]
,...,(164, 196]
,...,(..., 512]
。每个类大小管理自己的OS页面列表,并将每个OS页面刻为自己的私有用途。 4kB OS页面上512字节类的示例可以是:
+---+---+---+---+---+---+---+---+
| a | b | c | d | e | f | g | h |
+---+---+---+---+---+---+---+---+
其中512字节的插槽a
到g
可用于分配,最新的插槽h
保留用于元数据(同一类中的空闲插槽,下一页/上一页)等等......)请注意,类大小越大,在最后一个插槽中浪费的就越多,这就是为什么更大的分配使用不同的方案。
取消分配时,页面保持在类大小,直到最后一个插槽被释放,此时页面再次为空,可以用于另一个组。
注意:默认情况下,Rust可执行文件使用jemalloc,此paper具有相关的具体详细信息。
对内存消耗意味着什么?
小平板方案 1 的最大内存消耗是一些OS页面,它们可以计算为每个桶大小消耗的OS页面的最大数量之和,它本身就是此大小的最大并发分配数乘以页面中适合的分配数(并向上舍入)。
这是因为如果您分配给定大小的1000个插槽,以随意的方式释放大部分插槽,在操作系统页面中挖出漏洞,然后重新分配相同大小的插槽,直到再次达到1000 ...然后你的内存消耗是不变的,因为分配器将使用已部分填充的OS页面中的空闲槽来完成第二波分配。
这意味着小班级的分配既快又好,但对分片的贡献不大。
当然,这忽略了一个程序的情况,该程序将进行1M 1字节分配,以一种使所有页面都使用的方式释放大部分,然后以2字节,3字节等方式执行相同操作......但这似乎是一个病态的案例。
1 是的,我正在撒谎。您还需要考虑分配器的内部结构开销以及它可以缓存一些未使用的OS页面以备将来分配的事实,......但是,它足以解释碎片的影响的
那么,碎片是一个问题吗?
嗯,它仍然可以。尽管在操作系统页面的粒度上,地址空间仍然可以分段。
对于虚拟内存,RAM不需要是连续的,因此只要有足够的空间就可以使用页面。也就是说,即使内存在物理上遍布整个RAM,地址空间也会给用户带来连续内存的错觉。
存在的问题是:这种连续记忆的错觉需要找到一个地址空间的连续区域,这个区域会受到碎片化。
这种碎片不会出现在小型请求中,但对于超过页面大小的请求,它们可能是个问题。目前,使用64位指针,这在实践中的问题要小得多(即使仅使用47位用户用户),但在32位程序中,它更容易浮出水面:例如,它是在32位地址空间中mmap
2GB文件非常困难,因为它立即占据了它的一半...假设没有杂散分配阻止它(在这种情况下请求将失败)。
战斗失败了吗?
那么,系统编程的主要优点是......你可以说系统语言。
如果您的程序具有典型分配器无法正常处理的分配行为,您可以:
sbrk
/ mmap
来处理问题)10年来,我个人从未需要,只在业余时间写作分配器以获得乐趣;但这是可能的。
答案 1 :(得分:2)
总结Matthieu的详细解释 -
Rust和C和C ++,当使用标准内存管理时,执行会导致内存碎片化。他们不进行碎片整理。
但是在绝大多数现实世界的使用案例中,碎片是如此之小,以至于它不是问题。
如果这是一个问题,你可以推出自己的分配器。