Тупость компилятора или ограничения стандарта?
От: programmater  
Дата: 01.09.10 13:16
Оценка:
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, 1
    je    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], 4
    je    SHORT $LN3@InitInstan

Ну? Совсем другое дело! Может же зараза, когда хочет
Но позвольте, ведь при использовании inline функций результаты должны быть эквивалентны! Почему же здесь результаты разные? Ну и собственно вопрос: как заставить компилятор генерировать оптимальный код при использовании функций? Все же хочется писать понятный код, который проще сопровождать (поменять одну функцию проще, чем выискивать во всей программе места, где используется этот кусок кода).

Ухищрения типа

    inline bool GetDrawPoints() const
    {
      if(m_Mask.bShowPoints) {return m_Values.bShowPointIcons != 0;}
      else return false;
    }

или даже

    inline bool GetDrawPoints() const
    {
      return m_Mask.bShowPoints ? m_Values.bShowPointIcons: false;
    }


к желаемому результату не приводят.
Пока пришел к компромиссному варианту, когда вместо 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: Тупость компилятора или ограничения стандарта?
От: Сергей Мухин Россия  
Дата: 01.09.10 13:30
Оценка:
Здравствуйте, programmater, Вы писали:

P>Hi уважаемый All.

P>Вопрос по работе с битовыми флагами. Понимаю, что пытаюсь экономить на спичках, но все же. Как говорится мелочь, а неприятно.
P>Итак, есть два набора битовых флагов (маска и значение), хочется инкапсулировать работу с такой структурой посредством inline функций.

P>Но позвольте, ведь при использовании inline функций результаты должны быть эквивалентны!

результаты выполнения, а не ассемблерный код!

напиши так:

  bool GetDrawPoints() const
    {return m_Mask.bShowPoints && m_Values.bShowPointIcons;}
---
С уважением,
Сергей Мухин
Re: Тупость компилятора или ограничения стандарта?
От: potapov.d  
Дата: 01.09.10 13:35
Оценка: -1
Есть мнение что вы не задали набор процессорных инструкций и компилятор пытается сгенерить код который работал бы даже на 8086.
Например ++i в этом случае будет переведено в add eax,1, а не inc eax.
Чтобы gcc автоматически использовал полный набор инструкций поддерживаемых вашим процессором, добавьте параметр -march=native.
Помнится какой-то из старых мотороловских процессоров как раз имел набор инструкций которые позволяли доставать значение конкретного бита в одну операцию.
Re[2]: Тупость компилятора или ограничения стандарта?
От: Сергей Мухин Россия  
Дата: 01.09.10 13:38
Оценка:
Здравствуйте, potapov.d, Вы писали:

PD>Есть мнение что вы не задали набор процессорных инструкций и компилятор пытается сгенерить код который работал бы даже на 8086.

PD>Например ++i в этом случае будет переведено в add eax,1, а не inc eax.

и это верно, т.к. add везде рекомендуют вместо inc!!!

PD>Чтобы gcc автоматически использовал полный набор инструкций поддерживаемых вашим процессором, добавьте параметр -march=native.

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

процессоров много, чем нам поможет знание, что существует такие? в интеле есть BT и тп например
---
С уважением,
Сергей Мухин
Re[3]: Тупость компилятора или ограничения стандарта?
От: potapov.d  
Дата: 01.09.10 13:48
Оценка:
Здравствуйте, Сергей Мухин, Вы писали:
СМ>и это верно, т.к. add везде рекомендуют вместо inc!!!
И чем это верно кроме того что "рекомендуют"?
add — это три байта инструкций, а inc — один, количества тиков на выполнение тоже соответствующие.

СМ>процессоров много, чем нам поможет знание, что существует такие? в интеле есть BT и тп например

Человек спросил как сделать так чтобы значение битового поля доставалось наиболее эффективно, я ответил. Ещё вопросы?
Re[4]: Тупость компилятора или ограничения стандарта?
От: Сергей Мухин Россия  
Дата: 01.09.10 13:52
Оценка:
Здравствуйте, potapov.d, Вы писали:

PD>Здравствуйте, Сергей Мухин, Вы писали:

СМ>>и это верно, т.к. add везде рекомендуют вместо inc!!!
PD>И чем это верно кроме того что "рекомендуют"?
PD>add — это три байта инструкций, а inc — один, количества тиков на выполнение тоже соответствующие.

1. он оди байт только в 32битной интел
2. почитайте сначала. тиков то одинаково, а выполнение медленней, из-за многопоточного выполнения имхо.

СМ>>процессоров много, чем нам поможет знание, что существует такие? в интеле есть BT и тп например

PD>Человек спросил как сделать так чтобы значение битового поля доставалось наиболее эффективно, я ответил. Ещё вопросы?

вы ответили про какой-то мотороловский процессор!!! который никаког отношения к обсужлаемой теме не имеет!
---
С уважением,
Сергей Мухин
Re[2]: Тупость компилятора или ограничения стандарта?
От: programmater  
Дата: 01.09.10 14:05
Оценка:
Здравствуйте, Сергей Мухин, Вы писали:

СМ>Здравствуйте, programmater, Вы писали:


СМ>напиши так:


СМ>
СМ>  bool GetDrawPoints() const
СМ>    {return m_Mask.bShowPoints && m_Values.bShowPointIcons;}

СМ>


