Проверка во время компиляции, является ли сдвиг вправо арифметическим на подписанных типах - PullRequest
5 голосов
/ 26 февраля 2011

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

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

Читая тему Проверка того, что C / C ++ смещение вправо со знаком является арифметикой для определенного компилятора? Я пришел к идее инициализировать флаг

static const bool is_arithmetic_rs = (((signed int)-1)>>1) == ((signed int)-1));

и проверить его во время выполнения следующим образом:

if (is_arithmetic_rs) {
  // some fast algorithm using arithmetic right shifts (using >> operator)
} else {
  // the same algorithm without arithmetic right shifts (much slower)
}

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

Если не существует переносимого способа сделать эту проверку, есть ли способ сделать это, проверив на основе максимальных усилий, например, проверяя с помощью ifdefs для определенного компилятора / платформы?

Ответы [ 7 ]

8 голосов
/ 26 февраля 2011

Лучший способ выполнить такие проверки - например, GNU autotools do:

  • Скомпилировать небольшую программу на целевой платформе и проверить, что происходит

  • Установите соответствующий #define в заголовочном файле

  • Включите этот заголовочный файл в свои исходные файлы

  • Опционально, используйте правильно определенные макросы, чтобы не загромождать ваш код директивами #ifdef для каждой мелочи.

  • Скомпилируйте ваш основной проект

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

Возможно, вам следуетвзгляните на широко используемые системы сборки, такие как GNU autotools или CMake , чтобы повторно использовать существующие макросы и информацию, относящуюся к платформе, и избежать необходимости создавать свои собственные и, следовательно, заново изобретать колесо.

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

7 голосов
/ 26 февраля 2011

Ветвления можно избежать с помощью теста времени предварительной обработки

#if ((-1)>>1) == (-1))
...
#else
...
#endif
4 голосов
/ 09 марта 2012

Действительно больше комментариев, чем ответов (но, видимо, я не заслуживаю уважения)

В нескольких ответах здесь используются проверки перед обработкой, такие как

#if ((-1)>>1) == (-1))

Лично я бы не сталне доверяйте препроцессору сообщать мне, какой код генерирует компилятор.

3 голосов
/ 26 февраля 2011

Вы действительно убедились, что ваш компилятор не оптимизирует деление на арифметическое смещение, когда оно доступно?

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

template <bool B>
void do_work();

template <>
void do_work<true>()
{
    // Do stuff with H/W.
}

template <>
void do_work<false>()
{
    // Do slow stuff with S/W.
}

do_work<(-2 >> 1) == -1>();

Тогда сделайте его более привлекательным дляиспользовать со встроенной функцией:

inline real_do_work()
{
    do_work<(-2 >> 1) == -1>();
}
1 голос
/ 26 февраля 2011

Вдохновленный Джузеппе и R .. ответы:

#if -2 >> 1 == -1
#define rshift_ar(X, Y) ((X) >> (Y))
#elif ~(~(-2) >> 1) == -1
#define rshift_ar(X, Y) ((X) >= 0 ? (X) >> (Y) : ~(~(X) >> (Y)))
#else
#error "unsupported shifting semantics"
#endif
1 голос
/ 26 февраля 2011

Попробуйте это:

#define SAR(x,y) ((x)>=0) ? ((x)>>(y)) : (~(~(x)>>(y)))

Хороший компилятор оптимизирует его до ((x)>>(y)), если процессор нормальный.

Приветствуются отзывы о том, какие компиляторы хороши.

0 голосов
/ 14 сентября 2012

Вся эта магия препроцессора бесполезна с любым приличным компилятором. Вы убедились, что ваш код, приведенный выше, действительно генерирует ветвь в своем выводе? Я очень сомневаюсь в этом, так как static const bool can может быть оценен как постоянная времени компиляции, поэтому false -часть вашего if (is_arithmetic_rs) будет исключена компилятором, со значением выше -O1 Смотрите также мой ответ здесь .

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

...