代码之家  ›  专栏  ›  技术社区  ›  ChrisB

为什么Rust不为内存布局重新排序枚举中的字段?

  •  0
  • ChrisB  · 技术社区  · 1 年前

    我知道Rust结构中的字段会自动重新排序以节省内存(除非例如。 #[repr(C)] 被指定)。

    我的假设是,枚举也是如此。 今天我创建了一个简单的助手类型(示例的语义无关紧要,这是关于内存布局的):

    pub enum LazyRwLockGuardVersionA<'a, T> {
        Unlocked(&'a RwLock<T>),
        Read {
            lock: &'a RwLock<T>, // still neccessary for promoting to write lock
            guard: RwLockReadGuard<'a, T>,
        },
        Write(RwLockWriteGuard<'a, T>),
    }
    

    而原始读/写保护使用 16 字节,此类型使用 32 . 为了让编译器使用利基优化,我尝试了这个版本:

    pub enum LazyRwLockWriteGuard<'a, T> {
        Unlocked(&'a RwLock<T>),
        Write(RwLockWriteGuard<'a, T>),
    }
    
    pub enum LazyRwLockGuardVersionB<'a, T> {
        Read {
            guard: RwLockReadGuard<'a, T>,
            lock: &'a RwLock<T>,
        },    
        NonRead(LazyRwLockWriteGuard<'a, T>),
    }
    

    这成功地将内存使用率降低到 24 字节。


    然而,我注意到了一些奇怪的事情: 反转中字段的顺序时 Read 变种

    pub enum LazyRwLockGuardVersionC<'a, T> {
        Read {
            lock: &'a RwLock<T>, // order of fields reversed
            guard: RwLockReadGuard<'a, T>,
        },
        NonRead(LazyRwLockWriteGuard<'a, T>),
    }
    

    这种类型突然使用 32 字节。

    我的Rust版本是1.77,这里有一个godbolt repro: https://godbolt.org/z/svE5v6Tr8


    • 是规范不允许对枚举字段进行重新排序,还是编译器在这里只是“懒惰”?
    • 编译器当前使用的算法是否有粗略的直觉 用于布局枚举,这样我就可以理解这个结果,并防止将来意外地对我的代码进行悲观化?
    1 回复  |  直到 1 年前
        1
  •  1
  •   eggyal    1 年前

    是规范不允许对枚举字段进行重新排序,还是编译器在这里只是“懒惰”?

    默认值/ Rust 类型布局完全未指定,超出了可靠性保证范围。编译器在其他方面可以自由地做任何它想做的事情,事实上,这在编译器版本之间确实会发生变化。

    编译器目前用于布局枚举的算法是否有粗略的直觉,这样我就可以理解这个结果,并防止将来我的代码出现意外的悲观情绪?

    如果要保证布局,则必须选择不同的 representation .