数据在内存中的优化布局是 Rust 的一个关键特性,它允许节省资源而不损失代码的安全性。枚举布局是编译器如何放置枚举的变体(包括结构体变体或原始数据类型),以及任何结构体的字段。在其他语言中,这种优化通常是隐含的,而在 Rust 中,考虑字段的顺序非常重要,以避免内存的浪费。
如果结构体中的字段顺序选择不当,由于数据对齐的特性,结构体的大小会开始“膨胀”。对于具有关联数据的枚举,情况更加复杂——枚举的大小由最大的变体的大小加上分类符的大小决定。忽视这一点会导致内存的过度使用,从而降低处理器缓存的性能。
为了有效地打包结构和枚举,应该首先安排宽度最大的字段,然后是较窄的字段,并考虑编译器可能添加的填充。对于枚举,选择变体的结构,以便它们不追求最大尺寸,除非有充分的理由。
代码示例:
struct BadAlign { a: u8, b: u32, c: u16, } struct GoodAlign { b: u32, c: u16, a: u8, } enum Packet { A(u8), B(u32, [u8; 10]), }
关键特性:
仅通过改变字段的顺序,可以使结构体变小吗?
可以。如果字段按大小递减顺序排列,编译器通常会减少填充的数量,从而减小结构体的整体大小。
println!("{}", std::mem::size_of::<BadAlign>()); // 例如,12 println!("{}", std::mem::size_of::<GoodAlign>()); // 例如,8
字段的顺序是否会影响访问它们的性能?
顺序本身并不会影响访问字段的速度。然而,在低级按顺序遍历结构时(例如,使用 SIMD 指令或在循环中处理结构体数组),正确的对齐可以加速访问,因为更好地利用了缓存。
如果枚举的一个变体非常大,那么即使它是其他变体,每个枚举示例是否都会占用同样的内存?
是的,枚举的大小始终由变体中最大的一项加上分类符决定。任何 Packet 将占用 B 的大小,即使它是 A。
** 负面案例
在结构体中,字段是 u8,然后是 u64。在包含 100000 条记录的数组中,由于填充,内存消耗高达 1GB。
优点:
缺点:
** 正面案例
结构体按字段宽度排序,大变体的枚举被移到 Box 中,小变体保持原位。
优点:
缺点: