7个不稳定版本 (3个破坏性更改)
0.4.1 | 2021年12月11日 |
---|---|
0.4.0 | 2021年12月2日 |
0.3.0 | 2021年11月28日 |
0.2.2 | 2021年11月26日 |
0.1.0 | 2021年6月6日 |
#362 in 嵌入式开发
每月下载量 209
在 2 个工具中使用 (通过 scapegoat)
40KB
517 行
smallnum
数值原语的编译时大小优化。宏返回能够容纳静态边界的最小数值类型。对于无符号整数,宏输入是最大值。对于有符号整数,宏输入可以是最大值或最小值。
- 可以在不产生运行时成本的情况下节省内存。
- 嵌入式友好:
!#[no_std]
。 - 安全:
#![forbid(unsafe_code)]
。
这是为什么?
-
节省内存:帮助编译器进行内存布局优化(即"结构打包")。
- 下面是零成本的示例。
-
提高易用性:创建抽象底层整数类型的API。
- 例如,方法参数/返回值使用
usize
,但内部存储为u16
。向上转换是免费的。向下转换会检查精度损失。
- 例如,方法参数/返回值使用
不使用 #[repr(packed)]
已经可以节省内存吗?
不安全。这种区别虽然微妙但很重要。
-
#[repr(packed)]
会移除结构体字段之间的所有填充。这可能会对未对齐访问造成性能损失,在最坏的情况下,还会引发未定义的行为。通常情况下,我们都希望避免这种行为。 -
smallnum
在不移除填充的同时,有助于打包并保持目标的本征对齐。实际上,它可以提高 [数据缓存] 性能,同时保证完全安全。
为了进行极端的尺寸优化,你可以将 smallnum
与 #[repr(packed)]
示例:集合索引
当集合的大小在编译时已知时,可以对该索引变量进行尺寸优化。
- 目标: 集合/容器索引运算符的值
- 输出:
x * 1
其中x< size_of<usize>()
use smallnum::{small_unsigned, SmallUnsigned};
use core::mem::size_of_val;
const MAX_SIZE: usize = 500;
let mut my_array: [u8; MAX_SIZE] = [0x00; MAX_SIZE];
let idx: usize = 5;
let small_idx: small_unsigned!(MAX_SIZE) = 5;
// Equivalent values
my_array[idx] = 0xff;
assert_eq!(my_array[idx], my_array[small_idx.usize()]);
// Memory savings (6 bytes on a 64-bit system)
#[cfg(target_pointer_width = "64")]
assert_eq!(size_of_val(&idx) - size_of_val(&small_idx), 6);
请注意,在作用域中拥有特质 SmallUnsigned
允许调用 small_idx.usize()
。此函数返回一个 usize
,方便索引,无论宏选择了哪种类型(如上述示例中的 u16
),因此相较于64位宿主的 u64
,可以节省6个字节。
示例:树节点元数据
当树的最大容量在编译时已知时,每个节点中存储的元数据可以进行尺寸优化。
- 目标: 内部元数据
- 输出:
x * n
其中x<= size_of<usize>()
n==node_cnt
use smallnum::small_unsigned;
use core::mem::size_of;
const MAX_CAPACITY: usize = 50_000;
// Regular node in a binary tree
pub struct BinTree<T> {
value: T,
left_child: Option<Box<BinTree<T>>>,
right_child: Option<Box<BinTree<T>>>,
subtree_size: usize,
}
// Node with size-optimized metadata
pub struct SmallBinTree<T> {
value: T,
left_child: Option<Box<SmallBinTree<T>>>,
right_child: Option<Box<SmallBinTree<T>>>,
subtree_size: small_unsigned!(MAX_CAPACITY),
}
// Per-node memory savings (8 bytes on a 64-bit system)
#[cfg(target_pointer_width = "64")]
assert_eq!(size_of::<BinTree<i16>>() - size_of::<SmallBinTree<i16>>(), 8);
示例:基于索引的图
当实现一个最大容量在编译时已知的基于 {索引,区域}-基于图 时,每个结构(边或节点)中存储的索引可以进行尺寸优化。
- 目标: 内部“指针”表示
- 输出:
(x + y) * n
其中x<= size_of<usize>()
y<= size_of<Option<usize>>()
n==edge_cnt
use smallnum::small_unsigned;
use core::mem::size_of;
const MAX_CAPACITY: usize = 50_000;
// Based on "Modeling graphs in Rust using vector indices" by Niko Matsakis (April 2015)
// http://smallcultfollowing.com/babysteps/blog/2015/04/06/modeling-graphs-in-rust-using-vector-indices/
// Unoptimized indexes
pub type NodeIdx = usize;
pub type EdgeIdx = usize;
pub struct EdgeData {
target: NodeIdx,
next_outgoing_edge: Option<EdgeIdx>
}
// Optimized indexes
pub type SmallNodeIdx = small_unsigned!(MAX_CAPACITY);
pub type SmallEdgeIdx = small_unsigned!(MAX_CAPACITY);
pub struct SmallEdgeData {
target: SmallNodeIdx,
next_outgoing_edge: Option<SmallEdgeIdx>
}
// Per-edge memory savings (18 bytes on a 64-bit system)
#[cfg(target_pointer_width = "64")]
assert_eq!(size_of::<EdgeData>() - size_of::<SmallEdgeData>(), 18);
高级示例
请参阅 examples/
目录,使用 cargo run --example <file_name>
运行。
宏 <-> 类型选择集
small_unsigned!
<-> (u8
,u16
,u32
,u64
,u128
)small_signed!
<-> (i8
,i16
,i32
,i64
,i128
)
许可和贡献
在 MIT 许可证 下许可。欢迎贡献力量!