#memory-allocator #allocator #drop #arena #concurrency #no-std

no-std blink-alloc

快速、并发、基于区域的分配器,具有释放支持

9个版本

0.3.1 2023年8月31日
0.3.0 2023年5月10日
0.2.5 2023年3月8日
0.2.1 2023年2月28日
0.0.0 2023年2月27日

#106 in 内存管理

Download history 70/week @ 2024-04-07 76/week @ 2024-04-14 111/week @ 2024-04-21 105/week @ 2024-04-28 50/week @ 2024-05-05 63/week @ 2024-05-12 85/week @ 2024-05-19 105/week @ 2024-05-26 82/week @ 2024-06-02 163/week @ 2024-06-09 120/week @ 2024-06-16 131/week @ 2024-06-23 168/week @ 2024-06-30 107/week @ 2024-07-07 108/week @ 2024-07-14 146/week @ 2024-07-21

536 每月下载量
3 crates 中使用

MIT/Apache

165KB
3K SLoC

blink-alloc

crates docs actions MIT/Apache loc

Blink-alloc是一个基于常见思想的极其快速的分配器,即通过线性地将游标通过内存块进行压缩分配,并通过将游标设置回开始位置一次性重置所有内容。

使用Rust的借用检查器,此想法可以安全实现,防止在分配的内存使用期间发生重置。

跳转到示例

设计

blink-分配器充当某些底层分配器的适配器。从底层分配器获取内存块,并从中提供分配。当块耗尽时,blink-分配器从底层分配器请求新的更大内存块。

重置时,除了最后一个块之外的所有块都返回给底层分配器。目标是分配足够大的单个块,以在重置之间提供所有分配,这样底层分配器在初始预热阶段之后就不会被触及。使分配和重置几乎免费。

blink-分配器的实现方式提供了在新的布局适合当前分配内存块时的廉价分配缩小。这对于像 Vec::shrink_toVec::shrink_to_fit 这样的东西来说肯定是这样。当在分配的末端完成时,它还释放了用于重用的内存。

此外,在分配的末端进行时,还可能实现快速分配增长。当使用线程局部版本时,这很容易控制。这为在完全 Vec 容量时进行 Vec::push 调用开辟了一条途径。

简单的实现使用非同步内部可变性,因此只能在单线程场景下工作。它可以发送到另一个线程,但不能共享。

为每个线程维护一个实例是解决这个问题的一种方法。但这并不总是可能或理想的。

这个包为多线程场景提供了额外的实现,但会有轻微的性能损失。为了消除它,可以创建一个单线程blink-allocator的本地副本,以从共享的多线程blink-allocator中获取内存,从而在保持单线程版本快速分配的同时,跨线程重用内存。它最适合fork-join类型的并行性,因为它需要一个多线程blink-allocator不共享的点来重置它。

对于基于任务的并行性,可以构建一个blink-allocators的缓存。任务可以借用单线程blink-allocator,完成后归还。这将使缓存充满预先预热好的blink-allocators。

单线程

BlinkAlloc是分配器的单线程版本。它使用非同步内部可变性以实现最快性能。BlinkAlloc可以发送到其他线程,但不能共享。

对于多线程,可以为每个线程/任务创建一个BlinkAlloc实例。尽管在某些方面可能不可能或不实际,所以考虑以下替代方案。

多线程

SyncBlinkAlloc在多线程环境中工作,并在线程之间共享一个内存块。减少整体内存使用,跳过新线程和任务的预热阶段。SyncBlinkAlloc可以产生LocalBlinkAlloc实例,它们的工作方式类似于BlinkAllocLocalBlinkAlloc实例从共享的SyncBlinkAlloc中获取内存块。SyncBlinkAlloc仍然需要独占访问来重置。最适合fork-join风格的并行性,重置发生在所有线程都加入之后。

对于基于任务的并行性,此包提供了BlinkAllocCache类型,它是一组BlinkAlloc实例的缓存。任务可以从缓存中检索blink分配器,使用它,然后将其返回到缓存。缓存保持BlinkAlloc实例预热。

分配器API

分配器实现了来自alloc包的Allocator接口或它的副本,当没有启用"nightly"功能时。启用"nightly"需要Rust功能allocator_api,并且仅在nightly版本中工作。一旦Allocator特质稳定,该功能将不会做任何事情,并在下一个主要版本中删除。

不带集合的blink

BlinkAlloc 和其相关实现 Allocator,来自 allocator_api 不稳定特性。只有在为这个包启用 "nightly" 特性时,它才可用。否则 Allocator 不是 core::alloc::Allocator,而是在包中定义的重复项。通过 allocator_api,可以在支持它的集合类型中使用 BlinkAlloc 和其他分配器类型。目前 VecVecDequeBTreeMapBTreeSet 可以使用用户提供的分配器。此外,hashbrown::HashMaphashbrown::HashSet 也支持它,需要启用 "nightly" 特性。

然而,在稳定版本中目前不能使用它。

仍然可以使用 blink-allocators 以安全的方式 - 欢迎使用 Blink 分配器适配器。将任何东西放入 Blink 分配器分配的内存中。值、迭代器、用于构造值的闭包、切片和字符串。它与所有东西都兼容*。使用底层 blink-分配器,并返回放置在分配内存中的值的可变引用。默认情况下,在重置时丢弃放置的值。

* 如果您的用例不适用,请要求 API 扩展。

示例

Blink 分配器适配器的使用。初始化并开始放置值。

use blink_alloc::Blink;

#[cfg(feature = "alloc")]
fn main() {
    // `Blink::new` uses `BlinkAlloc<Global>`
    let mut blink = Blink::new();

    // Allocates memory and moves value there.
    // Returns mutable reference to it.
    let x = blink.put(42);
    assert_eq!(*x, 42);
    *x = 11;

    // Copies string slice to the blink-allocated memory.
    let string = blink.copy_str("Hello world");

    // Mutable reference allows string mutation
    string.make_ascii_lowercase();
    assert_eq!(string, "hello world");

    // Consumes iterator and returns all values from it in slice.
    // Works fine on problematic iterators with terrible size hint.
    let slice = blink.emplace().from_iter((0..10).filter(|x| x % 3 != 0));
    assert_eq!(&*slice, &[1, 2, 4, 5, 7, 8]);
    blink.reset();
}
#[cfg(not(feature = "alloc"))] fn main() {}
#![cfg_attr(feature = "nightly", feature(allocator_api))]
use blink_alloc::BlinkAlloc;

#[cfg(feature = "alloc")]
fn main() {
    use allocator_api2::vec::Vec;

    let mut blink = BlinkAlloc::new();
    let mut vec = Vec::new_in(&blink);
    vec.extend((1..10).map(|x| x * 3 - 2));

    drop(vec);
    blink.reset();
}
#[cfg(not(feature = "alloc"))] fn main() {}

No-std

这个包支持 no_std 环境。默认启用 "alloc" 特性,并添加了对 alloc 包的依赖。

许可证

根据您的选择,许可如下

贡献

除非您明确表示,否则根据 Apache-2.0 许可证定义的,您有意提交以包含在作品中并由您提交的贡献,将按上述方式双重许可,不附加任何其他条款或条件。

依赖关系

~0.2–5MB