Именно! Этот ответ я нашел сам в процессе эксперимента (уже после того, как отослал вопрос).
Более, если для доступа к отдельному флагу писать не
inline bool GetSomeFlag() const {return m_Flags.bSomeFlag;}

которое работает неэффективно, а
inline bool GetSomeFlag() const 
{
  if(m_Flags.bSomeFlag) {return true;}
  else return false;
}

то сгенерированный код будет более эффективным. Воистину пути компилятора неисповедимы , и иногда более страшно выглядящий фрагмент программы на С++ разворачивается в более эффективный код.
Всем спасибо.
ЗЫ. набор инструкций тут не при чем. Иначе бы он и при развороте тела функции непосредственно в оператор условия тоже генерировал бы неэффективный код. Компилятор — 2005 студия (которая по моему меньше чем для пентиума и генерировать не умеет).
Re[5]: Тупость компилятора или ограничения стандарта?
От: potapov.d  
Дата: 01.09.10 14:32
Оценка: -2
Здравствуйте, Сергей Мухин, Вы писали:

СМ>1. он оди байт только в 32битной интел

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

СМ>вы ответили про какой-то мотороловский процессор!!! который никаког отношения к обсужлаемой теме не имеет!

Да засунь уже свои восклицательные знаки себе в задницу.
Re[4]: Тупость компилятора или ограничения стандарта?
От: CreatorCray  
Дата: 01.09.10 15:15
Оценка:
Здравствуйте, 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) для сохранения значения флага, чтобы избежать задержки.

Примеры:

INC EAX / PUSHFD ; задержка
ADD EAX,1 / PUSHFD ; нет задержки

SHR EAX,1 / PUSHFD ; задержка
SHR EAX,1 / OR EAX,EAX / PUSHFD ; нет задержки

TEST EBX,EBX / LAHF ; задержка
AND EBX,EBX / LAHF ; нет задержки
TEST EBX,EBX / SETZ AL ; нет задержки

CLC / SETZ AL ; задержка
CLD / SETZ AL ; нет задержки

Потери при частичной задержки флагов примерно равны 4 тактам.


Грубо говоря если у нас за INC стоит инструкция, которая обращается к флагам то мы можем на ровном месте получить тормоза.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re[5]: Тупость компилятора или ограничения стандарта?
От: potapov.d  
Дата: 01.09.10 15:22
Оценка:
Здравствуйте, CreatorCray, Вы писали:

CC>Здравствуйте, potapov.d, Вы писали:


СМ>>>и это верно, т.к. add везде рекомендуют вместо inc!!!

PD>>И чем это верно кроме того что "рекомендуют"?
PD>>add — это три байта инструкций, а inc — один, количества тиков на выполнение тоже соответствующие.
CC>Add — устанавливает все флаги
CC>Inc — не меняет флаг CF, в результате на современных процах выполняется медленее, т.к. вызывает задержку регистра флагов и притормаживает исполнение (которое в современных процах отнюдь не последовательное).

Спасибо. Теперь понимаю.
Re[6]: Тупость компилятора или ограничения стандарта?
От: Сергей Мухин Россия  
Дата: 01.09.10 17:45
Оценка: +1
Здравствуйте, potapov.d, Вы писали:

>> код который работал бы даже на 8086 ... будет переведено в add eax,1, а не inc eax.

кстати, ваш пример, крайне не удачен, т.к. inc eax (естественно регистр 8битный) был еще (если мне память не изменяет) на 8битном 8008 !
---
С уважением,
Сергей Мухин
Re: Тупость компилятора или ограничения стандарта?
От: Pzz Россия https://github.com/alexpevzner
Дата: 01.09.10 19:51
Оценка:
Здравствуйте, programmater, Вы писали:

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>Вот зачем он генерирует выделенные строки?

Вполне возможно, чтобы сделать TRUE из подручного хлама. Что там дальше, по метке-то, возвращает 1, которую он уже имеет в регистре, или еще раз засовывает ее в регистр?

Попробуйте вернуть не TRUE, а 148234. Будет он сдвигать и маскировать, или проверкой обойдется?
Re: Тупость компилятора или ограничения стандарта?
От: CreatorCray  
Дата: 01.09.10 21:12
Оценка:
Здравствуйте, 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

Грубо говоря у компилятора в кодогенераторе есть встроенная функция наподобие:
unsigned тип field;

decltype (field) GetField (uint bitOffset, uint bitMask)
{
    return (field >> bitOffset) & bitMask;
}

которую он применяет при чтении битового поля.
После чего он полученное значение кастит в нужный тип:
bool tmpBool = (bool)GetField (2, 1);


Можно было конечно для bool типов сделать в кодогенераторе отдельную оптимизацию, но видимо решили не заморачиваться, сочтя что это не особо народу нужно.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re: Тупость компилятора или ограничения стандарта?
От: SpaceConscience  
Дата: 01.09.10 22:00
Оценка:
Ну, очевидно, при приведении к bool, компилятор всегда делает 1 или 0. Там даже ворнинг есть на эту тему ("forcing value to bool true or false").

А инлайнинг — да, в MSVC отстойный. Он не дает оптимальных результатов.

Как победить? Ну, может быть, вместо bool возвращать int (или BOOL), подразумевая, что ненулевой int — это true.
Собрался ставить минус? Да сам иди в жопу!

































































.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.