Перейти к содержанию
    

Битовые поля vs битовые маски

Доброго времени суток.

 

Битовые поля - кто за, кто против??

 

Да, знаю, что задаю в принципе извечный вопрос, однако ответа толкового в Интернете не нашел..

Кто-то говорит, что они помогают экономить память, кто-то - что эффективнее or'ить xor'ить и and'ить используя битовые маски и операции сдвига. Типа у bitfield overhead некий появляется, как плата за удобочитаемость....

 

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

#define BIT0 0x001

. . .

#define BIT15 0x8000

#define REGISTERx (*(volatile unsigned long *)(0x00D00))

 

а потом ставить биты REGISTERx|=BIT0+BIT3+BIT7;

или REGISTERx|=1<<7; //Ставим бит 7

 

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

Это же ужас! И никакой удобочитаемости это особо не добавляет. Часа два ушло у меня, чтобы толком разобраться что куда и откуда линкуется.

Неужели это всё может превзойти старые добрые методы?

 

Я всё-таки переделал на свой лад.

Потом задумался, а может зря... :) Может особо ничего и не выиграл ))

Оперативка очень важна. Иду впритык, каждый байт на счету.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Неужели это всё может превзойти старые добрые методы?

 

Может. Например, у того же техасовского TMS320C6x есть такая замечательная инструкция EXT. Она, по сути, заменяет собой две инструкции SHR и AND и служит для извлечения битового поля из регистра.

 

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

 

Существуют также и другие инструкции для манипулирования отдельными битами в словах. И они тоже будут использоваться компилятором.

 

Однако есть и минусы у такого подхода: стандарт C, если я правильно помню, ничего не говорит на тему того, какие биты слова должны ставиться в соответствие битовым полям. Так, имея два поля шириной 2 и 3 бита и 32-битную архитектуру, им может быть поставленно в соответствие 0-1 и 2-4 биты соотв., а может наоборот: 27-29 и 30-31. Если ты пишешь код, который отлаживаешь на писюке, а потом его же пускаешь на контролер, то тут могут возникнуть нюансы, которые зависят от твоих компиляторов. Но обычно, все же, имеет место здравый смысл, и поля заполняются по мере их объявления, заполняя младшие биты.

 

Оперативка очень важна. Иду впритык, каждый байт на счету.

 

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

 

Лично я, там где не критично, юзаю маски - привычнее и, ИМХО, нагляднее чуть-чуть. Где важна производительность, но при этом до асма спускаться лень, юзаю поля.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Позволю заметить.

Стандарт не говорит каким будет представление bit-field в памяти, это на совести компилятора и архитектуры (big или little indian), НО:

 

#define FLAG_0 0x00000001

unsigned int x=0;

x = x | (FLAG_0);

 

и

 

union{

struct{

unsigned int FLAG0 : 1;

unsigned int : 31;

}bits;

unsigned int val;

}y

y.bits.FLAG0 = 1;

 

Будет полностью ролностью эквивалентно независимо от архитектур (расположения байт в памяти для Int, если слова меньше 32 бит).

Использование union+bitfields или флагов+переменные полностью эквивалентно.

 

http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf

страница 37:

Values stored in unsigned bit-fields and objects of type unsigned char shall be

represented using a pure binary notation.40)

 

40) A positional representation for integers that uses the binary digits 0 and 1, in which the values

represented by successive bits are additive, begin with 1, and are multiplied by successive integral

powers of 2, except perhaps the bit with the highest position. (Adapted from the American National

Dictionary for Information Processing Systems.) A byte contains CHAR_BIT bits, and the values of

type unsigned char range from 0 to 2CHAR_BIT - 1.

 

Как уже отмечал, порядок байт в int зависит от компилера и платформы.

 

дополнение (начал перечитывать):

10 An implementation may allocate any addressable storage unit large enough to hold a bitfield.

If enough space remains, a bit-field that immediately follows another bit-field in a

structure shall be packed into adjacent bits of the same unit. If insufficient space remains,

whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is

implementation-defined. The order of allocation of bit-fields within a unit (high-order to

low-order or low-order to high-order) is implementation-defined. The alignment of the

addressable storage unit is unspecified.

 

Если биты не влезли, то пакуются в в прилегающие биты (биту 0 прилегает бит 7) в такой же примитивный тип (char..int) как и предыдущие биты.

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

Или если операции идет с байтами, то паковка в байты идущие подряд, для многобайтовых полей не связаных в union.

 

Использовать union надо обязательно для примитивного типа:

A union type describes an overlapping nonempty set of member objects, each of

which has an optionally specified name and possibly distinct type.

 

Ну стоит помнить что unsigned int может быть как 32 бита так и 16.

Ограничение определено как 16 и больше.

The

minimum magnitudes shown shall be replaced by implementation-defined magnitudes

with the same sign.

#define UINT_MAX 65535

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

#define BIT0 0x001

