Я знаю, что поля в структурах 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: https://godbolt.org/z/svE5v6Tr8
Я это понимаю. Я не прошу подробного объяснения/уточнения. Я просто надеялся получить приблизительное понимание того, почему это могло произойти. Может быть, компилятор сначала распределяет подструктуры перечисления индивидуально и не пересматривает это решение позже? Или происходит что-то еще?
Вопросы по (текущим) деталям реализации компилятора, вероятно, лучше всего задавать на форуме IRLO или в чате Rust's Zulip. Это не значит, что вы не обязательно получите здесь ответ.
Или, возможно, зарегистрируйте это как ошибку в трекере проблем GitHub? Мне кажется разумным, что представление перечисления Rust должно лучше оптимизировать макет здесь.
Разве спецификация не позволяет переупорядочивать поля перечислений, или компилятор здесь просто «ленив»?
Макет типа по умолчанию/Rust
совершенно не указан, за исключением гарантий работоспособности. В остальном компилятор волен делать все, что хочет, и действительно, это меняется в зависимости от версии компилятора.
Есть ли примерное представление об алгоритме, который компилятор в настоящее время использует для размещения перечислений, чтобы я мог понять этот результат и предотвратить случайную пессимизацию моего кода в будущем?
Если вы хотите иметь гарантию на планировку, вам необходимо выбрать другое представительство.
Оптимизация ниши использует эвристики, и они постоянно настраиваются. Я не думаю, что есть какая-то спецификация, кроме исходного кода компилятора.