15 个稳定版本
2.3.0 | 2022年2月10日 |
---|---|
2.1.0 | 2021年12月28日 |
1.7.0 | 2021年11月13日 |
1.2.0 | 2021年7月16日 |
0.1.0 | 2020年9月4日 |
#475 在 数据结构 中
每月158 次下载
在 miratope 中使用
250KB
4K SLoC
scapegoat
通过基于场的scapegoat树实现的有序集合和映射数据结构(内存高效、自我平衡的二叉搜索树)。
- 嵌入式友好:默认情况下为
#![no_std]
。 - 安全:包含所有依赖项的
#![forbid(unsafe_code)]
。 - 通过差异模糊测试进行验证,与标准库的
BTreeSet
和BTreeMap
进行比较。
关于
两个API
追求三个特性
-
最大安全性:强大的内存安全保证,因此
#![forbid(unsafe_code)]
。- 编译时安全性:没有
unsafe
(没有原始指针解引用等)。 - 调试时安全性:在测试中执行的逻辑不变量使用
debug_assert!
。 - 运行时安全性:无内部可变性(例如,无需
Rc<RefCell<T>>的运行时检查)。
- 编译时安全性:没有
-
最小占用空间:资源使用量低,因此使用
#![no_std]
。- 内存高效:节点只有子索引元数据,节点内存被重用。
- 无递归:所有操作都是迭代的,因此栈使用量固定,运行时最小化。
- 零复制:重建/移除就地重定向,节点永远不会被复制或克隆。
-
可靠性:对于优先考虑鲁棒性的嵌入式用例(或内核空间代码)。
- 每个易出错误的API(例如,
insert
、append
、extend
等)都提供了一种try_*
变体。 - 内存不足(OOM)
panic!
变得可避免:try_*
变体返回Result<_, SgError>
。 - 堆碎片化不影响最坏情况执行时间(WCET),这个库不使用堆。
- 每个易出错误的API(例如,
其他功能
使用方法
SgMap
非穷举,#![no_std]
API 示例(对于std::collections::BTreeMap
几乎相同)
use scapegoat::SgMap;
use tinyvec::{array_vec, ArrayVec};
// This const is an argument to each generic constructor below.
// So we'll use *only the bare minimum* memory for 5 elements.
// - Stack RAM usage can be precisely controlled: per map instance (constructor call-site).
// - To save executable RAM/ROM (monomorphization!), stick to a global capacity like this.
const CAPACITY: usize = 5;
let mut example = SgMap::<_, _, CAPACITY>::new(); // BTreeMap::new()
let mut static_str = "your friend the";
// Insert "dynamically" (as if heap)
example.insert(3, "the");
example.insert(2, "don't blame");
example.insert(1, "Please");
// Fallible insert variant
assert!(example.try_insert(4, "borrow checker").is_ok());
// Ordered reference iterator
assert!(example
.iter()
.map(|(_, v)| *v)
.collect::<ArrayVec<[&str; CAPACITY]>>()
.iter()
.eq(["Please","don't blame","the","borrow checker"].iter()));
// Container indexing
assert_eq!(example[&3], "the");
// Head removal
let please_tuple = example.pop_first().unwrap();
assert_eq!(please_tuple, (1, "Please"));
// By-predicate removal
example.retain(|_, v| !v.contains("a"));
// Extension
let iterable = array_vec![
[(isize, &str); CAPACITY] =>
(1337, "safety!"), (0, "Leverage"), (100, "for")
];
example.extend(iterable.into_iter());
// Value mutation
if let Some(three_val) = example.get_mut(&3) {
*three_val = &mut static_str;
}
// New message :)
assert!(example
.into_values()
.collect::<ArrayVec<[&str; CAPACITY]>>()
.iter()
.eq(["Leverage","your friend the","borrow checker","for","safety!"].iter()));
更多示例在这里。
堆栈容量:重要上下文
根据上述内容,const泛型类型参数决定了集合容量。因此,也决定了堆栈使用。这种使用是固定的
use core::mem::size_of_val;
use scapegoat::SgMap;
let small_map: SgMap<u64, u64, 100> = SgMap::new(); // 100 item capacity
let big_map: SgMap<u64, u64, 2_048> = SgMap::new(); // 2,048 item capacity
#[cfg(target_pointer_width = "64")]
#[cfg(not(feature = "low_mem_insert"))]
#[cfg(not(feature = "fast_rebalance"))]
{
assert_eq!(size_of_val(&small_map), 2_680); // 2.7 KB
assert_eq!(size_of_val(&big_map), 53_328); // 53.3 KB
}
支持的最大容量是65_535
(例如,0xffff
或u16::MAX
)项目。请注意
- 对于嵌入式平台,堆栈大小限制(受可用RAM限制)在制造商的数据表中指示。
- 在Linux上,主线程的默认堆栈限制为8MB,新生的线程为2MB(除非重写)。
- 在任意操作系统上运行
cargo test
,除非设置环境变量RUST_MIN_STACK
,否则限制为2MB。
警告:尽管栈使用量是恒定的(无递归),但如果
N
(常量泛型容量)和/或存储的项目类型(泛型)过大,则在运行时可能会发生栈溢出。注意栈溢出与缓冲区溢出(Rust可以防止)是不同的。无论如何,您必须测试以确保您不会超过目标平台的栈大小限制。Rust只支持x86/x64上的栈探测,尽管已经提出了针对其他架构的创意链接解决方案。
有关高级配置选项,请参阅此处文档。
可信依赖项
此库有三个依赖项,每个依赖项都没有自己的依赖项(例如,总共有三个依赖项)。
tinyvec
-#![no_std]
,#![forbid(unsafe_code)]
是Vec
的替代方案。micromath
-#![no_std]
,#![forbid(unsafe_code)]
浮点近似。smallnum
-#![no_std]
,#![forbid(unsafe_code)]
整数抽象。
由于此库及其所有依赖项都#![forbid(unsafe_code)]
,因此不会将第三方unsafe
代码引入到您的项目中。这最大限度地提高了内存安全的静态保证(通过Rust的类型系统强制执行)。除内存安全之外的鲁棒性和正确性属性通过差异模糊测试动态验证。
其他注意事项
总体目标
此项目是安全、便携式数据结构设计的一个练习。目标是向嵌入式开发者提供熟悉、易于使用的API,这些系统在其他情况下没有动态集合的奢侈。不会牺牲安全性。
scapegoat
的速度或成熟度不如标准库的BTreeMap
/BTreeSet
(通过cargo bench
进行基准测试)。标准库已经针对缓存性能进行了大量优化。此库针对低、仅栈的内存占用进行了优化。它提供
-
最佳兼容性:API 大部分是
BTreeMap
和BTreeSet
的子集,因此它基本上可以作为#![no_std]
系统的“即插即用”替代品。如果您需要的 API 还未得到支持,请 提交一个问题。 -
动态验证:使用 基于覆盖率、结构感知、差异模糊测试 来证明此实现在逻辑上是等价的,并且具有相同的可靠性。
-
可调性能:一个 单个浮点数值 优化了
insert
、get
和remove
操作类的相对性能。并且可以在运行时更改。
算法复杂度
空间复杂度始终为 O(n)
。
操作 | 平均情况 | 最坏情况 |
---|---|---|
get |
O(log n) |
O(log n) |
insert |
O(log n) |
平均 O(log n) |
remove |
O(log n) |
平均 O(log n) |
第一个 |
O(1) |
O(1) |
最后一个 |
O(1) |
O(1) |
内存占用演示
-
代码大小演示 - 调用
SgMap<usize, usize, 1024>
以及insert
、get
和remove
:x86-64 二进制文件的 14.2KB。注意:您可能需要使用超过 3 个函数,这将导致更多可执行代码被包含在内。 -
堆栈空间演示 -
SgMap<u8, u8, 128>
:存储成本为 1.3KB。注意:运行时需要更多的堆栈空间进行维护(例如,重新平衡)。
许可和贡献
依赖项
约 510KB
约 16K SLoC