2个稳定版本
1.1.0 | 2024年1月12日 |
---|---|
1.0.0 | 2023年12月5日 |
#83 in 内存管理
98KB
2.5K SLoC
基于优化内存占用实现的二级分割拟合(TLSF)分配器
设计特性
alloc
和dealloc
操作在有限常数时间内执行(O(1)
)- 良好的拟合策略
- 立即合并增加了可预测性并减少了碎片化
更多详情请查看“参考”部分的链接中的论文
[^1]: 在最坏的情况下,realloc
操作涉及到一个memcpy
操作,它在线性时间内执行(O(N)
)
实现特性
- 与原始TLSF相比,由于指针压缩(
usize
->u16
),内存占用减少 - 优化时无panic分支,即使启用了调试断言
- 遵循严格的来源,由
miri
检查
已拒绝的特性
GlobalAlloc
实现。
原因:它可以在本库之上实现,而每个实现都需要做出特定于应用程序的决定,如同步以及是否“禁止”没有有限执行时间的类似realloc
的操作,总是为它们触发OOM。
这些决定最好留给应用程序作者。
限制
- 只能管理最多256 KiB的连续内存
- 只能分配最多62 KiB的大小
示例
分配器管理可变借用内存;内存甚至可以是栈分配的。
use core::alloc::Layout;
use core::mem::MaybeUninit;
use tlsf::Tlsf;
let mut tlsf = Tlsf::<1>::empty();
let mut memory = [MaybeUninit::uninit(); 256];
tlsf.initialize(&mut memory);
let alloc: &mut [MaybeUninit<u32>] = tlsf.memalign(Layout::new::<u32>()).unwrap();
assert!(alloc.len() >= 1);
alloc.iter_mut().for_each(|mu| { mu.write(42); });
当对对齐不重要时,可以使用malloc
方法。返回的块将具有最小的4字节对齐。
use core::alloc::Layout;
use core::mem::MaybeUninit;
use core::num::NonZeroU16;
use tlsf::Tlsf;
let mut tlsf = Tlsf::<1>::empty();
let mut memory = [MaybeUninit::uninit(); 256];
tlsf.initialize(&mut memory);
let size = NonZeroU16::new(4).unwrap();
let alloc: &mut [MaybeUninit<u32>] = tlsf.malloc(size).unwrap();
assert!(alloc.len() >= 1);
alloc.iter_mut().for_each(|mu| { mu.write(42); });
分配器跟踪初始内存池的生存期,分配不能超出其范围。以下代码无法编译
use core::alloc::Layout;
use core::mem::MaybeUninit;
use tlsf::Tlsf;
let mut tlsf = Tlsf::<1>::empty();
{
let memory = [MaybeUninit::uninit(); 256];
tlsf.initialize(&mut memory); //~ error: `memory` does not live long enough
// `memory` goes out of scope here
}
let alloc = tlsf.memalign(Layout::new::<u64>());
由于此生存期限制,与#[global_allocator]
一起使用时,需要初始内存池具有'static
生存期。可以在本项目的仓库的thumbv7em
目录中找到一个GlobalAlloc
实现的示例。
参数
TLSF分配器有两个参数:FL和SL(有关详细信息,请参阅相关论文)。此实现将SL硬编码为16
。可以通过Tlsf
类型的FLL
类型参数控制FL。下表显示了FLL
的可能值及其对分配器的影响
FLL |
FL | MAX_ALLOC_SIZE |
HEADER_SIZE |
---|---|---|---|
1 | 6 | 60 B | 36 B |
2 | 7 | 124 B | 72 B |
3 | 8 | 248 B | 104 B |
4 | 9 | 496 B | 140 B |
5 | 10 | 992 B | 172 B |
6 | 11 | 1,984 B | 208 B |
7 | 12 | 3,968 B | 240 B |
8 | 13 | 7,936 B | 276 B |
9 | 14 | 15,872 B | 308 B |
10 | 15 | 31,744 B | 344 B |
11 | 16 | 63,488 B | 376 B |
从分配器请求超过MAX_ALLOC_SIZE
字节的内存将始终导致OOM条件。请注意,当通过memalign
请求大于4的对齐时,MAX_ALLOC_SIZE
的有效值会减少,因为可能需要填充以满足对齐要求。
HEADER_SIZE
是分配器的固定内存开销。每个由分配器管理的内存块都有4或8字节的额外开销。
性能
基准配置
- rustc: 1.74.0
- 目标:
thumbv7em-none-eabi
- 配置文件:发布(优化级别=3,lto='fat',codegen-units=1)
- FLL: 2
二进制(“Flash”)大小
~1,594 B(.text
部分)如果仅使用memalign
~1,440 B(.text
部分)如果仅使用malloc
测量方法
#![no_main]
#![no_std]
#[no_mangle]
fn _start() -> [usize; 3] {
[
Tlsf::<2>::free as usize,
Tlsf::<2>::initialize as usize,
Tlsf::<2>::memalign as usize, // OR malloc
]
}
$ size -A binary
section size addr
.ARM.exidx 16 65748
.text 1594 131300
.debug_aranges 0 0
请注意,这些测量使用了针对速度的优化,旨在为您提供一个关于库二进制占用大小的概念。应用针对大小的优化将明显导致更小的占用。
执行时间
工作负载
memalign
:N个随机大小的(<MAX_ALLOC_SIZE
)分配,具有随机对齐(< = 32 B),直到OOM
操作 | 最小值 | 最大值 |
---|---|---|
memalign (所有) |
66 | 407 |
memalign (失败) |
66 | 125 |
memalign (成功) |
193 | 407 |
释放 |
96 | 337 |
"失败"表示memalign
返回了None
;"成功"表示它返回了Some
;"所有"考虑了这两种情况。
![][memalign-histograms]
malloc
:N个随机大小的(<MAX_ALLOC_SIZE
)分配,直到OOM,然后以相反的顺序进行N次释放
操作 | 最小值 | 最大值 |
---|---|---|
malloc (所有) |
91 | 292 |
malloc (失败) |
91 | 103 |
malloc (成功) |
173 | 292 |
释放 |
98 | 237 |
![][malloc-histograms]
用于进行测量的代码可以在项目存储库的thumbv7em
目录中找到。
参考文献
以下论文中描述和讨论了TLSF分配器的设计
- "实时系统的常时动态存储分配器"。
- "常量时间动态存储分配器的实现."
- "TLSF:实时系统的全新动态内存分配器."
依赖项
~57–310KB