#byte-array #contiguous #compact #graph #performance #graph-node

lib-sokoban

Sokoban:紧凑、高效的数据结构,打包在连续的字节数组中

22个版本

0.3.3 2024年8月23日
0.3.2 2024年6月4日
0.3.1 2023年2月14日
0.2.6 2023年1月25日
0.1.13 2022年8月21日

#139数据结构

Download history 221/week @ 2024-05-03 439/week @ 2024-05-10 484/week @ 2024-05-17 574/week @ 2024-05-24 931/week @ 2024-05-31 716/week @ 2024-06-07 717/week @ 2024-06-14 860/week @ 2024-06-21 910/week @ 2024-06-28 857/week @ 2024-07-05 602/week @ 2024-07-12 385/week @ 2024-07-19 501/week @ 2024-07-26 702/week @ 2024-08-02 713/week @ 2024-08-09 776/week @ 2024-08-16

2,753 每月下载量
用于 10 crate(5个直接使用)

MIT/Apache

145KB
3.5K SLoC

Sokoban

连续字节数组中的紧凑、高效数据结构。

基准测试

根据简单的基准测试,Sokoban数据结构的原始性能与Rust标准库相当,但略慢。

test bench_tests::bench_sokoban_avl_tree_insert_1000_u128             ... bench:     134,301 ns/iter (+/- 4,033)
test bench_tests::bench_sokoban_avl_tree_insert_1000_u128_stack       ... bench:     134,135 ns/iter (+/- 3,620)
test bench_tests::bench_sokoban_avl_tree_insert_20000_u128            ... bench:   2,744,853 ns/iter (+/- 158,364)
test bench_tests::bench_sokoban_avl_tree_remove_u128                  ... bench:     355,992 ns/iter (+/- 22,770)
test bench_tests::bench_sokoban_critbit_insert_1000_u128              ... bench:      90,306 ns/iter (+/- 590)
test bench_tests::bench_sokoban_critbit_insert_1000_u128_stack        ... bench:      76,819 ns/iter (+/- 661)
test bench_tests::bench_sokoban_critbit_insert_20000_u128             ... bench:   2,839,050 ns/iter (+/- 207,241)
test bench_tests::bench_sokoban_critbit_remove_1000_u128              ... bench:      97,366 ns/iter (+/- 6,124)
test bench_tests::bench_sokoban_hash_map_insert_1000_u128             ... bench:      46,828 ns/iter (+/- 1,928)
test bench_tests::bench_sokoban_hash_map_insert_1000_u128_stack       ... bench:      46,686 ns/iter (+/- 1,691)
test bench_tests::bench_sokoban_hash_map_insert_20000_u128            ... bench:   1,492,742 ns/iter (+/- 43,362)
test bench_tests::bench_sokoban_hash_map_remove_1000_u128             ... bench:      59,896 ns/iter (+/- 1,782)
test bench_tests::bench_sokoban_red_black_tree_insert_1000_u128       ... bench:      69,574 ns/iter (+/- 8,581)
test bench_tests::bench_sokoban_red_black_tree_insert_1000_u128_stack ... bench:      66,057 ns/iter (+/- 8,853)
test bench_tests::bench_sokoban_red_black_tree_insert_20000_u128      ... bench:   1,905,406 ns/iter (+/- 25,546)
test bench_tests::bench_sokoban_red_black_tree_remove_1000_u128       ... bench:     128,889 ns/iter (+/- 13,508)
test bench_tests::bench_std_btree_map_insert_1000_u128                ... bench:      51,353 ns/iter (+/- 10,240)
test bench_tests::bench_std_btree_map_insert_20000_u128               ... bench:   1,535,224 ns/iter (+/- 21,645)
test bench_tests::bench_std_btree_map_remove_1000_u128                ... bench:     131,879 ns/iter (+/- 19,325)
test bench_tests::bench_std_hash_map_insert_1000_u128                 ... bench:      38,775 ns/iter (+/- 237)
test bench_tests::bench_std_hash_map_insert_20000_u128                ... bench:     797,904 ns/iter (+/- 10,719)
test bench_tests::bench_std_hash_map_remove_1000_u128                 ... bench:      57,452 ns/iter (+/- 364)

为什么是紧凑数据结构?

对于大多数应用来说,没有必要超出Rust标准库来寻找数据结构。然而,当应用程序具有有限的或昂贵的内存,并且由于性能瓶颈而受到影响时,程序员通常会需要设计定制的解决方案来处理这些约束。这类约束在高频交易、嵌入式系统和区块链开发中相当常见。

这就是Sokoban的用武之地:一个专门设计用来简化此类问题的数据结构库。

泛型节点分配器

几乎所有数据结构都可以用某种类型的节点和边连接的图来表示。`node-allocator`模块实现了一个用于连续缓冲区的原始节点分配数据结构。缓冲区中的每个条目必须包含相同的基本类型的对象。每个条目还将有一个固定数量的寄存器,这些寄存器包含有关当前节点的元数据。这些寄存器通常被解释为图边。

#[repr(C)]
#[derive(Copy, Clone)]
pub struct NodeAllocator<
    T: Default + Copy + Clone + Pod + Zeroable,
    const MAX_SIZE: usize,
    const NUM_REGISTERS: usize,
> {
    /// Size of the allocator
    pub size: u64,
    /// Furthest index of the allocator
    bump_index: u32,
    /// Buffer index of the first element in the free list
    free_list_head: u32,
    pub nodes: [Node<NUM_REGISTERS, T>; MAX_SIZE],
}

#[repr(C)]
#[derive(Copy, Clone)]
pub struct Node<T: Copy + Clone + Pod + Zeroable + Default, const NUM_REGISTERS: usize> {
    /// Arbitrary registers (generally used for pointers)
    /// Note: Register 0 is ALWAYS used for the free list
    registers: [u32; NUM_REGISTERS],
    value: T,
}

模板化的`NodeAllocator`对象是一个灵活的原始数据结构,用于实现更复杂类型。以下是使用`NodeAllocator`实现双向链表的示例:

// Register aliases
pub const PREV: u32 = 0;
pub const NEXT: u32 = 1;

#[derive(Copy, Clone)]
pub struct DLL<T: Default + Copy + Clone + Pod + Zeroable, const MAX_SIZE: usize> {
    pub head: u32,
    pub tail: u32,
    allocator: NodeAllocator<T, MAX_SIZE, 2>,
}

DLL实际上只是一个具有每个节点2个寄存器的节点分配器。这些寄存器代表DLL节点的`prev`和`next`指针。创建和删除边的逻辑是特定于类型的,但分配器结构提供了一种实现具有此属性(树和图)的任意类型的接口。

依赖项

~0.5–1MB
~21K SLoC