Подтвердить что ты не робот

Когда стоит использовать битовые поля?

Стоит ли использовать реализацию битового поля C? Если да, когда он когда-либо используется?

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

Это что-то, чего можно избежать по соображениям производительности (или по какой-то другой причине)?

Есть ли еще время, когда используются битовые поля? (т.е. прошивка для установки фактических чипов и т.д.)

4b9b3361

Ответ 1

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

В то время как бит-поля могут привести к аккуратным синтаксисам, они довольно зависимы от платформы и, следовательно, не переносятся. Более портативный, но еще более подробный подход заключается в использовании прямого побитового манипулирования с использованием сдвигов и бит-масок.

Если вы используете бит-поля для чего угодно, кроме сборки (или дизассемблирования) структур на каком-либо физическом интерфейсе, производительность может пострадать. Это связано с тем, что каждый раз, когда вы читаете или записываете из битового поля, компилятору придется генерировать код для маскировки и сдвига, который будет записывать циклы.

Ответ 2

Одно использование битовых полей, о которых еще не упоминалось, состоит в том, что битподы unsigned предоставляют арифметику по модулю "сила-два" бесплатно ". Например, данный:

struct { unsigned x:10; } foo;

арифметика на foo.x будет выполняться по модулю 2 10= 1024.

(То же самое можно достичь напрямую, используя битётные операции &, конечно, но иногда это может привести к более четкому коду, чтобы компилятор сделал это для вас).

Ответ 3

FWIW, и глядя только на относительный вопрос о производительности - футуристический тест:

#include <time.h>
#include <iostream>

struct A
{
    void a(unsigned n) { a_ = n; }
    void b(unsigned n) { b_ = n; }
    void c(unsigned n) { c_ = n; }
    void d(unsigned n) { d_ = n; }
    unsigned a() { return a_; }
    unsigned b() { return b_; }
    unsigned c() { return c_; }
    unsigned d() { return d_; }
    volatile unsigned a_:1,
                      b_:5,
                      c_:2,
                      d_:8;
};

struct B
{
    void a(unsigned n) { a_ = n; }
    void b(unsigned n) { b_ = n; }
    void c(unsigned n) { c_ = n; }
    void d(unsigned n) { d_ = n; }
    unsigned a() { return a_; }
    unsigned b() { return b_; }
    unsigned c() { return c_; }
    unsigned d() { return d_; }
    volatile unsigned a_, b_, c_, d_;
};

struct C
{
    void a(unsigned n) { x_ &= ~0x01; x_ |= n; }
    void b(unsigned n) { x_ &= ~0x3E; x_ |= n << 1; }
    void c(unsigned n) { x_ &= ~0xC0; x_ |= n << 6; }
    void d(unsigned n) { x_ &= ~0xFF00; x_ |= n << 8; }
    unsigned a() const { return x_ & 0x01; }
    unsigned b() const { return (x_ & 0x3E) >> 1; }
    unsigned c() const { return (x_ & 0xC0) >> 6; }
    unsigned d() const { return (x_ & 0xFF00) >> 8; }
    volatile unsigned x_;
};

struct Timer
{
    Timer() { get(&start_tp); }
    double elapsed() const {
        struct timespec end_tp;
        get(&end_tp);
        return (end_tp.tv_sec - start_tp.tv_sec) +
               (1E-9 * end_tp.tv_nsec - 1E-9 * start_tp.tv_nsec);
    }
  private:
    static void get(struct timespec* p_tp) {
        if (clock_gettime(CLOCK_REALTIME, p_tp) != 0)
        {
            std::cerr << "clock_gettime() error\n";
            exit(EXIT_FAILURE);
        }
    }
    struct timespec start_tp;
};

template <typename T>
unsigned f()
{
    int n = 0;
    Timer timer;
    T t;
    for (int i = 0; i < 10000000; ++i)
    {
        t.a(i & 0x01);
        t.b(i & 0x1F);
        t.c(i & 0x03);
        t.d(i & 0xFF);
        n += t.a() + t.b() + t.c() + t.d();
    }
    std::cout << timer.elapsed() << '\n';
    return n;
}

int main()
{
    std::cout << "bitfields: " << f<A>() << '\n';
    std::cout << "separate ints: " << f<B>() << '\n';
    std::cout << "explicit and/or/shift: " << f<C>() << '\n';
}

Вывод на моем тестовом компьютере (числа варьируются от ~ 20% до запуска):

