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

В особых случаях: есть & быстрее, чем%?

Я увидел выбранный ответ на этот пост.

Я был удивлен, что (x & 255) == (x % 256), если x - целое число без знака, я задавался вопросом, имеет ли смысл всегда заменять % на & в x % n для n = 2^a (a = [1, ...]), а x - положительное целое число.

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

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

4b9b3361

Ответ 1

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

Эта программа:

int mod_signed(int i) {
  return i % 256;
}
int and_signed(int i) {
  return i & 255;
}
unsigned mod_unsigned(unsigned int i) {
  return i % 256;
}
unsigned and_unsigned(unsigned int i) {
  return i & 255;
}

будет скомпилирован (от GCC 6.2 с -O3; Clang 3.9 производит очень похожий код) в:

mod_signed(int):
        mov     edx, edi
        sar     edx, 31
        shr     edx, 24
        lea     eax, [rdi+rdx]
        movzx   eax, al
        sub     eax, edx
        ret
and_signed(int):
        movzx   eax, dil
        ret
mod_unsigned(unsigned int):
        movzx   eax, dil
        ret
and_unsigned(unsigned int):
        movzx   eax, dil
        ret

Результат сборки mod_signed отличается от

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

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

Следовательно, mod_signed оптимизирован для (из nwellnhof комментария):

int d = i < 0 ? 255 : 0;
return ((i + d) & 255) - d;

Логически, мы можем доказать, что i % 256 == i & 255 для всех целых без знака, поэтому мы можем доверять компилятору выполнять его работу.

Ответ 2

Я сделал некоторые измерения с помощью gcc, и если аргумент a / или % является скомпилированной константой времени, мощность 2, gcc может превратить его в соответствующую операцию бит.

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

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