18 个版本 (11 个重大更新)

0.13.0 2023年5月12日
0.12.0 2022年4月11日
0.11.0 2021年11月27日
0.10.0 2021年1月7日
0.1.0 2017年7月11日

#512Rust 模式

Download history 718/week @ 2024-04-20 819/week @ 2024-04-27 1180/week @ 2024-05-04 763/week @ 2024-05-11 1574/week @ 2024-05-18 1313/week @ 2024-05-25 1072/week @ 2024-06-01 820/week @ 2024-06-08 791/week @ 2024-06-15 801/week @ 2024-06-22 689/week @ 2024-06-29 1203/week @ 2024-07-06 1090/week @ 2024-07-13 1174/week @ 2024-07-20 2032/week @ 2024-07-27 1382/week @ 2024-08-03

5,801 每月下载量

MIT/Apache

36KB
294

Rust 自动生成数组结构体

Test Crates.io

此包提供自定义 derive (#[derive(StructOfArray)]) 以从给定的结构体 T 自动生成代码,允许用数组结构体替换 Vec<T>。例如,以下代码

#[derive(StructOfArray)]
pub struct Cheese {
    pub smell: f64,
    pub color: (f64, f64, f64),
    pub with_mushrooms: bool,
    pub name: String,
}

将生成一个类似下面的 CheeseVec 结构体

pub struct CheeseVec {
    pub smell: Vec<f64>,
    pub color: Vec<(f64, f64, f64)>,
    pub with_mushrooms: Vec<bool>,
    pub name: Vec<String>,
}

它还将生成与 Vec<Cheese> 相同的函数,以及一些辅助结构体: CheeseSliceCheeseSliceMutCheeseRefCheeseRefMut,分别对应于 &[Cheese]&mut [Cheese]&Cheese&mut Cheese

任何通过 StructOfArray 衍生的结构体都将自动实现 StructOfArray 特性。您可以使用 <Cheese as StructOfArray>::Type 代替显式命名的类型 CheeseVec

如何使用它

#[derive(StructOfArray)] 添加到您想要生成数组结构版本的每个结构中。如果您需要辅助结构来派生额外的特质(如 DebugPartialEq),可以在结构声明中添加一个属性 #[soa_derive(Debug, PartialEq)]

#[derive(Debug, PartialEq, StructOfArray)]
#[soa_derive(Debug, PartialEq)]
pub struct Cheese {
    pub smell: f64,
    pub color: (f64, f64, f64),
    pub with_mushrooms: bool,
    pub name: String,
}

如果您想向特定的生成结构添加属性(如 #[cfg_attr(test, derive(PartialEq))]CheeseVec 上),可以在结构声明中添加一个属性 #[soa_attr(Vec, cfg_attr(test, derive(PartialEq)))]

#[derive(Debug, PartialEq, StructOfArray)]
#[soa_attr(Vec, cfg_attr(test, derive(PartialEq)))]
pub struct Cheese {
    pub smell: f64,
    pub color: (f64, f64, f64),
    pub with_mushrooms: bool,
    pub name: String,
}

soa_attr 的第一个参数到为 Cheese 生成的结构的映射

  • Vec => CheeseVec
  • Slice => CheeseSlice
  • SliceMut => CheeseSliceMut
  • Ref => CheeseRef
  • RefMut => CheeseRefMut
  • Ptr => CheesePtr
  • PtrMut => CheesePtrMut

使用方法和API

所有生成的代码都有一些生成文档,所以您应该能够在您的crate上使用 cargo doc 来查看所有生成结构和函数的文档。大多数情况下,您可以将 Vec<Cheese> 替换为 CheeseVec,除非代码直接使用向量的索引以及以下列出的其他注意事项。

注意事项和限制

Vec<T> 的功能在很大程度上依赖于引用和自动 解引用 特性,用于从 [T] 和索引中获取函数。但由该crate生成的SoA向量(我们称之为 CheeseVec,由 Cheese 结构体生成)无法实现 Deref<Target=CheeseSlice>,因为 Deref 需要返回一个引用,而 CheeseSlice 不是一个引用。同样的情况也适用于 IndexIndexMut 特性,它们不能返回 CheeseRef/CheeseRefMut。这意味着我们无法索引到 CheeseVec,并且一些函数是重复的,或者需要调用 as_ref()/as_mut() 来更改使用的类型。

迭代

可以遍历 CheeseVec 中的值

let mut vec = CheeseVec::new();
vec.push(Cheese::new("stilton"));
vec.push(Cheese::new("brie"));

for cheese in vec.iter() {
    // when iterating over a CheeseVec, we load all members from memory
    // in a CheeseRef
    let typeof_cheese: CheeseRef = cheese;
    println!("this is {}, with a smell power of {}", cheese.name, cheese.smell);
}

SoA布局的主要优势之一是在遍历向量时只从内存中加载一些字段。为了做到这一点,可以手动选择所需的字段

for name in &vec.name {
    // We get referenes to the names
    let typeof_name: &String = name;
    println!("got cheese {}", name);
}

为了同时遍历多个字段,可以使用 soa_zip! 宏。

for (name, smell, color) in soa_zip!(vec, [name, mut smell, color]) {
    println!("this is {}, with color {:#?}", name, color);
    // smell is a mutable reference
    *smell += 1.0;
}

嵌套的数组结构

为了在另一个数组结构体内部嵌套数组结构体,可以使用 #[nested_soa] 属性。

例如,以下代码

#[derive(StructOfArray)]
pub struct Point {
    x: f32,
    y: f32,
}
#[derive(StructOfArray)]
pub struct Particle {
    #[nested_soa]
    point: Point,
    mass: f32,
}

将生成如下结构的结构体

pub struct PointVec {
    x: Vec<f32>,
    y: Vec<f32>,
}
pub struct ParticleVec {
    point: PointVec, // rather than Vec<Point>
    mass: Vec<f32>
}

所有辅助结构体也将被嵌套,例如 PointSlice 将嵌套在 ParticleSlice 中。

文档

请参阅 http://lumol.org/soa-derive/soa_derive_example/ 了解一个小示例和所有生成的代码的文档。

基准测试

以下是一些在我的机器上进行的简单基准测试结果

running 10 tests
test aos_big_do_work_100k   ... bench:     415,315 ns/iter (+/- 72,861)
test aos_big_do_work_10k    ... bench:      10,087 ns/iter (+/- 219)
test aos_big_push           ... bench:          50 ns/iter (+/- 10)
test aos_small_do_work_100k ... bench:      93,377 ns/iter (+/- 1,106)
test aos_small_push         ... bench:           3 ns/iter (+/- 1)
test soa_big_do_work_100k   ... bench:      93,719 ns/iter (+/- 2,793)
test soa_big_do_work_10k    ... bench:       9,253 ns/iter (+/- 103)
test soa_big_push           ... bench:          39 ns/iter (+/- 13)
test soa_small_do_work_100k ... bench:      93,301 ns/iter (+/- 1,765)
test soa_small_push         ... bench:           4 ns/iter (+/- 1)

基准测试测试了soa(结构体数组)和aos(结构体数组)版本的相同代码,使用了一个小(24字节)和一个大(240字节)的结构体。

您可以通过克隆此存储库并运行 cargo bench 在您的系统上运行相同的基准测试。

许可和贡献

此crate根据您选择,在MIT或Apache许可下分发。欢迎贡献,请在提出更改前先打开一个问题!

感谢 @maikklein 提出最初的想法: https://maikklein.github.io/soa-rust/

依赖项

~310–760KB
~18K SLoC