| Crates.io | soapy-shared |
| lib.rs | soapy-shared |
| version | 0.2.0 |
| created_at | 2023-12-29 23:21:42.457828+00 |
| updated_at | 2024-01-05 23:00:48.48307+00 |
| description | Custom types used by soapy macros |
| homepage | https://github.com/tim-harding/soapy |
| repository | https://github.com/tim-harding/soapy |
| max_upload_size | |
| id | 1083754 |
| size | 12,935 |
Soapy makes it simple to work with structure-of-arrays memory layout. What Vec<T>
is to array-of-structures (AoS), Soa<T> is to structure-of-arrays (SoA).
use soapy::{Soa, Soapy};
[derive(Soapy, Debug, Clone, Copy, PartialEq)]
struct Example {
foo: u8,
bar: u16,
}
let elements = [Example { foo: 1, bar: 2 }, Example { foo: 3, bar: 4 }];
let mut soa: Soa<_> = elements.into_iter().collect();
// The index operator is not possible, but we can use nth:
*soa.nth_mut(0).foo += 10;
// We can get the fields as slices as well:
let slices = soa.slices();
assert_eq!(slices.foo, &[11, 3][..]);
assert_eq!(slices.bar, &[2, 4][..]);
for (actual, expected) in soa.iter().zip(elements.iter()) {
assert_eq!(&expected.bar, actual.bar);
}
The following types illustrate the difference between AoS and Soa:
[(u8, u64)] // AoS
([u8], [u64]) // Soa
Whereas AoS stores all the fields of a type in each element of the array, SoA splits each field into its own array. This has several benefits:
SoA is a popular technique in data-oriented design. Andrew Kelley gives a wonderful talk describing how SoA and other data-oriented design patterns earned him a 39% reduction in wall clock time in the Zig compiler.
Note that SoA does not offer performance wins in all cases. SoA is most appropriate when either
As always, it is best to profile both for your use case.
Soapy provides the [Soapy] derive macro to generate SoA compatibility for
structs automatically. When deriving Soapy, several new structs are
created. Because of the way SoA data is stored, iterators and getters often
yield these types instead of the original struct. If each field of some
struct Example has type F, our new structs have the same fields but
different types:
| Struct | Field type | Use |
|---|---|---|
ExampleRawSoa |
*mut F |
Low-level, unsafe interface for Soa |
ExampleRef |
&F |
.iter(), nth(), .get() |
ExampleRefMut |
&mut F |
.iter_mut(), nth_mut, get_mut() |
ExampleSlices |
&[F] |
.slices(), .get() |
ExampleSlicesMut |
&mut [F] |
.slices_mut(), .get_mut() |
These types are included as associated types on the [Soapy] trait as well.
Generally, you won't need to think about these them as [Soa] picks them up
automatically. However, since they inherit the visibility of the derived
struct, you should consider whether to include them in the pub items of
your module.
soa_derivesoa_derive makes each field its own Vec. Because of this, each field's
length, capacity, and allocation are managed separately. In contrast, Soapy
manages a single allocation for each Soa. This uses less space and allows
the collection to grow and shrink more efficiently. soa_derive also
generates a new collection type for every struct, whereas Soapy generates a
minimal, low-level interface and uses the generic Soa type for the
majority of the implementation. This provides more type system flexibility,
less code generation, and more accessible documentation.
soa-vecWhereas soa-vec only compiles on nightly, Soapy also compiles on stable.
Rather than using derive macros, soa-vec instead uses macros to generate
eight static copies of their SoA type with fixed tuple sizes.
depup / dedup_by / dedup_by_keydrainextend_from_slice / extend_from_withinextract_ifleakretaintry_reserve / try_reserve_exactdedup_by / dedup_by_keyresize / resize_withsplicesplit_offselect_nth_unstable / select_nth_unstable_by / select_nth_unstable_by_keysort / sort_by / sort_by_key / sort_by_cached_keysort_unstable / sort_unstable_by / sort_unstable_by_key / sort_unstable_by_cached_keybinary_search / binary_search_by / binary_search_by_keyis_sorted / is_sorted_by / is_sorted_by_keychunks / rchunkschunks_exact / rchunks_exactfirst / lastrotate_left / rotate_rightsplit / rsplit / splitnsplit_at / split_first / split_lastswapswap_with_slicegroup_bycontainscopy_withinfill / fill_withrepeatreverse