关注了就能看到更多这么棒的文章哦~
Slabs, sheaves, and barns
By Jonathan Corbet
February 24, 2025
Gemini-1.5-flash translation
https://lwn.net/Articles/1010667/
内核的 slab (slab:一种内存分配机制)分配器负责分配小的(通常小于一个内存页)内存块。对于许多工作负载来说,对象分配和释放的速度是影响整体性能的关键因素之一,因此,长期以来,人们投入了大量精力来优化 slab 分配器。既然内核现在已经精简到一个单独的 slab 分配器,内存管理开发人员就可以自由地向其中添加复杂性;slab 维护者 Vlastimil Babka 最新的举措是per-CPU sheaves 补丁集。
许多内核开发人员使用诸如 kmalloc()
的函数与 slab 分配器交互,这些函数可以分配任何(合理的)大小的对象。然而,slab 分配器还有一个更底层的接口,它处理固定大小的对象;子系统经常使用该接口来频繁地分配和释放相同大小的对象。好奇的人可以通过查看 /proc/slabinfo
来查看他们系统中的所有专用 slab。许多核心内核操作都涉及到从这些 slab 中分配对象并返回它们,因此,slab 分配器已经获得了许多特性,包括 NUMA 感知和批量操作,以加速分配和释放。
但是,很自然的,它永远不够快。
提高当今多处理器系统性能的关键之一是尽可能避免 CPU 之间的交互。交互会导致争用、锁延迟和缓存行反弹,所有这些都会损害性能,但是一个可以像系统中不存在其他部分一样运行的 CPU 可以全速前进。这种理解推动了 per-CPU 数据结构在内核中的采用。slab 分配器已经使用了 per-CPU 数据,但它仍然有足够的跨 CPU 交互来降低速度。
Sheaves (sheaves:一组对象的集合,这里指 per-CPU 对象缓存)是 Babka 的补丁集中引入的一个概念;本质上,它们是 per-CPU 的对象缓存,可以用来响应分配请求,而无需与系统中的任何其他 CPU 交互。默认情况下,sheaves 是禁用的,但是可以通过在传递给 kmem_cache_create()
的 kmem_cache_args
结构中的新字段 sheaf_capacity
中设置一个非零值来为特定的 slab 启用它们。该值应该是在单个 sheaf 中缓存的对象数量;将 sheaf 用法添加到 maple-tree 数据结构的补丁 将其设置为 32。
启用 sheaves 后,分配器将为每个 CPU 维护一个具有给定对象数量的 sheaf。只要有可能,分配请求将从这个 sheaf 中得到满足,并且释放的对象如果有空间,将被放回 sheaf 中。这会将分配和释放操作转换为纯粹的本地分配,可以快速执行;不需要锁定(甚至不需要原子操作)。每个 CPU 还会维护第二个(备份)sheaf;当发现主 sheaf 为空时,将从备份 sheaf 中分配一个对象。如果在释放对象时主 sheaf 已满,则如果可能,该对象将被放入备份 sheaf 中。
当两个 sheaves 都满时,将不再有地方可以通过简单的赋值来存放释放的对象;这就是 “barn” (barn:存放未使用的 sheaves 的仓库)的用武之地。barn 只是一个用于存放当前未被任何 CPU 用于缓存的 sheaves 的地方;系统中的每个 NUMA 节点都有一个 barn。一旦一个 CPU 填满了它的 sheaves,它将尝试将一个放入 barn 中;如果一个 CPU 的 sheaves 为空,它将尝试从 barn 中获得一个满的 sheaf。在任何一种情况下,此操作都较慢,因为需要锁定才能安全地访问共享的 barn,但它仍然比完全进入 slab 分配器要快。
barn 保存两组 sheaves — 一组用于完整的 sheaves,另一组用于空的 sheaves。如果一个 CPU 正在释放大量对象,它可以将其完整的 sheaves 放入 barn 中,并获得空的 sheaves 来替换它们。barn 可以容纳的 sheaves 数量有限;在当前的补丁集中,完整和空的 sheaves 各自被硬编码为 10 个。如果一个 CPU 尝试将一个 sheaf 放入一个已满的 barn 中,那么该 sheaf 将被释放,以及它包含的任何对象,都将返回到它们来自的 slabs 中。
到目前为止,这个系列有可能极大地加速为分配和释放大量 slab 对象的工作负载的内存分配操作。但是,该系列后面添加了其他几个特性,以使 sheaves 更有用。
其中之一是对 kfree_rcu()
的增强,它将延迟对象的释放,直到 read-copy-update (RCU:一种并发控制机制)宽限期过去之后,从而确保该对象不再有任何活动引用。维护第三个 per-CPU sheaf 以保存用 kfree_rcu()
释放的对象;一旦 sheaf 填满,它将被传递给 RCU 子系统以进行宽限期等待。一旦发生这种情况,将尝试将 sheaf 放回 barn 中。
另一个补充是预分配。内核中的许多地方都必须立即分配内存,当然也不能阻塞分配线程。还有许多代码路径无法以任何合理的方式处理分配失败。在大多数这些情况下,有机会在进入更受约束的代码之前预先分配所需的内存。长期以来,内核一直维护着像 mempools 这样的子系统来满足这种需求。
但是,如果内核可以进入关键代码,并且知道它有一个装满对象的 per-CPU sheaf 可供使用,那么许多这些问题(以及对 mempool 的需求)就会消失。为此,该系列提供 一组函数 用于处理特殊用途的 sheaves。调用 kmem_cache_prefill_sheaf()
将返回一个包含至少所需数量的对象的 sheaf,如果可能,从 barn 中获取它。然后,可以使用 kmem_cache_alloc_from_sheaf()
从 sheaf 中分配对象,并保证至少所需数量的对象的成功。其他函数可用于将 sheaf 返回到 barn 或将更多对象放入 sheaf 中。这些特殊用途的 sheaves 的作用类似于 mempool,但它们旨在是短期的,而 mempool 通常存在于系统的整个生命周期中。
这个系列似乎没有引起太多的争议,尽管可能开发人员会将他们的评论保留到即将到来的 Linux Storage, Filesystem, Memory-Management, and BPF Summit,该峰会将于 3 月下旬举行。考虑到对更快的内存分配和释放的渴求,sheaves 和 barns 似乎很可能会在某个时候被添加到组合中。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~