bitfields: 0.140586
1449991808
separate ints: 0.039374
1449991808
explicit and/or/shift: 0.252723
1449991808

Предполагает, что с g++ -O3 на довольно недавнем Athlon битовые поля хуже, чем в несколько раз медленнее, чем отдельные ints, и эта конкретная реализация и/или/bithift по крайней мере в два раза хуже снова ( "хуже", чем другие операции, такие как чтение/запись памяти подчеркивается волатильностью выше, а также накладными расходами в цикле и т.д., поэтому различия в занижении результатов).

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


UPDATE: user2188211 попытался отредактировать, которое было отклонено, но полезно проиллюстрировано, как битовые поля становятся быстрее по мере увеличения объема данных: "при повторении по вектору из нескольких миллионов элементов в [измененной версии] вышеупомянутого кода, так что переменные не находятся в кеше или регистрах, код битового поля может быть самым быстрым".

template <typename T>
unsigned f()
{
    int n = 0;
    Timer timer;
    std::vector<T> ts(1024 * 1024 * 16);
    for (size_t i = 0, idx = 0; i < 10000000; ++i)
    {
        T& t = ts[idx];
        t.a(i & 0x01);
        t.b(i & 0x1F);
        t.c(i & 0x03);
        t.d(i & 0xFF);
        n += t.a() + t.b() + t.c() + t.d();
        idx++;
        if (idx >= ts.size()) {
            idx = 0;
        }
    }
    std::cout << timer.elapsed() << '\n';
    return n;
}

Результаты из примера run (g++ -03, Core2Duo):

 0.19016
 bitfields: 1449991808
 0.342756
 separate ints: 1449991808
 0.215243
 explicit and/or/shift: 1449991808

Конечно, синхронизация всех относительных и способ реализации этих полей может вообще не иметь значения в контексте вашей системы.

Ответ 4

Я видел/использовал битовые поля в двух ситуациях: компьютерные игры и аппаратные интерфейсы. Аппаратное обеспечение довольно прямолинейно: аппаратное обеспечение ожидает данных в определенном битном формате, который вы можете определить вручную или через предварительно определенные структуры библиотеки. Это зависит от конкретной библиотеки, используют ли они битовые поля или просто манипулируют битами.

В компьютерах "старых дней" часто использовались битовые поля, чтобы максимально использовать компьютерную/дисковую память. Например, для определения NPC в RPG вы можете найти (составленный пример):

struct charinfo_t
{
     unsigned int Strength : 7;  // 0-100
     unsigned int Agility : 7;  
     unsigned int Endurance: 7;  
     unsigned int Speed : 7;  
     unsigned int Charisma : 7;  
     unsigned int HitPoints : 10;    //0-1000
     unsigned int MaxHitPoints : 10;  
     //etc...
};

Вы не видите его так много в более современных играх/программном обеспечении, поскольку экономия пространства ухудшается, поскольку компьютеры получают больше памяти. Сохранение 1 МБ памяти, когда ваш компьютер имеет только 16 МБ, - это большое дело, но не так много, когда у вас 4 ГБ.

Ответ 5

Бит-поля были использованы в старые времена для сохранения памяти программы.

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

Посмотрите источник http://www.nethack.org/, чтобы увидеть pre ansi c во всей его славе битлов!

Ответ 6

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

Ответ 7

В современном коде есть только одна причина использования битполей: для управления требованиями к пространству типа bool или enum в структуре/классе. Например (С++):

enum token_code { TK_a, TK_b, TK_c, ... /* less than 255 codes */ };
struct token {
    token_code code      : 8;
    bool number_unsigned : 1;
    bool is_keyword      : 1;
    /* etc */
};

IMO в принципе не имеет смысла не использовать бит-бит :1 для bool, так как современные компиляторы будут генерировать для него очень эффективный код. В C, однако, убедитесь, что ваш bool typedef является либо C99 _Bool, либо не работает, что неподписанный int, потому что подписанное однобитовое поле может содержать только значения 0 и -1 (если у вас не будет не-двухкомпонентная машина).

С типами перечислений всегда используйте размер, соответствующий размеру одного из примитивных целочисленных типов (8/16/32/64 бит, на обычных ЦП), чтобы избежать неэффективной генерации кода (повторяющиеся циклы чтения-модификации-записи, обычно).

