Почему Rust не меняет порядок полей в перечислении для размещения в памяти?

Я знаю, что поля в структурах 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


  • Разве спецификация не позволяет переупорядочивать поля перечислений, или компилятор здесь просто «ленив»?
  • Есть ли примерное представление об алгоритме, который в настоящее время использует компилятор? для макетирования перечислений, чтобы я мог понять этот результат и предотвратить случайную пессимизацию моего кода в будущем?

Оптимизация ниши использует эвристики, и они постоянно настраиваются. Я не думаю, что есть какая-то спецификация, кроме исходного кода компилятора.

Chayim Friedman 11.04.2024 13:45

Я это понимаю. Я не прошу подробного объяснения/уточнения. Я просто надеялся получить приблизительное понимание того, почему это могло произойти. Может быть, компилятор сначала распределяет подструктуры перечисления индивидуально и не пересматривает это решение позже? Или происходит что-то еще?

ChrisB 11.04.2024 13:51

Вопросы по (текущим) деталям реализации компилятора, вероятно, лучше всего задавать на форуме IRLO или в чате Rust's Zulip. Это не значит, что вы не обязательно получите здесь ответ.

eggyal 11.04.2024 14:08

Или, возможно, зарегистрируйте это как ошибку в трекере проблем GitHub? Мне кажется разумным, что представление перечисления Rust должно лучше оптимизировать макет здесь.

eggyal 11.04.2024 14:19
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
4
125
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Разве спецификация не позволяет переупорядочивать поля перечислений, или компилятор здесь просто «ленив»?

Макет типа по умолчанию/Rust совершенно не указан, за исключением гарантий работоспособности. В остальном компилятор волен делать все, что хочет, и действительно, это меняется в зависимости от версии компилятора.

Есть ли примерное представление об алгоритме, который компилятор в настоящее время использует для размещения перечислений, чтобы я мог понять этот результат и предотвратить случайную пессимизацию моего кода в будущем?

Если вы хотите иметь гарантию на планировку, вам необходимо выбрать другое представительство.

Другие вопросы по теме