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 CheeseVec
struct that looks like this:
It will also generate the same functions that a Vec<Cheese>
would have, and a
few helper structs: CheeseSlice
, CheeseSliceMut
, CheeseRef
and
CheeseRefMut
corresponding respectivly to &[Cheese]
, &mut [Cheese]
,
&Cheese
and &mut Cheese
.
Any struct derived by StructOfArray will auto impl trait StructOfArray
.
You can use <Cheese as StructOfArray>::Type
instead of the explicitly named type CheeseVec
.
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.
If you want to add attribute to a specific generated struct(such as
#[cfg_attr(test, derive(PartialEq))]
on CheeseVec
), you can add an
attribute #[soa_attr(Vec, cfg_attr(test, derive(PartialEq)))]
to the
struct declaration.
Mappings for first argument of soa_attr
to the generated struct for Cheese
:
Vec
=>CheeseVec
Slice
=>CheeseSlice
SliceMut
=>CheeseSliceMut
Ref
=>CheeseRef
RefMut
=>CheeseRefMut
Ptr
=>CheesePtr
PtrMut
=>CheesePtrMut
Usage and API
All the generated code have some generated documentation with it, so you
should be able to use cargo doc
on your crate and see the documentation
for all the generated structs and functions.
Most of the time, you should be able to replace Vec<Cheese>
by
CheeseVec
, with exception of code using direct indexing in the vector and
a few other caveats listed below.
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.
Iteration
It is possible to iterate over the values in a CheeseVec
let mut vec = new;
vec.push;
vec.push;
for cheese in vec.iter
One of the main advantage of the SoA layout is to be able to only load some fields from memory when iterating over the vector. In order to do so, one can manually pick the needed fields:
for name in &vec.name
In order to iterate over multiple fields at the same time, one can use the soa_zip! macro.
for in soa_zip!
Nested Struct of Arrays
In order to nest a struct of arrays inside another struct of arrays, one can use the #[nested_soa]
attribute.
For example, the following code
will generate structs that looks like this:
All helper structs will be also nested, for example PointSlice
will be nested in ParticleSlice
.
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 small 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/