Преимущества логических значений для битовых полей - PullRequest
1 голос
/ 20 марта 2019

Кодовая база, в которой я работаю, довольно старая. Пока мы компилируем почти все с помощью c ++ 11. Большая часть кода была написана много лет назад. Разрабатывая новые классы в старых областях, я всегда оказываюсь в ситуации, когда мне приходится выбирать между соответствием старым методологиям или переходом на более современный подход.

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

class NewStructure
{
public:

    const bool getValue1() const
    {
        return value1;
    }

    void setValue1(const bool input)
    {
        value1 = input;
    }

private:
    bool value1;
    bool value2;
    bool value3;
    bool value4;
    bool value5;
    bool value6;
    bool value7;
    bool value8;
};

struct OldStructure
{
    const bool getValue1() const
    {
        return value1;
    }

    void setValue1(const bool input)
    {
        value1 = input;
    }

    unsigned char value1 : 1;
    unsigned char value2 : 1;
    unsigned char value3 : 1;
    unsigned char value4 : 1;
    unsigned char value5 : 1;
    unsigned char value6 : 1;
    unsigned char value7 : 1;
    unsigned char value8 : 1;
};

В этом случае размеры составляют 8 байтов для новой структуры и 1 для старой.
Я добавил «getter» и «setter», чтобы проиллюстрировать, что с точки зрения пользователя они могут быть идентичными. Я понимаю, что, возможно, вы могли бы сделать случай читабельности для следующего разработчика, но кроме этого, есть ли причина избегать битовых полей? Я знаю, что упакованные поля наносят удар по производительности, но, поскольку это все символы, правила заполнения все еще действуют.

Ответы [ 3 ]

4 голосов
/ 20 марта 2019

При использовании битовых полей необходимо учитывать несколько моментов. Те (порядок важности будет зависеть от ситуации)

  • Производительность

Работа битовых полей влечет за собой снижение производительности при установке или чтении (по сравнению с прямыми типами). На простом примере codegen показаны дополнительные испускаемые инструкции: https://gcc.godbolt.org/z/DpcErN Однако битовые поля обеспечивают более компактные данные, которые становятся более удобными для кэша и которые могут полностью перевесить любые недостатки дополнительных операций. Единственный способ понять реальное влияние на производительность - это сравнить реальное приложение с реальным вариантом использования.

  • Совместимость ABI

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

  • Юзабилити

Нет привязки ссылки к битовому полю, и вы не можете взять его адрес. Это может повлиять на код и сделать его менее понятным.

0 голосов
/ 20 марта 2019
template<typename enum_type,size_t n_bits>
class bit_flags{
    std::bitset<n_bits> bits;
    auto operator[](enum_type bit){return bits[bit];};
    auto& set(enum_type bit)){return set(bit);};
    auto& reset(enum_type bit)){return set(bit);};
     //go on with flip et al...
static_assert(std::is_enum<enum_type>{});
 };

enum class  v_flags{v1,v2,/*...*/vN};

bit_flags<v_flags,v_flags::vN+1> my_flags;

my_flags.set(v_flags::v1);
my_flags.[v_flags::v2]=true;

std::bitset столь же эффективно, как и bool битовые поля. Вы можете заключить его в класс, чтобы принудительно использовать каждый бит по именам, определенным в enum. Теперь у вас есть небольшая, но масштабируемая утилита для использования с несколькими различными наборами bool флагов. C ++ 17 делает это еще удобнее:

template<auto last_flag, typename enum_type=decltype(last_flag)>
class bit_flags{
    std::bitset<last_flag+1> bits;
    //...
};

bit_flags<v_flags::vN+1> my_flags;
0 голосов
/ 20 марта 2019

Для вас, как для программиста, особой разницы нет. Но машинный код для доступа к целому байту намного проще / короче, чем для доступа к отдельному биту, поэтому использование битовых полей увеличивает объем сгенерированного кода.

На псевдо-ассемблере ваш сеттер может превратиться в нечто вроде:

    ldb input1,b         ; get the new value into accumulator b
    movb b,value1        ; put it into the variable
    rts                  ; return from subroutine

Но для битовых полей это не так просто:

    ldb input1,b        ; get the new value into accumulator b
    movb bitfields,a    ; get current bitfield values into accumulator a
    cmpb b,#0           ; See what to do.
    brz clearvalue1:    ; If it's zero, go to clearing the bit
    orb #$80,a          ; set the bit representing value1.
    bra resume:         ; skip the clearing code.
clearvalue1:
    andb #$7f,a         ; clear the bit representing value1
resume:
    movb a,bitfields    ; put the value back
    rts                 ; return

И он должен делать это для каждого из ваших сеттеров из 8 участников, и что-то подобное для добытчиков. Это складывает. Кроме того, даже самые глупые на сегодняшний день компиляторы, вероятно, встроили бы полнобайтовый установочный код, вместо того чтобы фактически вызывать подпрограмму. Для установщика битового поля это может зависеть от того, что вы компилируете, оптимизируя скорость и пространство.

И вы спрашивали только о логических значениях. Если они были целочисленными битовыми полями, то компилятору приходится иметь дело с загрузкой, маскированием предыдущих значений, смещением значения для выравнивания по его полю, маскированием неиспользуемых битов, and / or значения на месте и последующей записью его обратно. в память.

Так зачем вам использовать одно против другого?

  • Битовые поля медленнее, но упаковывают данные более эффективно.
  • Недобитовые поля работают быстрее и требуют меньше машинного кода для доступа.

Как разработчик, это ваше суждение. Если вы будете хранить много экземпляров Structure в памяти одновременно, экономия памяти может стоить того. Если вы не собираетесь иметь много экземпляров этой структуры в памяти одновременно, то скомпилированный код компенсирует экономию памяти, и вы жертвуете скоростью.

...