. . .

#define BIT15 0x8000

#define REGISTERx (*(volatile unsigned long *)(0x00D00))

 

а потом ставить биты REGISTERx|=BIT0+BIT3+BIT7;

или REGISTERx|=1<<7; //Ставим бит 7

Нельзя однозначно сказать. Эффективность применения того или иного приема зависит от реализации компилятора под конкретную архитектуру. Но в целом обращение через #define распознается хуже и компиляторы предпочитают лепить две команды ("достать константу"+"переслать значение константы в регистр"), хотя часто архитектура дает возможность сделать одной командой ("установить бит в регистре"). Анализ конечного опкода показывает: даже в тех компиляторах, где есть отступление от стандарта и вводятся новые типы битовых данных (навроде типа bit в HT), старые добрые битовые поля не уступают им в эффективности.

Изменено пользователем blackbit

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Битовые поля - кто за, кто против??
У меня такое мнение: если речь идет о периферии, то однозначно маски. Потому что регистры периферии объявлены как volatile и при использовании битовых полей при всем желании не получится заставить компилятор занести значения сразу в два или более полей, а это часто необходимо. Также часто необходимо просто присвоить всему регистру начальное значение, и это тоже невозможно при использовании битовых полей - приходится битовое поле заворачивать в union и в итоге писанины получается намного больше. А там, где много лишнего текста, потенциально будут ошибки.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Отлично! Значит правильно я сделал - сейчас работаю именно с периферией.

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

Вот вот, я когда на это всё посмотрел в техасовских примерах, сразу понял, что это не самое красивое решение.

Например, у того же техасовского TMS320C6x есть такая замечательная инструкция EXT

Гляну как у нас на 28мых с этим. Накрайняк макрос забить с ассемблерной вставкой и можно даже вручную кое-что оптимизировать.

 

Всем спасибо за ответы :a14:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Гляну как у нас на 28мых с этим. Накрайняк макрос забить с ассемблерной вставкой и можно даже вручную кое-что оптимизировать.

 

Ты того - с ассемблерными вставками поосторожней! Любая ассемблерная вставки приводит к тому, что компилятору приходится сбрасывать оптимизатор после того, как он наткнулся на это вставку. Гораздо лучше для этих целей использовать intrinsic'и, если они есть.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ты того - с ассемблерными вставками поосторожней!

Покорнейше прошу разъяснить, как-же мне без асма сделать к примеру запрет прерываний, если нужно чтобы компилер встявил именно setc INTM и не что иное?

Ничего кроме

#define DINT asm("setc INTM")

мне в голову не приходит.

И как-же мне заюзать "замечательную инструкцию EXT", о которой шла речь, без асма?

Может, конечно, я не понял того, что ты предлагаешь....

 

И если говорить об insintric, то на сколько я понимаю, inline круче!

В случае intrinsic компилятор имеет право решать сам что делать(вызов функции или вставку кода в место вызова).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Покорнейше прошу разъяснить, как-же мне без асма сделать к примеру запрет прерываний, если нужно чтобы компилер встявил именно setc INTM и не что иное?
Компиляторы бывают разные. Использование инлайн-асма в GCC безопасно, ибо он имеет инструменты сообщить компилятору что именно затрагивает ваша функция. У ИАР такой возможности нет, поэтому компилятор пытается подстраховаться и в инструкции прямо написано пожелание не использовать асмовые вставки. Вместо этого в компилятор встроены intrinsic-функции __disable_interrupts(), __no_operation(), __reset_watchdog() и т.д. Надо внимательно смотреть документацию на ваш компилятор.

 

 

И если говорить об insintric, то на сколько я понимаю, inline круче!
Угу. "Резистор хорошо, но транзистор круче!" - смысл примерно тот же. intrinsic и inline - совершенно ортогональные понятия, поэтому сравнивать их просто некорректно.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

как-же мне без асма сделать к примеру запрет прерываний, если нужно чтобы компилер встявил именно setc INTM и не что иное?

А что за компилятор для TMS320 не имеет в составе intrinsics чего-либо глобально запрещающего прерывания или генерящий "что-то иное"?

мне в голову не приходит.

Иногда нужно не только на свою голову полагаться, но и обращаться к документации.

Может, конечно, я не понял того, что ты предлагаешь....

Предлагается не пользоваться бездумно ASM вставками, там, где требуется качество кода, а не живописные заплатки демонстрирующие знание ASM и незнание инструментария. Если нужен ASM (для DSP это весьма частое явление - спору нет), то и писать этот кусочек нужно на ASM.

И если говорить об insintric, то на сколько я понимаю, inline круче!

Вообще-то "это", полагаю называется intrinsics, посему наверное Вам надо сначала разобраться, что есть одно и что есть другое.

 

 

 

 

 

Компиляторы бывают разные. Использование инлайн-асма в GCC безопасно..

