gpt4 book ai didi

memory-management - Rust 的内存管理会导致内存碎片化吗?

转载 作者:行者123 更新时间:2023-12-03 11:47:32 32 4
gpt4 key购买 nike

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

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

但是有没有可能内存块以一种顺序分配,然后以不同的顺序释放,从而导致碎片?如果可以防止这种情况,如何防止?如果发生这种情况,如何有效地管理内存碎片?如果对它们进行碎片整理,使用的方法是什么?

最佳答案

TL;DR:大多数程序永远不必担心 C、C++ 或 Rust 中的碎片。那些这样做的人将不得不自己处理。

Does the Rust programming language's automatic memory management need to reclaim fragmented memory?



Rust 没有自动内存管理;它具有手动内存管理,编译器会检查其正确性。这种差异可能听起来是理论上的,但它很重要,因为这意味着内存操作直接映射到源代码,幕后没有任何魔法。

一般来说,一种语言需要有一个 Compacting GC 才能压缩碎片化的内存。 Rust 与 C 和 C++ 一样,没有 GC,因此它的内存可能会根据使用情况进行碎片化,并且无法在没有程序释放烦人的块的情况下进行碎片整理,因为重定位是不可能的。

然而,在我们开始害怕碎片化之前,我们必须首先考虑它意味着什么。

碎片化有什么影响?

碎片会导致物理内存和地址空间的浪费:您的程序占用的比它使用的多。在极端情况下,这种浪费可能会阻止分配请求,即使未使用的内存量应该足以授予它们。

在与 GC 语言进行并行时,重要的是要意识到大多数 GC 语言也会造成一些浪费。

确实,值得注意的是,碎片化并不是浪费的唯一来源;过度分配也是一个常见的“问题”:
  • Vec将分配 2 的幂的元素数量,但也许您只使用 2^N + 1 ,浪费2^N - 1插槽
  • BTreeMapHashMap分配比实际使用更多的空间
  • 甚至内存分配器通常会分配预定义大小的块,因此要求 157 字节实际上可能会四舍五入为 196 字节,浪费 39 字节

  • 这甚至没有计算管理该内存所造成的浪费,因为内存分配器会维护一些状态以了解它拥有哪些页面以及其中使用了哪些页面。

    这强调了一个非常重要的事实:如果由于簿记/开销而消耗了如此多的内存,那么消除碎片就没有什么意义了,因为您的分配方案强加给了您同样的问题。

    现代分配器如何管理内存?

    现代分配器不是您的 da 的空闲列表分配器。

    典型的分配方案相对简单,但非常擅长为小请求保持碎片化:
  • 用于“大”请求的大块内存(接近或超过操作系统页面大小:一般为 4kB)
  • 用于“较小”请求的小内存块

  • 对于小板坯,按尺寸定义了许多类。例如: (0, 8] , (8, 12] , (12, 16] , ..., (164, 196] , ..., (..., 512] .每个类大小管理自己的操作系统页面列表,并将每个操作系统页面雕刻为自己的私有(private)使用。 4kB 操作系统页面上的 512 字节类的示例可能是:
    +---+---+---+---+---+---+---+---+
    | a | b | c | d | e | f | g | h |
    +---+---+---+---+---+---+---+---+

    其中 512 字节插槽 a通过 g可用于分配和最新插槽 h保留用于元数据(空闲插槽、同一类中的下一页/上一页等......)。请注意,类大小越大,最后一个槽中浪费的就越多,这就是为什么更大的分配使用不同的方案。

    释放时,页面保持在类大小,直到最后一个槽被释放,此时页面再次为空白,可用于另一个组。

    注意:默认情况下,Rust 可执行文件使用 jemalloc , 这个 paper有关于它的具体细节。

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

    smallslabs方案1的最大内存消耗是OS页面的数量,可以计算为每个bucket大小消耗的最大OS页面数量的总和,它本身就是这个大小的最大并发分配数乘以除以适合页面的分配数量(并向上取整)。

    这是因为如果你分配 1000 个给定大小的插槽,以随意的方式释放它们中的大部分,在操作系统页面上戳洞,然后重新分配相同大小的插槽,直到你再次达到 1000 ......然后你的内存消耗是恒定的,因为分配器将使用已经部分填充的操作系统页面中的空闲槽来完成第二波分配。

    这意味着小类(class)的分配既快又不造成碎片化。

    当然,这忽略了一个程序的情况,它会进行 1M 1 字节的分配,以保留所有页面使用的方式释放大部分,然后对 2 个字节、3 个字节等执行相同的操作……但这似乎是一个病理案例。

    1 是的,我在说谎。您还需要考虑分配器的内部结构开销,以及它可能会缓存一些未使用的操作系统页面以准备 future 分配的事实,……不过,这足以解释碎片的影响。

    那么,碎片化是一个问题吗?

    嗯,它仍然可以。地址空间仍然可以碎片化,尽管是在操作系统页面的粒度上。

    对于虚拟内存,RAM 不需要是连续的,只要有足够的空间就可以使用页面。也就是说,即使内存物理上分布在整个 RAM 上,地址空间也会给用户一种连续内存的错觉。

    问题就在这里:这种连续内存的错觉需要找到地址空间的一个连续区域,该区域容易碎片化。

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

    战斗失败了吗?

    嗯,系统编程的主要优点是……您可以使用系统语言。

    如果您的程序具有典型分配器不能很好处理的分配行为,您可以:
  • 控制导致问题的特定分配(使用 sbrk/mmap 自己处理它们)
  • 或者只是重写一个专门调整过的分配器

  • 10 年来,我个人从来不需要,只是在业余时间写分配器;但这是可能的。

    关于memory-management - Rust 的内存管理会导致内存碎片化吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40658045/

    32 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com