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