Безопасность(работоспособность результата), это только одна из сторон использования ASM вставок. Вторая сторона это способность компилятора нормально оптимизировать такой, ломающийего "домашние заготовки" код.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А что за компилятор для TMS320 не имеет в составе intrinsics чего-либо глобально запрещающего прерывания или генерящий "что-то иное"?

Иногда нужно не только на свою голову полагаться, но и обращаться к документации.

CCS3.3, code generation tools v 5.0.1

раздел help'a TMS320C28x C/C++ Compiler Intrinsics видим:

int _ _abs16_sat(int src) Clear the OVM status bit. Load src into AH. Take absolute value of ACC. Store AH into dst. Clear the OVM status bit.

long _ _addcu(long src1, unsigned int src2); The contents of src2 and the value of the carry bit are added to ACC. The result is in ACC.

long dst=_ _ IQmpy(long A, long B, int N); The dst becomes ACC or P, A becomes XT:

long dst = _ _IQsat(long A, long max, long min); The dst becomes ACC. Different code is generated based on the value of max and/or min.

Где тут можно найти то, о чем я говорил?

Предлагается не пользоваться бездумно ASM вставками, там, где требуется качество кода, а не живописные заплатки демонстрирующие знание ASM и незнание инструментария. Если нужен ASM (для DSP это весьма частое явление - спору нет), то и писать этот кусочек нужно на ASM.

Спору нет. Для того и спрашиваю, чтобы узнать ответ.

Вообще-то "это", полагаю называется intrinsics, посему наверное Вам надо сначала разобраться, что есть одно и что есть другое.

Ну чуток напутал ))

Безопасность(работоспособность результата), это только одна из сторон использования ASM вставок. Вторая сторона это способность компилятора нормально оптимизировать такой, ломающий его "домашние заготовки" код.

т.е. имеется ввиду то, что компилятор может оптимизировать ещё такие втсвки? Или же по ошибке опущено "не" в словах "Вторая сторона это способность компилятора".

 

И всё-же как быть?

В моем коде используются следующие ассемблерные вставочки-макросы

#define EINT asm(" clrc INTM")

#define DINT asm(" setc INTM")

#define ERTM asm(" clrc DBGM")

#define DRTM asm(" setc DBGM")

#define EALLOW asm(" EALLOW")

#define EDIS asm(" EDIS")

#define ESTOP0 asm(" ESTOP0")

#define IDLE asm(" IDLE")

 

Ответа в документации я не нахожу пока.... Может быть я не там смотрю ))

И я думаю такие безобидные спец. инструкции(не использующие ни регистры, ни память) особо не повлияют на ход событий при оптимизации...

Или тут опять есть свои подводные камни?

Тема для меня в принципе новая, поэтому может быть и всплывают такие вопросы:)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Или же по ошибке опущено "не" в словах "Вторая сторона это способность компилятора".

Не опущено - либо эта способность есть, либо ее нет. Первое много более вероятно, чем второе.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

 

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

 

 

Где тут можно найти то, о чем я говорил?

 

...

 

И всё-же как быть?

В моем коде используются следующие ассемблерные вставочки-макросы

#define EINT asm(" clrc INTM")

#define DINT asm(" setc INTM")

#define ERTM asm(" clrc DBGM")

#define DRTM asm(" setc DBGM")

#define EALLOW asm(" EALLOW")

#define EDIS asm(" EDIS")

#define ESTOP0 asm(" ESTOP0")

#define IDLE asm(" IDLE")

 

Ответа в документации я не нахожу пока.... Может быть я не там смотрю ))

И я думаю такие безобидные спец. инструкции(не использующие ни регистры, ни память) особо не повлияют на ход событий при оптимизации...

Или тут опять есть свои подводные камни?

Тема для меня в принципе новая, поэтому может быть и всплывают такие вопросы:)

 

Компилятор не смотрит на то, что делают твой ассемблерные вставки. Он компилирует C, а не ассемблер. Представь себе, что ты компилятор. Ты компилируешь какой-то код, где решил что-то прооптимизировать, зачитав переменные в регистры. Затем ты натыкаешь на вставку. Ты не знаешь, что она делает. Возможно она портит твои регистры, возможно нет. Возможно она меняет значения переменных в памяти, которые ты загрузил в регистры, а возможно нет. В первом случае программист будет надеятся, что ты подхватишь из памяти новые значения, после того как отработает вставка. Вопрос к тебе как к компилятору: как при наличи такого черного ящика, сгенерировать такой код, который будет работать так, как это написал программист? Ответ один. Разрушить все свои предположения, а именно:

1) Не считать, что регистры, в которых закэшированны переменные, сохранили свое значение после вставки.

2) Не считать, что переменные, которые закэшированны в регистры, не изменились вставкой.

3) В зависимости от архитектуры могут поменятся различные флаги различных регистров состояний и т.п.

 

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

 

Все нужно делать с умом.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...