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 嵌入式开发

Download history 100/week @ 2024-03-15 29/week @ 2024-03-22 49/week @ 2024-03-29 44/week @ 2024-04-05 95/week @ 2024-04-12 43/week @ 2024-04-19 13/week @ 2024-04-26 30/week @ 2024-05-03 26/week @ 2024-05-10 29/week @ 2024-05-17 83/week @ 2024-05-24 71/week @ 2024-05-31 64/week @ 2024-06-07 72/week @ 2024-06-14 52/week @ 2024-06-21 14/week @ 2024-06-28

每月下载量 209
2 个工具中使用 (通过 scapegoat)

MIT 许可证

40KB
517

smallnum

crates.io docs.rs GitHub Actions License: MIT

数值原语的编译时大小优化。宏返回能够容纳静态边界的最小数值类型。对于无符号整数,宏输入是最大值。对于有符号整数,宏输入可以是最大值或最小值。

  • 可以在不产生运行时成本的情况下节省内存。
  • 嵌入式友好:!#[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> 运行。

宏 <-> 类型选择集

许可和贡献

MIT 许可证 下许可。欢迎贡献力量!

无运行时依赖