Hi уважаемый All.
Вопрос по работе с битовыми флагами. Понимаю, что пытаюсь экономить на спичках, но все же. Как говорится мелочь, а неприятно.
Итак, есть два набора битовых флагов (маска и значение), хочется инкапсулировать работу с такой структурой посредством inline функций.
class Opts
{
public:
// Должно быть protected, public стоит для эксперимента (сравнения сгенерированного ассемблерного кода).union OptsFlags
{
WORD Flags;
struct
{
bool bShowPointNames:1;
bool bShowPoints:1;
bool bShowPointIcons:1;
};
};
OptsFlags m_Mask;
OptsFlags m_Values;
public:
// И делаем такие функции для доступа к переменным с масками.inline bool GetDrawPoints() const
{
if(m_Mask.bShowPoints) {return m_Values.bShowPointIcons;}
else return false;
}
};
Пытаемся это дело использовать:
Opts O;
if(O.GetDrawPoints()) {return TRUE;}
Смотрим, что сгенерировал компилятор:
test BYTE PTR _O$[esp+756], 2
je SHORT $LN3@InitInstan
mov al, BYTE PTR _O$[esp+758]
shr al, 2
and al, 1je SHORT $LN3@InitInstan
Вот зачем он генерирует выделенные строки?
Ладно бы он был просто тупой и в принципе не умел работать с битовыми полями. Но так может же сделать нормально, если захочет. Проверяем. Переносим тело функции непосредственно в место вызова (для этого и нужна метка public для членов данных в классе Opts, иначе следующий фрагмент не скомпилируется)
Opts O;
if (O.m_Mask.bShowPoints && O.m_Values.bShowPointIcons) {return TRUE;}
Смотрим, что получилось:
test BYTE PTR _O$[esp+756], 2
je SHORT $LN3@InitInstan
test BYTE PTR _O$[esp+758], 4je SHORT $LN3@InitInstan
Ну? Совсем другое дело! Может же зараза, когда хочет
Но позвольте, ведь при использовании inline функций результаты должны быть эквивалентны! Почему же здесь результаты разные? Ну и собственно вопрос: как заставить компилятор генерировать оптимальный код при использовании функций? Все же хочется писать понятный код, который проще сопровождать (поменять одну функцию проще, чем выискивать во всей программе места, где используется этот кусок кода).
к желаемому результату не приводят.
Пока пришел к компромиссному варианту, когда вместо m_Values используются реальные bool переменные. Тогда код генерируется нормальный:
test BYTE PTR _O$[esp+756], 2
je SHORT $LN3@InitInstan
cmp BYTE PTR _O$[esp+760], 0
je SHORT $LN3@InitInstan
Но все же хочется чтобы и вместо m_Values можно было использовать битовые поля без потери эффективности (и не только ради экономии места, а еще и ради того, чтобы не нужно было дублировать объявление флагов (один раз для битовых полей, второй раз для реальных bool переменных)). Что скажет сообщество? Или дело в каких-то ограничениях стандарта, который где-то говорит что-то типа того, что если стоит return, то компилятор просто обязан преобразовать возвращаемое значение к "каноническому" виду булевской переменной? В общем, хотелось бы разобраться.
Re: Тупость компилятора или ограничения стандарта?
Здравствуйте, programmater, Вы писали:
P>Hi уважаемый All. P>Вопрос по работе с битовыми флагами. Понимаю, что пытаюсь экономить на спичках, но все же. Как говорится мелочь, а неприятно. P>Итак, есть два набора битовых флагов (маска и значение), хочется инкапсулировать работу с такой структурой посредством inline функций.
P>Но позвольте, ведь при использовании inline функций результаты должны быть эквивалентны!
результаты выполнения, а не ассемблерный код!
Есть мнение что вы не задали набор процессорных инструкций и компилятор пытается сгенерить код который работал бы даже на 8086.
Например ++i в этом случае будет переведено в add eax,1, а не inc eax.
Чтобы gcc автоматически использовал полный набор инструкций поддерживаемых вашим процессором, добавьте параметр -march=native.
Помнится какой-то из старых мотороловских процессоров как раз имел набор инструкций которые позволяли доставать значение конкретного бита в одну операцию.
Re[2]: Тупость компилятора или ограничения стандарта?
Здравствуйте, potapov.d, Вы писали:
PD>Есть мнение что вы не задали набор процессорных инструкций и компилятор пытается сгенерить код который работал бы даже на 8086. PD>Например ++i в этом случае будет переведено в add eax,1, а не inc eax.
и это верно, т.к. add везде рекомендуют вместо inc!!!
PD>Чтобы gcc автоматически использовал полный набор инструкций поддерживаемых вашим процессором, добавьте параметр -march=native. PD>Помнится какой-то из старых мотороловских процессоров как раз имел набор инструкций которые позволяли доставать значение конкретного бита в одну операцию.
процессоров много, чем нам поможет знание, что существует такие? в интеле есть BT и тп например
---
С уважением,
Сергей Мухин
Re[3]: Тупость компилятора или ограничения стандарта?
Здравствуйте, Сергей Мухин, Вы писали: СМ>и это верно, т.к. add везде рекомендуют вместо inc!!!
И чем это верно кроме того что "рекомендуют"?
add — это три байта инструкций, а inc — один, количества тиков на выполнение тоже соответствующие.
СМ>процессоров много, чем нам поможет знание, что существует такие? в интеле есть BT и тп например
Человек спросил как сделать так чтобы значение битового поля доставалось наиболее эффективно, я ответил. Ещё вопросы?
Re[4]: Тупость компилятора или ограничения стандарта?
Здравствуйте, potapov.d, Вы писали:
PD>Здравствуйте, Сергей Мухин, Вы писали: СМ>>и это верно, т.к. add везде рекомендуют вместо inc!!! PD>И чем это верно кроме того что "рекомендуют"? PD>add — это три байта инструкций, а inc — один, количества тиков на выполнение тоже соответствующие.
1. он оди байт только в 32битной интел
2. почитайте сначала. тиков то одинаково, а выполнение медленней, из-за многопоточного выполнения имхо.
СМ>>процессоров много, чем нам поможет знание, что существует такие? в интеле есть BT и тп например PD>Человек спросил как сделать так чтобы значение битового поля доставалось наиболее эффективно, я ответил. Ещё вопросы?
вы ответили про какой-то мотороловский процессор!!! который никаког отношения к обсужлаемой теме не имеет!
---
С уважением,
Сергей Мухин
Re[2]: Тупость компилятора или ограничения стандарта?
то сгенерированный код будет более эффективным. Воистину пути компилятора неисповедимы , и иногда более страшно выглядящий фрагмент программы на С++ разворачивается в более эффективный код.
Всем спасибо.
ЗЫ. набор инструкций тут не при чем. Иначе бы он и при развороте тела функции непосредственно в оператор условия тоже генерировал бы неэффективный код. Компилятор — 2005 студия (которая по моему меньше чем для пентиума и генерировать не умеет).
Re[5]: Тупость компилятора или ограничения стандарта?
Здравствуйте, Сергей Мухин, Вы писали:
СМ>1. он оди байт только в 32битной интел СМ>2. почитайте сначала. тиков то одинаково, а выполнение медленней, из-за многопоточного выполнения имхо.
Ваше, имхо, для меня, безусловно, очень важно. А теперь, пожалуйста, результаты официальных замеров производительности в студию.
СМ>вы ответили про какой-то мотороловский процессор!!! который никаког отношения к обсужлаемой теме не имеет!
Да засунь уже свои восклицательные знаки себе в задницу.
Re[4]: Тупость компилятора или ограничения стандарта?
Здравствуйте, potapov.d, Вы писали:
СМ>>и это верно, т.к. add везде рекомендуют вместо inc!!! PD>И чем это верно кроме того что "рекомендуют"? PD>add — это три байта инструкций, а inc — один, количества тиков на выполнение тоже соответствующие.
Add — устанавливает все флаги
Inc — не меняет флаг CF, в результате на современных процах выполняется медленее, т.к. вызывает задержку регистра флагов и притормаживает исполнение (которое в современных процах отнюдь не последовательное).
Где то видел хорошую развёрнутую статью на эту тему, но не могу найти.
Пока нашёл выдержку со статьи с Wasm, в которой вкратце описана проблема.
19.2 Частичные задержки флагов
Регистр флагов также может вызвать частичную задержку:
CMP EAX, EBX
INC ECX
JBE XX ; задержка
Инструкция JBE читает и флаг переноса, и флаг нуля. Так как инструкция INC изменяет флаг нуля, но не флаг переноса, то инструкции JBE приходится подождать, пока две предыдущие инструкции не будут выведены из обращения, прежде чем она сможет скомбинировать флаг переноса от инструкции CMP с флагом нуля от инструкции INC. Подобная ситуация больше похожа на баг, чем на преднамеренную комбинацию флагов. Чтобы скорректировать эту ситуацию, измените INC ECX на ADD ECX, 1. Похожий баг, вызывающий задержку, — это 'SAHF / JL XX'/. Инструкция JL тестирует флаг знака и флаг переполнения, но не меняет последний. Чтобы исправить это, измените 'JL XX' на 'JS XX'.
Неожиданно (и в противоположность тому, что говорят руководства от Intel), частичная задержка регистра может случиться, если были изменены какие-то биты регистра флагов, а затем считаны неизмененные.
CMP EAX, EBX
INC ECX
JC XX ; задержка
но не при чтении только изменных битов:
CMP EAX, EBX
INC ECX
JE XX ; нет задержки
Частичные задержки флагов возникают, как правило, при использовании инструкций, которые считывают много или все биты регистра флагов, например LAHF, PUSHF, PUSHFD. Инструкции, которые вызывают задержку, если за ними идут LAHF или PUSHF(D), следующие: INC, DEC, TEST, битовые тесты, битовые сканирования, CLC, STC, CMC, CLD, STD, CLI, STI, MUL, IMUL, и все виды битовых сдвигов и вращений. Следующие инструкции не вызывают задержки: AND, OR, XOR, ADD, ADC, SUB, SBB, CMP, NEG. Странно, что TEST и AND ведут себя различно, хотя по описанию они делают с флагами одно и то же. Вы можете использовать инструкции SETcc вместо LAHF или PUSHF(D) для сохранения значения флага, чтобы избежать задержки.
Здравствуйте, CreatorCray, Вы писали:
CC>Здравствуйте, potapov.d, Вы писали:
СМ>>>и это верно, т.к. add везде рекомендуют вместо inc!!! PD>>И чем это верно кроме того что "рекомендуют"? PD>>add — это три байта инструкций, а inc — один, количества тиков на выполнение тоже соответствующие. CC>Add — устанавливает все флаги CC>Inc — не меняет флаг CF, в результате на современных процах выполняется медленее, т.к. вызывает задержку регистра флагов и притормаживает исполнение (которое в современных процах отнюдь не последовательное).
Спасибо. Теперь понимаю.
Re[6]: Тупость компилятора или ограничения стандарта?
Здравствуйте, potapov.d, Вы писали:
>> код который работал бы даже на 8086 ... будет переведено в add eax,1, а не inc eax.
кстати, ваш пример, крайне не удачен, т.к. inc eax (естественно регистр 8битный) был еще (если мне память не изменяет) на 8битном 8008 !
---
С уважением,
Сергей Мухин
Re: Тупость компилятора или ограничения стандарта?
P> test BYTE PTR _O$[esp+756], 2
P> je SHORT $LN3@InitInstan
P>mov al, BYTE PTR _O$[esp+758]
P> shr al, 2
P> and al, 1
P> je SHORT $LN3@InitInstan
P>
P>Вот зачем он генерирует выделенные строки?
Вполне возможно, чтобы сделать TRUE из подручного хлама. Что там дальше, по метке-то, возвращает 1, которую он уже имеет в регистре, или еще раз засовывает ее в регистр?
Попробуйте вернуть не TRUE, а 148234. Будет он сдвигать и маскировать, или проверкой обойдется?
Re: Тупость компилятора или ограничения стандарта?
Здравствуйте, programmater, Вы писали:
P>Смотрим, что сгенерировал компилятор:
P>
P> test BYTE PTR _O$[esp+756], 2
P> je SHORT $LN3@InitInstan
P>mov al, BYTE PTR _O$[esp+758]
P> shr al, 2
P> and al, 1
P> je SHORT $LN3@InitInstan
P>
P>Вот зачем он генерирует выделенные строки?
Затем, что у него нет bool, а есть некое unsigned value, в котором 1 бит занят под переменную
Он сперва вытаскивает этот бит в такой же unsigned, и только потом кастит получившееся значение к bool
Грубо говоря у компилятора в кодогенераторе есть встроенная функция наподобие:
которую он применяет при чтении битового поля.
После чего он полученное значение кастит в нужный тип:
bool tmpBool = (bool)GetField (2, 1);
Можно было конечно для bool типов сделать в кодогенераторе отдельную оптимизацию, но видимо решили не заморачиваться, сочтя что это не особо народу нужно.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re: Тупость компилятора или ограничения стандарта?