#memory-allocator #allocator #constant-time #real-time

无需std tlsf

基于优化内存占用实现的二级分割拟合(TLSF)分配器

2个稳定版本

1.1.0 2024年1月12日
1.0.0 2023年12月5日

#83 in 内存管理

MIT/Apache

98KB
2.5K SLoC

基于优化内存占用实现的二级分割拟合(TLSF)分配器

设计特性

  • allocdealloc操作在有限常数时间内执行(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:实时系统的全新动态内存分配器."

详细信息请访问 http://www.gii.upv.es/tlsf/main/docs.html

依赖项

~57–310KB