Какой из следующих методов является лучшим вариантом для деления целого числа на 2 и почему?
Техника 1:
x = x >> 1;
Техника 2:
x = x / 2;
Вот x
целое число.
c++
c
optimization
division
micro-optimization
Абхинит
источник
источник
x
снова, ни один из них не подходит таким образом: он должен быть либо,x >>= 1
либо вx /= 2
зависимости от того, что вы намерены выразить с помощью операции. Не потому, что это быстрее (любой современный компилятор все равно скомпилирует все эквивалентные варианты в одинаковую, быструю сборку), а потому, что это менее запутанно.x = x >>> 1
. Также обратите внимание, что в зависимости от платформы и компилятора может быть разумно вручную оптимизировать деления и умножения, используя сдвиги. - Думая о микроконтроллерах, например, без прямой поддержки ALU для умножения.x /= 2
потому чтоx >>= 1
выглядит слишком похоже на монадное связывание;)x = x / 2
вместоx /= 2
. Субъективное предпочтение может быть :)⬜=
комбинации, их следует использовать, когда это возможно. Он устраняет шум и акцентирует внимание на том факте, чтоx
он изменен , в то время как общий=
оператор скорее предполагает, что он принимает совершенно новое значение, независимое от старого. - Всегда избегать комбинированных операторов (так что это читаемое так , кто знает только математические оператор) может иметь свою точку , а также, но тогда вы должны были бы отказаться от крайне полезных++
,--
,+=
тоже.Ответы:
Используйте операцию, которая лучше всего описывает то, что вы пытаетесь сделать.
Обратите внимание, что они не совсем эквивалентны. Они могут давать разные результаты для отрицательных целых чисел. Например:
(Ideone)
источник
%
и/
операторы должны быть согласованы для положительных и отрицательных операндов так , что(a/b)*b+(a%b)==a
справедливо независимо от признаковa
иb
. Обычно автор делает выбор, который позволяет добиться максимальной производительности процессора.Первый выглядит как деление? Нет. Если вы хотите разделить, используйте
x / 2
. Компилятор может оптимизировать его для использования битового сдвига, если это возможно (это называется снижением прочности), что делает его бесполезной микрооптимизацией, если вы делаете это самостоятельно.источник
Скопировать: есть так много причин, чтобы использовать
x = x / 2;
Вот некоторые из них:он выражает ваше намерение более четко (при условии, что вы не имеете дело с битами регистра сдвоенного твида или чем-то еще)
компилятор все равно сведет это к операции сдвига
даже если компилятор не уменьшил ее и выбрал более медленную операцию, чем сдвиг, вероятность того, что это в конечном итоге повлияет на производительность вашей программы измеримым образом, сама по себе исчезающе мала (и если она действительно изменится, то у вас есть реальная причина использовать смену)
если деление будет частью более крупного выражения, вы с большей вероятностью получите правильный приоритет, если будете использовать оператор деления:
арифметика со знаком может усложнить ситуацию даже больше, чем проблема старшинства, упомянутая выше
повторить - компилятор уже сделает это за вас в любом случае. Фактически, он преобразует деление на константу в серию сдвигов, добавлений и умножений для всех видов чисел, а не только для степеней двух. Смотрите этот вопрос для ссылки на еще больше информации об этом.
Короче говоря, вы ничего не покупаете, кодируя сдвиг, когда вы действительно хотите умножить или разделить, за исключением, возможно, повышенной вероятности внесения ошибки. Это была целая жизнь, так как компиляторы не были достаточно умны, чтобы оптимизировать такие вещи, когда это необходимо.
источник
a/b/c*d
(гдеa..d
обозначены числовые переменные) вместо гораздо более читабельного(a*d)/(b*c)
.a*d
илиb*c
произойдет переполнение, менее читаемая форма не эквивалентна и имеет очевидное преимущество. PS Я согласен, что скобки - твой лучший друг.a/b/c*d
с кодом R - в контексте, где переполнение означало бы, что с данными что-то серьезно не так, а не, скажем, в критичном для производительности блоке кода C).x=x/2;
только «более понятен», чемx>>=1
если быx
он никогда не был нечетным отрицательным числом, или не заботятся об ошибках. В противном случаеx=x/2;
иx>>=1;
имеют разные значения. Если нужно вычислить значениеx>>=1
, я бы посчитал это более понятным, чемx = (x & ~1)/2
илиx = (x < 0) ? (x-1)/2 : x/2
, или любую другую формулировку, которую я могу придумать, используя деление на два. Точно так же, если нужно вычислить значениеx/=2
, это будет яснее, чем((x + ((unsigned)x>>31)>>1)
.Зависит от того, что вы подразумеваете под лучшим .
Если вы хотите, чтобы ваши коллеги ненавидели вас или сделали ваш код трудным для чтения, я бы определенно выбрал первый вариант.
Если вы хотите разделить число на 2, переходите ко второму.
Два не эквивалентны, они не ведут себя одинаково, если число отрицательное или внутри больших выражений - битное смещение имеет меньший приоритет, чем
+
или-
, деление имеет более высокий приоритет.Вы должны написать свой код, чтобы выразить его намерения. Если вам важна производительность, оптимизатор хорошо справляется с подобной микрооптимизацией.
источник
Просто используйте разделяемую (
/
), предполагая, что это понятнее. Компилятор будет оптимизировать соответственно.источник
ASSUME(x >= 0); x /= 2;
overx >>= 1;
, но это все еще важный момент для обсуждения.Я согласен с другими ответами, которые вы должны одобрить,
x / 2
потому что его цель более ясна, и компилятор должен оптимизировать его для вас.Тем не менее, еще одна причина предпочесть
x / 2
болееx >> 1
, что поведение>>
зависит от реализации , еслиx
это знаковоеint
и отрицательный.Из раздела 6.5.7, пул 5 стандарта ISO C99:
источник
x>>scalepower
отрицательных чисел, будет именно тем, что необходимо при делении значения на степень два для таких целей, как рендеринг экрана, при этом использованиеx/scalefactor
будет неправильным, если не применить поправки к отрицательным значениям.x / 2
понятнее иx >> 1
не намного быстрее (согласно микропроцессору, примерно на 30% быстрее для Java JVM). Как уже отмечали другие, для отрицательных чисел округление немного отличается, поэтому вы должны учитывать это, когда хотите обработать отрицательные числа. Некоторые компиляторы могут автоматически конвертироватьx / 2
в,x >> 1
если они знают, что число не может быть отрицательным (даже подумал, что я не смог это проверить).Даже
x / 2
может не использовать (медленно) инструкцию процессора деления, потому что некоторые сочетания клавиш возможны , но это все же медленнее, чемx >> 1
.(Это C / C ++ вопрос, другие языки программирования имеют больше операторов. Для Java есть также сдвиг вправо без знака,
x >>> 1
, что опять - таки отличается. Это позволяет правильно рассчитать среднее (среднее) значение из двух значений, так что(a + b) >>> 1
волей возвращает среднее значение даже для очень больших значенийa
иb
. Это требуется, например, для двоичного поиска, если индексы массива могут стать очень большими. Во многих версиях двоичного поиска была ошибка , поскольку они использовали(a + b) / 2
для вычисления среднего. не работает правильно. Правильное решение использовать(a + b) >>> 1
вместо.)источник
x/2
вx>>1
в тех случаях , когдаx
может быть отрицательным. Если тоx>>1
, что нужно, это значение, которое будет вычислено, это почти наверняка будет быстрее, чем любое выражение,x/2
которое вычисляет то же значение.x/2
вx>>1
если он знает , что значение не является отрицательным. Я постараюсь обновить свой ответ.div
инструкции путем преобразованияx/2
в(x + (x<0?1:0)) >> 1
(где >> - арифметическое смещение вправо, которое сдвигается в знаковых битах). Для этого нужно 4 инструкции: скопировать значение, shr (чтобы получить только знаковый бит в регистре), добавить, sar. goo.gl/4F8Ms4Кнут сказал:
Поэтому я предлагаю использовать
x /= 2;
Таким образом, код легко понять, и я думаю, что оптимизация этой операции в такой форме не означает большой разницы для процессора.
источник
(n+8)>>4
прекрасно работает. Можете ли вы предложить какой-либо подход, который был бы столь же ясным или эффективным без использования подписанного сдвига вправо?Посмотрите на вывод компилятора, чтобы помочь вам принять решение. Я запустил этот тест на x86-64 с
gcc (GCC) 4.2.1 20070719 [FreeBSD]
Также смотрите результаты компиляции онлайн на godbolt .
То, что вы видите, это то, что компилятор использует
sarl
(арифметическое смещение вправо) инструкцию в обоих случаях, поэтому он распознает сходство между двумя выражениями. Если вы используете деление, компилятор также должен скорректировать отрицательные числа. Для этого он сдвигает знаковый бит до младшего разряда и добавляет его к результату. Это устраняет проблему смещений на единицу при смещении отрицательных чисел по сравнению с тем, что будет делать деление.Поскольку в случае деления выполняется 2 смены, а в явном случае смен - только одна, мы теперь можем объяснить некоторые различия в производительности, измеренные другими ответами.
C-код с выводом сборки:
Для деления ваш вклад будет
и это компилируется в
аналогично для смены
с выходом:
источник
d
. Такое разбиение полезно для многих целей. Даже если вы бы предпочли иметь точку останова где-то, отличную от 0 до -1, добавление смещения переместит ее. Целочисленное деление, которое удовлетворяет второй аксиоме, разделит числовую линию на области, которые в основном имеют размерd
, но одна из которых имеет размер2*d-1
. Не совсем "равные" подразделения. Можете ли вы предложить предложения о том, когда раздел чудаков на самом деле полезен?sar
). goo.gl/KRgIkb . Этот пост списка рассылки ( gcc.gnu.org/ml/gcc/2000-04/msg00152.html ) подтверждает, что gcc исторически использует арифметические сдвиги для подписанных целых, поэтому маловероятно, что FreeBSD gcc 4.2.1 использовала беззнаковое смещение. Я обновил ваш пост, чтобы исправить это, и в начале параграфа говорилось, что оба использовали shr, когда они фактически используют SAR. SHR - это то, как он извлекает знаковый бит для/
случая. Также включена ссылка Godbolt.Просто добавленная заметка -
x * = 0.5 часто будет быстрее в некоторых языках на основе виртуальных машин - особенно в ActionScript, так как переменную не нужно проверять на деление на 0.
источник
eval
) сделали это каждый раз заново. В любом случае, да, это довольно плохой тест, потому что это очень глупая оптимизация.Используйте
x = x / 2;
ИЛИx /= 2;
Потому что возможно, что новый программист будет работать над этим в будущем. Так что ему будет легче узнать, что происходит в строке кода. Каждый может не знать о такой оптимизации.источник
Я говорю для целей соревнований по программированию. Как правило, они имеют очень большие входные данные, где деление на 2 происходит много раз, и известно, что входные данные являются положительными или отрицательными.
х >> 1 будет лучше, чем х / 2. Я проверил на ideone.com, запустив программу, в которой было выполнено более 10 ^ 10 делений на 2 операции. Для x / 2 потребовалось около 5,5 с, тогда как для х >> 1 потребовалось около 2,6 с для той же программы.
источник
x/2
доx>>1
. Для значений со знаком почти все реализации определяютx>>1
значение, которое эквивалентно,x/2
но может быть вычислено быстрее, когдаx
положительно, и полезно отличается от того,x/2
когдаx
отрицательно.Я бы сказал, что есть несколько вещей, которые следует учитывать.
Сдвиг битов должен быть быстрее, поскольку для сдвига битов действительно не требуется специальных вычислений, однако, как указывалось, существуют потенциальные проблемы с отрицательными числами. Если вы уверены, что у вас положительные числа, и вы ищете скорость, я бы порекомендовал bithift.
Оператор деления очень легко читается людьми. Так что если вы ищете читабельность кода, вы можете использовать это. Обратите внимание, что область оптимизации компилятора прошла долгий путь, поэтому удобство чтения и понимания кода является хорошей практикой.
Если вам нужна чистая производительность, я бы порекомендовал создать несколько тестов, которые могли бы выполнять операции миллионы раз. Попробуйте выполнить несколько раз (ваш размер выборки), чтобы определить, какой из них является статистически наилучшим для вашей ОС / Аппаратное обеспечение / Компилятор / Код.
источник
>>
делает, а не тому, что/
делает.Что касается процессора, операции сдвига битов выполняются быстрее, чем операции деления. Тем не менее, компилятор знает это и будет соответствующим образом оптимизировать, насколько это возможно, так что вы можете писать код так, как вам удобно, и быть спокойным, зная, что ваш код работает эффективно. Но помните, что
unsigned int
может (в некоторых случаях) быть оптимизирован лучше, чемint
по причинам, указанным ранее. Если вам не нужна арифметика со знаком, то не включайте бит знака.источник
х = х / 2; это подходящий код для использования ... но работа зависит от вашей собственной программы, как вывод, который вы хотели произвести.
источник
Сделайте ваши намерения более понятными ... например, если вы хотите разделить, используйте x / 2 и позвольте компилятору оптимизировать его для оператора сдвига (или чего-либо еще).
Современные процессоры не позволят этим оптимизациям повлиять на производительность ваших программ.
источник
Ответ на этот вопрос будет зависеть от среды, в которой вы работаете.
x /= 2
вx >>= 1
, присутствие символа деления вызовет больше удивлений в этой среде, чем используя сдвиг, чтобы произвести разделение.x >>= 1
комментарий с пояснением его аргументации, вероятно, лучше всего использовать для ясности цели.x /= 2
. Лучше сэкономить следующему программисту, который смотрит на ваш код, 10-секундную двойную проверку вашей смены, чем без необходимости доказывать, что вы знали, что сдвиг был более эффективным без оптимизации компилятора.Все они предполагают целые числа без знака. Простой переход, вероятно, не то, что вы хотите подписать. Кроме того, DanielH хорошо описывает использование
x *= 0.5
некоторых языков, таких как ActionScript.источник
мод 2, тест для = 1. не знаю синтаксис в с. но это может быть самым быстрым.
источник
В целом правильное смещение делит:
иногда это используется для ускорения программ за счет ясности. Я не думаю, что вы должны это делать. Компилятор достаточно умен, чтобы выполнить ускорение автоматически. Это означает, что внесение смены ничего не даст вам за счет ясности .
Взгляните на эту страницу из Практического программирования на C ++.
источник
(x+128)>>8
, вычислило бы значения,x
не близкие к максимальному, как можно было бы сделать это кратко без сдвига? Обратите внимание, что(x+128)/256
это не сработает. Вы знаете какое-нибудь хорошее выражение, которое будет?Очевидно, что если вы пишете свой код для следующего парня, который его читает, перейдите к ясности "x / 2".
Однако, если ваша цель - скорость, попробуйте оба способа и время результаты.Несколько месяцев назад я работал над процедурой свертки растровых изображений, которая включала пошаговое выполнение массива целых чисел и деление каждого элемента на 2. Я сделал все, чтобы оптимизировать его, включая старый трюк с заменой «x >> 1» на «x». / 2" .
Когда я на самом деле рассчитал оба пути, я с удивлением обнаружил, что x / 2 был быстрее, чем x >> 1
Это было с использованием Microsoft VS2008 C ++ с включенными оптимизациями по умолчанию.
источник
С точки зрения производительности. Операции сдвига процессора выполняются значительно быстрее, чем деление кодов операций. Таким образом, деление на два или умножение на 2 и т. Д. Приносит пользу от сменных операций.
Что касается внешнего вида. Как инженеры, когда мы стали настолько привязаны к косметике, что даже красавицы не пользуются! :)
источник
X / Y - правильный оператор смещения ... и ">>". Если мы хотим разделить целое число на два, мы можем использовать оператор деления (/). оператор сдвига используется для сдвига битов.
х = х / 2; х / = 2; мы можем использовать как это ..
источник
Хотя x >> 1 быстрее, чем x / 2, правильное использование >> при работе с отрицательными значениями немного сложнее. Это требует чего-то похожего на следующее:
источник