Использование битовых полей для выстраивания структуры с некоторыми внешними форматами данных (заголовки пакетов, регистры ввода-вывода с отображением памяти) обычно предлагается, но я на самом деле считаю это плохой практикой, потому что C не дает вам достаточно контроль над endianness, padding и (для регистров ввода-вывода) именно то, что собираются сборочные последовательности. Взгляните на предложения представления Ada когда-нибудь, если вы хотите увидеть, сколько C отсутствует в этой области.

Ответ 8

В 70-е годы я использовал битовые поля для управления оборудованием на trs80. Дисплей/клавиатура/кассета/диски были устройствами с отображением памяти. Отдельные биты управляли различными вещами.

  • Отображение столбцов 32 столбцов и 64 столбцов.
  • Бит 0 в той же ячейке памяти был вводом/выводом данных в кассете.

Насколько я помню, для управления дисководом было несколько из них. Всего было 4 байта. Я думаю, что был выбран 2-битный диск. Но это было давно. В то время было довольно впечатляюще, что для формы растений было по крайней мере два разных компилятора c.

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

Ответ 9

Boost.Thread использует битполы в своем shared_mutex, по крайней мере на Windows:

    struct state_data
    {
        unsigned shared_count:11,
        shared_waiting:11,
        exclusive:1,
        upgrade:1,
        exclusive_waiting:7,
        exclusive_waiting_blocked:1;
    };

Ответ 10

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

Вся идея состоит в том, чтобы воспользоваться ситуациями, в которых у вас есть несколько полей в каком-либо типе структуры, которым не нужна полная ширина (и диапазон) некоторого стандартного типа данных. Это дает вам возможность упаковать несколько таких полей в одну единицу распределения, тем самым уменьшая общий размер типа структуры. И крайним примером могут быть логические поля, которые могут быть представлены отдельными битами (например, 32 из них могут быть упакованы в единый блок выделения unsigned int).

Очевидно, что это имеет смысл только в ситуации, когда плюсы потребления уменьшенной памяти перевешивают минусы медленного доступа к значениям, хранящимся в битовых полях. Однако такие ситуации возникают довольно часто, что делает бит-поля абсолютно незаменимыми языковыми признаками. Это должно ответить на ваш вопрос о современном использовании битовых полей: они не только используются, но и являются практически обязательными в любом практически значимом коде, ориентированном на обработку больших объемов однородных данных (например, на больших графиках, например, на их примере) - преимущества использования значительно перевешивают любые индивидуальные штрафы за производительность.

В некотором смысле битовые поля в своей цели очень похожи на такие "малые" арифметические типы: signed/unsigned char, short, float. В фактическом коде обработки данных обычно не используются любые типы, меньшие, чем int или double (за небольшим исключением). Арифметические типы типа signed/unsigned char, short, float существуют только для того, чтобы служить в качестве типов "хранения": как компактные элементы с сохранением памяти типов структур в ситуациях, когда известно, что их диапазон (или точность) является достаточным. Бит-поля - это еще один шаг в том же направлении, который приносит более высокую производительность для гораздо больших преимуществ экономии памяти.

Итак, это дает нам довольно четкий набор условий, при которых стоит использовать битовые поля:

  • Тип структуры содержит несколько полей, которые могут быть упакованы в меньшее количество бит.
  • Программа создает экземпляр большого количества объектов этого типа структуры.

Если выполняются условия, вы объявляете все бит-пакеты полей смежно (обычно в конце типа структуры), назначая им соответствующую ширину битов (и, как правило, предпринимайте некоторые шаги, чтобы гарантировать, что ширина бит являются подходящими). В большинстве случаев имеет смысл играть с упорядочением этих полей для достижения наилучшей упаковки и/или производительности.


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

Ответ 11

Альтернативой рассмотрению является указание структур битового поля с фиктивной структурой (никогда не созданной), где каждый байт представляет бит:

struct Bf_format
{
  char field1[5];
  char field2[9];
  char field3[18];
};

При таком подходе sizeof дает ширину битового поля, а offsetof дает смещение битового поля. По крайней мере, в случае GNU gcc оптимизация компилятора битовых операций (с постоянными сдвигами и масками), похоже, дошла до грубой четности с битовыми полями (базового языка).

Я написал заголовочный файл С++ (используя этот подход), который позволяет определять и использовать структуры битовых полей в перформансе, гораздо более портативные, гораздо более гибкие: https://github.com/wkaras/C-plus-plus-library-bit-fields. Итак, если вы не застряли с использованием C, я думаю, что редко было бы веской причиной использовать средство базового языка для битных полей.