17个版本

0.13.2 2024年5月22日
0.13.1 2023年10月16日
0.13.0 2023年5月12日
0.12.0 2022年4月11日
0.5.0 2017年12月21日

#338#attributes

Download history 1182/week @ 2024-05-03 776/week @ 2024-05-10 1551/week @ 2024-05-17 1405/week @ 2024-05-24 1089/week @ 2024-05-31 870/week @ 2024-06-07 855/week @ 2024-06-14 831/week @ 2024-06-21 721/week @ 2024-06-28 1095/week @ 2024-07-05 976/week @ 2024-07-12 1157/week @ 2024-07-19 2029/week @ 2024-07-26 1303/week @ 2024-08-02 1383/week @ 2024-08-09 566/week @ 2024-08-16

5,534 每月下载量
2 个crate中使用 (通过 soa_derive)

MIT/Apache

120KB
2K SLoC

Rust自动生成数组结构的工具

Test Crates.io

此crate提供了一个自定义 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 trait。您可以使用 <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,
}

如果您想向特定生成的结构体添加属性(例如在 CheeseVec 上添加 #[cfg_attr(test, derive(PartialEq))]),可以在结构体声明中添加属性 #[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;
}

嵌套数组结构

为了在另一个 SoA 结构体内部嵌套数组结构体,可以使用 #[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/

依赖项

~260–710KB
~17K SLoC