#memory #slab #malloc #alloc #os

slabmalloc

基于slab的简单malloc实现。可以作为独立库使用,也可以为rust的liballoc库提供必要的接口。slabmalloc只依赖于libcore。

17个版本 (10个破坏性更新)

0.11.0 2022年8月24日
0.10.0 2021年6月2日
0.9.0 2021年6月2日
0.7.0 2019年11月13日
0.2.0 2015年7月30日

#101 in 内存管理

MIT 许可证

75KB
1.5K SLoC

slabmalloc 构建状态 Crates.io

基于slab的简单malloc实现,用于为rust的liballoc库提供必要的接口。slabmalloc只依赖于libcore,并设计用于内核级代码,因为客户端只需要提供必要的机制来分配和释放4KiB帧(或非x86硬件上的任何其他默认页面大小)。

构建

默认情况下,此库应使用Rust编译器的nightly版本通过cargo build进行编译。

将以下行添加到Cargo.toml依赖项

slabmalloc = ...

由于使用了const_fn,如果您使用的是稳定版的rustc,则需要禁用unstable特性

slabmalloc = { version = ..., default_features = false }

文档

API使用

slabmalloc有两个主要组件在此处描述。但是,如果您只想实现GlobalAlloc特质,请查看提供的示例

它提供了一个ZoneAllocator来分配任意大小的对象

let object_size = 12;
let alignment = 4;
let layout = Layout::from_size_align(object_size, alignment).unwrap();

// We need something that can provide backing memory
// (4 KiB and 2 MiB pages) to our ZoneAllocator
// (see tests.rs for a dummy implementation).
let mut pager = Pager::new();
let page = pager.allocate_page().expect("Can't allocate a page");

let mut zone: ZoneAllocator = Default::default();
// Prematurely fill the ZoneAllocator with memory.
// Alternatively, the allocate call would return an
// error which we can capture to refill on-demand.
unsafe { zone.refill(layout, page)? };

let allocated = zone.allocate(layout)?;
zone.deallocate(allocated, layout)?;

以及一个SCAllocator来分配固定大小的对象

let object_size = 10;
let alignment = 8;
let layout = Layout::from_size_align(object_size, alignment).unwrap();

// We need something that can provide backing memory
// (4 KiB and 2 MiB pages) to our ZoneAllocator
// (see tests.rs for a dummy implementation).
let mut pager = Pager::new();
let page = pager.allocate_page().expect("Can't allocate a page");

let mut sa: SCAllocator<ObjectPage> = SCAllocator::new(object_size);
// Prematurely fill the SCAllocator with memory.
// Alternatively, the allocate call would return an
// error which we can capture to refill on-demand.
unsafe { sa.refill(page) };

sa.allocate(layout)?;

性能

目前还没有对性能进行优化或分析。但是,如果您坚持,这里有一些单线程的基准测试数字

test tests::jemalloc_allocate_deallocate       ... bench:           6 ns/iter (+/- 0)
test tests::jemalloc_allocate_deallocate_big   ... bench:           7 ns/iter (+/- 0)
test tests::slabmalloc_allocate_deallocate     ... bench:          16 ns/iter (+/- 0)
test tests::slabmalloc_allocate_deallocate_big ... bench:          16 ns/iter (+/- 1)

对于多线程来说,为每个线程提供它自己的ZoneAllocator实例是有益的。代码中的dealloc可以处理在任何线程上释放任何指针。与之相比,该SMP设计(原子位字段操作)可能不如jemalloc的全分区方法那样好。

关于命名

我们称我们的分配器为slabmalloc;然而,这个名字可能会引起混淆,因为slabmalloc与Jeff Bonwick发表的开创性的论文中描述的“slab分配器”略有不同。slabmalloc实际上只是一个具有大小类和每个类不同分配器的malloc实现(一个隔离存储分配器),同时吸收了一些来自slab分配的简单而有效的方法。

对熟悉slab分配器的人来说,一些值得注意的差异

  • slab分配器构造函数需要请求一个对象构造函数和析构函数来初始化/反初始化对象。slabmalloc更类似于malloc;它只是处理内存,而不是对象缓存。

  • slab分配器中的一个slab由一个或多个页面的虚拟连续内存组成,分割成相等大小的块,用引用计数来表示有多少这样的块已被分配。相反,slabmalloc使用(缓存行大小)位图来跟踪slab内的对象。同样,slab分配器构建一个空闲对象的链表,而slabmalloc则在slab中扫描位图以找到空闲槽。

  • 对于大对象,slab分配器不会在slab页面中嵌入元数据。因为,你只能在一个4 KiB页面上放一个2 KiB的缓冲区加上嵌入的slab数据。此外,对于大(多页)slab,它无法从缓冲区地址确定slab数据地址。所以使用一个针对缓存的单个哈希表来映射分配的对象到元数据内存。在slabmalloc中,元数据始终位于页面末尾。它使用不同的slab大小来确保大对象不会分配在小slab页面上。在rust中,这个问题得到了缓解,因为我们在释放时也接收到对象的大小(我们通过查看要释放的对象的大小来确定底层slab的大小)。

依赖关系

~94KB