2 个不稳定版本
| 0.2.0 | 2024年1月5日 |
|---|---|
| 0.1.0 | 2023年12月29日 |
#2131 in Rust 模式
13KB
Soapy
Soapy 使得操作结构化数组内存布局变得简单。Vec
示例
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);
}
什么是 SoA?
以下类型说明了 AoS 和 SoA 之间的区别
[(u8, u64)] // AoS
([u8], [u64]) // Soa
与 AoS 相比,SoA 将每个字段分割成自己的数组。这有几个优点
- 不需要在相同类型实例之间填充。在上面的示例中,每个 AoS 元素需要 128 位来满足内存对齐要求,而每个 SoA 元素只需 72 位。这意味着更好的缓存局部性和更低的内存使用。
- SoA 更适合向量化。在 SoA 中,可以直接将多个值批量加载到 SIMD 寄存器中,而不是在不同的 SIMD 寄存器之间移动结构字段。
SoA 是面向数据设计中的一种流行技术。Andrew Kelley 提供了一次精彩的 演讲,描述了 SoA 和其他面向数据设计模式如何帮助他在 Zig 编译器中减少了 39% 的wall clock time。
请注意,SoA 并不是在所有情况下都能带来性能提升。SoA 最适合以下情况
- 顺序访问是常见的访问模式
- 您经常只访问或修改字段的一个子集
始终最好为您的用例进行性能分析。
派生
Soapy 提供了 Soapy 派生宏来自动生成结构体的 SoA 兼容性。当派生 Soapy 时,会创建几个新的结构体。由于 SoA 数据的存储方式,迭代器和获取器通常会返回这些类型而不是原始结构体。如果某个结构体 Example 的每个字段类型为 F,我们的新结构体具有相同的字段但不同的类型
| 结构体 | 字段类型 | 用途 |
|---|---|---|
ExampleRawSoa |
*mutF |
Soa 的低级、不安全接口 |
ExampleRef |
&F |
.iter()、nth()、.get() |
示例RefMut |
&可变F |
.iter_mut()、nth_mut、get_mut() |
示例Slices |
&[F] |
.slices()、.get() |
示例SlicesMut |
&可变 [F] |
.slices_mut()、.get_mut() |
这些类型也被包含在Soapy特质的关联类型中。通常,你不需要考虑这些,因为[Soa]会自动获取它们。然而,由于它们继承了派生结构的可见性,你应该考虑是否将它们包含在你的模块的pub项中。
比较
soa_derive
soa_derive将每个字段都当作自己的Vec。因此,每个字段的长度、容量和分配是分开管理的。相比之下,Soapy为每个Soa管理单个分配。这使用更少的空间,并允许集合更有效地增长和收缩。soa_derive还为每个结构生成一个新的集合类型,而Soapy生成最小的、底层的接口,并使用通用的Soa类型作为实现的大部分。这提供了更多类型系统灵活性、更少的代码生成和更易于访问的文档。
soa-vec
而soa-vec只能在nightly编译,Soapy也可以在稳定版编译。而不是使用derive宏,soa-vec使用宏生成八个具有固定元组大小的SoA类型的静态副本。
进展
Soa
-
depup/dedup_by/dedup_by_key -
drain -
extend_from_slice/extend_from_within -
extract_if -
leak -
retain -
try_reserve/try_reserve_exact -
dedup_by/dedup_by_key -
resize/resize_with -
splice -
split_off
SoaSlice
-
select_nth_unstable/select_nth_unstable_by/select_nth_unstable_by_key -
sort/sort_by/sort_by_key/sort_by_cached_key -
sort_unstable/sort_unstable_by/sort_unstable_by_key/sort_unstable_by_cached_key -
binary_search/binary_search_by/binary_search_by_key -
is_sorted/is_sorted_by/is_sorted_by_key -
chunks/rchunks -
chunks_exact/rchunks_exact -
first/last -
rotate_left/rotate_right -
split/rsplit/splitn -
split_at/split_first/split_last -
交换 -
与切片交换 -
按组分类 -
包含 -
在内部复制 -
fill/fill_with -
重复 -
反转