Automatic Struct of Array generation for Rust
This crate provides a custom derive (#[derive(StructOfArray)]) to
automatically generate code from a given struct T that allow to replace
Vec<T> with a struct of arrays. For example, the following code
will generate a ParticleVec struct that looks like this:
It will also generate the same functions that a Vec<Particle> would have,
and a few helper structs: ParticleSlice, ParticleSliceMut,
ParticleRef and ParticleRefMut.
How to use it
Add #[derive(StructOfArray)] to each struct you want to derive a struct
of array version. If you need the helper structs to derive additional
traits (such as Debug or PartialEq), you can add an attribute
#[soa_derive = "Debug, PartialEq"] to the struct declaration.
zip functions
You can activate generation of two additional functions zip and
zip_mut by decorating some fields of the struct with
#[soa_derive(zip)]. These functions allow to iterate over multiple
fields of the derivated Vec.
This generate a zip_particle module containing marker types used to
request some specific data, like this:
use ;
let mut vector = new;
for x in vector.zip
let mut total = 0.0;
for in vector.zip
// Mutable iteration is also available with zip_mut
for y in vector.zip_mut
for in vector.zip_mut
Caveats and limitations
Vec<T> functionalities rely a lot on references and automatic deref
feature, for getting function from [T] and indexing. But the SoA vector
(let's call it CheeseVec, generated from the Cheese struct) generated
by this crate can not implement Deref<Target=CheeseSlice>, because
Deref is required to return a reference, and CheeseSlice is not a
reference. The same applies to Index and IndexMut trait, that can not
return CheeseRef/CheeseRefMut.
This means that the we can not index into a CheeseVec, and that a few
functions are duplicated, or require a call to as_ref()/as_mut() to
change the type used.
Because of the way the code is implemented, the zip functions can not be
derived on more than 4 fields, as the compilation time grows exponentialy.
Deriving it for 5 fields makes the compilation time goes from 6s to more
than 2 minutes. These functions will only be derived if the struct is
declared as public.
Documentation
Please see http://lumol.org/soa-derive/soa_derive_example/ for a small example and the documentation of all the generated code.
Benchmarks
Here are a few simple benchmarks results, on my machine:
running 10 tests
test aos_big_do_work_1000 ... bench: 997 ns/iter (+/- 192)
test aos_big_do_work_10000 ... bench: 21,324 ns/iter (+/- 3,282)
test aos_big_push ... bench: 93 ns/iter (+/- 17)
test aos_small_do_work_10000 ... bench: 8,822 ns/iter (+/- 1,459)
test aos_small_push ... bench: 10 ns/iter (+/- 4)
test soa_big_do_work_1000 ... bench: 890 ns/iter (+/- 142)
test soa_big_do_work_10000 ... bench: 10,538 ns/iter (+/- 1,621)
test soa_big_push ... bench: 171 ns/iter (+/- 44)
test soa_small_do_work_10000 ... bench: 8,978 ns/iter (+/- 1,538)
test soa_small_push ... bench: 24 ns/iter (+/- 6)
Benchmarks tests exist for soa (struct of array) and aos (array of struct) versions of the same code, using a samll and a big struct.
You can run the same benchmarks on your own system by cloning this repository
and running cargo bench.
Licensing and contributions
This crate distributed under either the MIT or the Apache license, at your choice. Contributions are welcome, please open an issue before to discuss your changes !
The code is based on an initial idea by @maikklein: https://maikklein.github.io/post/soa-rust/