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日 |
#512 在 Rust 模式
5,801 每月下载量
36KB
294 行
Rust 自动生成数组结构体
此包提供自定义 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>
相同的函数,以及一些辅助结构体: CheeseSlice
、CheeseSliceMut
、CheeseRef
和 CheeseRefMut
,分别对应于 &[Cheese]
、&mut [Cheese]
、&Cheese
和 &mut Cheese
。
任何通过 StructOfArray 衍生的结构体都将自动实现 StructOfArray
特性。您可以使用 <Cheese as StructOfArray>::Type
代替显式命名的类型 CheeseVec
。
如何使用它
将 #[derive(StructOfArray)]
添加到您想要生成数组结构版本的每个结构中。如果您需要辅助结构来派生额外的特质(如 Debug
或 PartialEq
),可以在结构声明中添加一个属性 #[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
不是一个引用。同样的情况也适用于 Index
和 IndexMut
特性,它们不能返回 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