这是一篇关于 Rust 语言中内存分配器的博客文章,主要内容如下:
- 背景:Rust 是一种流行的低级语言,以其速度和内存安全性而受到称赞,但这两者都高度依赖于所使用的内存分配器。通常 Rust 使用系统分配器,其性能并非最佳,但自 2018 年以来,程序员可以设置自定义全局分配器。
- 设置自定义分配器:在 Rust 中设置自定义分配器非常简单,只需实现
GlobalAlloc
trait 并添加#[global_allocator]
属性,如use rsbmalloc::RSBMalloc; #[global_allocator] static ALLOCATOR: RSBMalloc = RSBMalloc::new();
。可以在crates.io
上查看#allocator
标签来选择好的全局分配器,如snmalloc-rs
、jemalloc
、mimalloc
和rsbmalloc
等。 - Rust 中自定义分配器的工作原理:如果现有的分配器不满足需求,可以编写自己的分配器。计算机内存以字节数组形式组织,分配器将字节数组分割成可用于各种变量的部分。在用户空间程序中,程序具有虚拟地址空间,指针需要通过页表映射到实际物理位置,否则会出现段错误。可以使用
mmap
函数创建页,使用munmap
函数释放内存。在 Rust 中,分配器需要实现GlobalAlloc
trait,其中包含alloc
、dealloc
等函数。 - 编写页分配器:最简单的分配器分配整个页,首先需要找到页大小,如在 macOS 上页大小是
vm_page_size
,在 Linux 或 Windows 上需要调用函数并使用lazy_static
缓存。然后可以在GlobalAlloc
impl 块中编写alloc
函数,通过mmap
请求内存,并设置相应的标志。dealloc
函数用于释放内存,通过munmap
或VirtualFree
函数实现。 - 编写分箱分配器:为了提高分配速度,可以使用分箱分配器,将整个页分配给特定大小的分配。分箱分配器中的
bin
是包含多个相同大小slot
的内存区域,slot
是一个联合类型,可以是包含程序数据的缓冲区,也可以是指向下一个空闲slot
的指针或空指针。rsbmalloc
使用 15 个大小翻倍的 bin,在alloc
函数中根据请求的大小匹配相应的 bin 并进行分配,在dealloc
函数中将指针转换为Slot
指针并设置为下一个空闲slot
。这种分配器的缺点是小于 64KiB 的分配不会释放回操作系统,单线程效率低,可能存在空间浪费等问题。 - 编写带有线程缓存的多线程分配器:理论上前面编写的分配器是多线程的,但由于只有一个静态的 bin 列表,并且每个分配都需要使用互斥锁,导致性能下降。可以使用线程缓存的方法,为每个线程分配一组 bin,避免锁竞争。
rsbmalloc
通过预创建线程缓存并使用线程 ID 映射到线程缓存来实现多线程分配器,在alloc
、dealloc
和realloc
函数中添加获取线程缓存的逻辑,可以显著提高多线程性能。 - 在 Rust 中调试分配器:调试分配器时,
println!()
和panic!()
宏可能会因为调用堆分配函数而失效,需要使用lldb
或gdb
等调试器来查看堆栈跟踪和变量,帮助找到问题。在开发分配器时,会看到很多信号,如SIGABRT
、SIGSEGV
等,需要调试器来分析问题。
总之,自定义内存分配器可以根据特定需求优化性能,也可以使用像snmalloc
这样的更快的分配器来提高程序的分配速度,同时不放弃 Rust 的内存安全保证。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。