2 个不稳定版本

0.2.0 2024年1月5日
0.1.0 2023年12月29日

#2131 in Rust 模式

MIT 许可证

13KB

docs.rs Crates.io Version GitHub License

Soapy

Soapy 使得操作结构化数组内存布局变得简单。Vec 对于数组化结构 (AoS),Soa 对于结构化数组 (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);
}

什么是 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_mutget_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
  • 重复
  • 反转

无运行时依赖