Является ли тип Option меньше обернутого типа плюс логическое значение? - PullRequest
0 голосов
/ 05 апреля 2020
use std::mem::size_of;

struct Position {
    x: f32,
    y: f32,
    z: f32,
}

struct PoolItem {
    entity_id: u32, // 4 bytes
    used: bool, // 1 bytes + 3 (padding)
    component: Position, // 12 bytes
}


assert_eq!(size_of::<u32>(), 4);
assert_eq!(size_of::<Position>(), 12);
assert_eq!(size_of::<PoolItem>(), 20);

Как видите, такая структура имеет длину 20 байт. Position на самом деле является необязательным и зависит от used.

. Будет ли использование Option устранять необходимость в поле used и уменьшать размер структуры до 16?

struct PoolItem {
    entity_id: u32, // 4 bytes
    component: Option<Position>, // 12 bytes ?
}

Если так, то как Option реализован для такого поведения?

Мои тесты на Playground , похоже, показывают, что он не работает. Почему?

Ответы [ 3 ]

5 голосов
/ 05 апреля 2020

Точная реализация Option на самом деле не имеет значения. Очевидно, что вы не можете хранить X объем данных в X объеме хранилища , а также хранит, есть ли данные вообще. Очевидной реализацией для Option будет хранение как объекта, так и логического значения, указывающего, существует ли объект; явно что-то подобное происходит. Option - это удобство, он все равно должен где-то хранить информацию.

Обратите внимание, что за пределами struct (который должен иметь постоянный размер) Option может избежать этой стоимости, если оптимизатор определит Option всегда знал статус «заполнен или нет», поэтому логическое значение может быть исключено в пользу кода, всегда использующего его детерминистически правильным образом (либо считывая объект из стека, если он логически существует или не делает, когда это не так). Но в этом случае нужны дополнительные данные.

2 голосов
/ 05 апреля 2020

Option<Position> необходимо хранить состояние (Some или None) где-то , а поскольку Position уже содержит 12 байтов информации, вам нужно больше места для ее хранения. Обычно это означает, что он добавляет дополнительный байт (плюс заполнение) для сохранения состояния, хотя в некоторых случаях внутренний тип имеет известное неиспользуемое состояние. Например, ссылка может указывать на адрес 0, поэтому Option<&'_ T> может использовать 0 в качестве состояния None и занимать то же количество байтов, что и &'_ T. Однако для вашего Position типа это не так.

Если вам абсолютно необходимо, чтобы ваша структура PoolItem была как можно меньше, и если вы можете сэкономить один бит из поля entity_id ( скажем, старший бит, 2 31 ), вы можете использовать его для хранения состояния:

const COMPONENT_USED_BIT: u32 = (1u32 << 31);

struct PoolItem {
    entity_id: u32, // lowest 31 bits = entity ID, highest bit = "component used"
    component: Position,
}

Это может стать немного сложным, так как вам нужно убедиться, что вы ' обрабатывая этот бит специально, но вы можете написать пару простых методов доступа, чтобы гарантировать, что специальный бит обрабатывается правильно.

impl PoolItem {
    /// Get entity ID, without the "component used" bit
    fn entity_id(&self) -> u32 {
        self.entity_id & !COMPONENT_USED_BIT
    }

    /// Set entity ID, keeping the existing "component used" bit
    fn set_entity_id(&mut self, entity_id: u32) {
        let component_used_bit = self.entity_id & COMPONENT_USED_BIT;
        self.entity_id = (entity_id & !COMPONENT_USED_BIT) | component_used_bit;
    }

    /// Get component if "component used" bit is set
    fn component(&self) -> Option<&Position> {
        if self.entity_id & COMPONENT_USED_BIT != 0 {
            Some(&self.component)
        } else {
            None
        }
    }

    /// Set component, updating the "component used" bit
    fn set_component(&mut self, component: Option<Position>) {
        if let Some(component) = component {
            self.component = component;
            self.entity_id |= COMPONENT_USED_BIT;
        } else {
            self.entity_id &= !COMPONENT_USED_BIT;
        }
    }
}

Пример игровой площадки с тестами

0 голосов
/ 05 апреля 2020

Как предлагается в комментариях, альтернативой было бы использовать Option с NonZeroU32 для entity_id и полагаться на Some и None, чтобы проверить, используется объект или нет.

struct PoolItem {
    entity_id: Option<core::num::NonZeroU32>, // 4 bytes
    component: Position, // 12 bytes
}

fn main() {
    assert_eq!(size_of::<u32>(), 4);
    assert_eq!(size_of::<Position>(), 12);
    assert_eq!(size_of::<PoolItem>(), 16);
}

Идентификатор сущности начинается с 1.

Детская площадка

...