8 个版本
0.4.1 | 2024年2月24日 |
---|---|
0.4.0 | 2023年4月28日 |
0.3.0 | 2022年11月23日 |
0.2.0 | 2022年10月15日 |
0.1.3 | 2022年8月11日 |
#34 在 内存管理 中
2,545 每月下载量
在 3 个crate(2 个直接) 中使用
39KB
623 代码行
lol_alloc
一个简单得令人发笑的wasm全局分配器。
类似于 wee_alloc,但由于我在名字中使用了更瘦的字母,因此更小。
lol_alloc
是一组简单的wasm global_allocator
。
我编写 lol_alloc
是为了学习分配器(我之前没有编写过),因为 wee_alloc
看起来不再维护 并且 存在内存泄漏。在查看 wee_alloc
的实现(我未能理解或修复)后,我想知道制作一个wasm全局分配器到底有多难,并且似乎提供一个分配器对rust wasm社区可能是有用的。
用法
您可以使用 LockedAllocator<FreeListAllocator>
替换 global_allocator
以使用 wasm32
构建
extern crate alloc;
#[cfg(target_arch = "wasm32")]
use lol_alloc::{FreeListAllocator, LockedAllocator};
#[cfg(target_arch = "wasm32")]
#[global_allocator]
static ALLOCATOR: LockedAllocator<FreeListAllocator> = LockedAllocator::new(FreeListAllocator::new());
为了更小的文件大小和略好的性能,单线程WASM应用程序可以使用 AssumeSingleThreaded
而不是 LockedAllocator
extern crate alloc;
use lol_alloc::{AssumeSingleThreaded, FreeListAllocator};
// SAFETY: This application is single threaded, so using AssumeSingleThreaded is allowed.
#[global_allocator]
static ALLOCATOR: AssumeSingleThreaded<FreeListAllocator> =
unsafe { AssumeSingleThreaded::new(FreeListAllocator::new()) };
不需要任何分配器的应用程序可以使用 FailAllocator
extern crate alloc;
#[cfg(target_arch = "wasm32")]
use lol_alloc::FailAllocator;
#[cfg(target_arch = "wasm32")]
#[global_allocator]
static ALLOCATOR: FailAllocator = FailAllocator;
仅进行有限小数量分配的应用程序,因此不需要释放内存,可以使用漏桶分配器之一。以下展示了LeakingPageAllocator
(如下所示),AssumeSingleThreaded<LeakingAllocator>
和LockedAllocator<LeakingAllocator>
是此情况的最佳选择。
extern crate alloc;
#[cfg(target_arch = "wasm32")]
use lol_alloc::LeakingPageAllocator;
#[cfg(target_arch = "wasm32")]
#[global_allocator]
static ALLOCATOR: LeakingPageAllocator = LeakingPageAllocator;
线程安全
LeakingAllocator
和FreeListAllocator
不是Sync
,必须包裹在LockedAllocator
或不可安全使用的AssumeSingleThreaded
中分配给静态(这是由Rust类型系统强制执行的)。如今在wasm中可以支持多线程:除非您确信所有分配和释放都来自单个线程,否则不要使用AssumeSingleThreaded
。
FailAllocator
和LeakingPageAllocator
是线程安全的,无需任何包裹。
状态
一些项目显然已经使用了这个库,并且没有报告任何问题(也没有报告成功,所以请自行承担风险)。
FreeListAllocator拥有相当好的测试套件,其余的分配器都很简单,并且至少进行了最小测试。
如果您使用它,请报告任何错误。如果它确实对您有用,也请让我知道(您可以通过提交问题来报告)。
分配器的大小包括示例中的开销(使用rustc 1.65.0和wasm-pack 0.10.3编译)
FailAllocator
:195字节:分配时出错。操作为O(1)。LeakingPageAllocator
:230字节:为每个分配分配页面。操作为O(1)。LeakingAllocator
:增长指针分配器,根据需要扩展堆,并且不重用/释放内存。操作为O(1)。除了对齐之外,没有其他分配空间开销。AssumeSingleThreaded<LeakingAllocator>
:356字节。LockedAllocator<LeakingAllocator>
:484字节。
FreeListAllocator
:基于空闲列表的分配器。操作(包括分配和释放)为O(空闲列表长度),但它会合并相邻的空闲列表节点。将分配大小向上取整至少为2个字,但否则应使用所有空间。即使是来自高对齐分配的间隙也最终会进入其空闲列表,以便用于较小的分配。AssumeSingleThreaded<FreeListAllocator>
:654字节。LockedAllocator<FreeListAllocator>
:775字节。
- 内置Rust分配器:5034字节。
如果您可以承受额外的代码大小,请使用内置的rust分配器:它是一个更好的分配器。
仅支持wasm32
:其他目标可能可以构建,但这些分配器将无法在这些目标上工作(除了:FailAllocator
,它在所有平台上都会出错)。
性能
渐近行为和代码大小在上述状态部分中已记录。
这些分配器都针对简单性进行了优化(因此代码大小和可维护性),而不是运行时性能。它们的运行时性能数据尚未收集,但将会很有趣:请随意发送带有基准测试和/或性能数据的补丁。
如果您关注运行时性能,您可能应该使用内置分配器,或者至少仔细测量使用这些分配器对您代码的影响。
健壮性
该库中指针操作的健壮性目前尚不明确。由于 wasm32::memory_grow 不返回指针,因此没有“原始指针”,因此无法遵循 严格来源 规则。尝试确定该库对指针的使用是否至少满足在解引用时满足可解引用的要求,与定义为 可解引用
可解引用:从指针开始的给定大小的内存范围必须全部在单个已分配对象的边界内。
"已分配对象"的定义在此处不明确。如果可增长的wasm堆被视为单个已分配对象,那么所有这些分配器至少在这个方面可能是好的。但是,如果将每个对 wasm32::memory_grow
的调用视为创建一个新的已分配对象,那么 FreeListAllocator
中的空闲列表合并将是不健壮的,并可能导致未定义的行为。
测试
有一些常规的rust单元测试(使用 cargo test
运行),这些测试使用 MemoryGrower
的测试实现。
还有一些 wasm-pack 测试(使用 wasm-pack test --node lol_alloc
运行)
尺寸测试
wasm-pack build --release example && wc -c example/pkg/lol_alloc_example_bg.wasm
变更日志
0.4.1:
- 避免在发布构建中的free_list_allocator中的断言消息。这应该会提高代码大小和性能,尤其是在编译器未完全内联分配的大小或对齐方式时。
0.4.0:
- 使仅适用于wasm的实现在其他目标上不可用,而不是在运行时失败。
- 提示 docs.rs 仅构建wasm。
0.3.0:
- 添加
AssumeSingleThreaded
。 - 删除
FreeListAllocator
和LeakingAllocator
的不健壮Sync
实现:使用AssumeSingleThreaded
和其不安全的AssumeSingleThreaded::new
函数代替:这将在不安全函数后面将所有已知的安全问题放在这个库中。 - 从
LockedAllocator
中删除默认FreeListAllocator
类型参数。
0.2.0:
- 添加
LockedAllocator
。
依赖项
~150KB