Существующие ответы на самом деле не касались аппаратной стороны, так что здесь немного об этом. Общепринятое мнение состоит в том, что умножение и деление намного медленнее, чем сдвиг, но фактическая история сегодня более нюансирована.
Например, верно, что умножение является более сложной операцией для реализации на аппаратном уровне, но это не всегда приводит к замедлению . Как оказалось, add
также значительно сложнее в реализации, чем xor
(или вообще любая битовая операция), но add
(и sub
) обычно получают достаточно транзисторов, предназначенных для их работы, которые в итоге оказываются такими же быстрыми, как битовые операторы. Таким образом, вы не можете просто рассматривать сложность аппаратной реализации как руководство по скорости.
Итак, давайте подробно рассмотрим сдвиг по сравнению с «полными» операторами, такими как умножение и сдвиг.
перевод
Почти на всех аппаратных средствах смещение на постоянную величину (т. Е. На величину, которую компилятор может определить во время компиляции) происходит быстро . В частности, это обычно происходит с задержкой одного цикла и с пропускной способностью 1 за цикл или лучше. На некоторых аппаратных средствах (например, на некоторых микросхемах Intel и ARM) некоторые сдвиги на константу могут даже быть «свободными», поскольку они могут быть встроены в другую инструкцию ( lea
на Intel - специальные возможности сдвига первого источника в ARM).
Сдвиг на переменную величину - больше серой области. На старом оборудовании это иногда было очень медленно, и скорость менялась от поколения к поколению. Например, в начальном выпуске Intel P4 переключение на переменную величину было общеизвестно медленным - требовало времени, пропорционального величине смены! На этой платформе использование умножений для замены смен может быть выгодным (т. Е. Мир перевернулся). На предыдущих чипах Intel, а также на последующих поколениях переключение на переменную величину не было столь болезненным.
На современных чипах Intel сдвиг на переменную величину не особенно быстрый, но и не страшный. Архитектура x86 затруднена, когда дело доходит до переменных сдвигов, потому что они определили операцию необычным образом: значения сдвигов, равные 0, не изменяют флаги условия, но все другие сдвиги делают. Это препятствует эффективному переименованию регистра флагов, так как это не может быть определено, пока сдвиг не выполнит, должны ли последующие инструкции читать коды условий, записанные сдвигом, или некоторую предыдущую инструкцию. Кроме того, сдвиги записывают только часть регистра флагов, что может привести к частичной остановке флагов.
В результате на последних архитектурах Intel сдвиг на переменную величину занимает три «микрооперации», в то время как большинство других простых операций (сложение, побитовые операции, даже умножение) занимают только 1. Такие сдвиги могут выполняться не чаще, чем раз в 2 цикла. ,
умножение
Тенденция в современном оборудовании для настольных компьютеров и ноутбуков - сделать умножение быстрой операцией. На последних чипах Intel и AMD фактически каждое цикл может выполняться по одному умножению (мы называем это обратной пропускной способностью ). Латентность , однако, умножения 3 циклов. Таким образом, это означает, что вы получаете результат любого заданного умножения 3 цикла после его запуска, но вы можете начинать новое умножение каждый цикл. Какое значение (1 цикл или 3 цикла) является более важным, зависит от структуры вашего алгоритма. Если умножение является частью цепочки критических зависимостей, важна задержка. Если нет, взаимная пропускная способность или другие факторы могут быть более важными.
Ключевым моментом для них является то, что на современных чипах для ноутбуков (или лучше) умножение - это быстрая операция, и, вероятно, она будет быстрее, чем последовательность команд из 3 или 4, которую компилятор выдаст, чтобы «получить округление» для снижения нагрузки. Для переменных смещений в Intel, как правило, также предпочтительнее умножение из-за вышеупомянутых проблем.
На меньших платформах форм-фактора умножение может все еще быть медленным, так как создание полного и быстрого 32-разрядного или особенно 64-разрядного умножителя требует много транзисторов и мощности. Если кто-то может заполнить подробности о производительности умножения на последних мобильных чипах, это будет очень цениться.
Делить
Разделение - это более сложная операция с аппаратной точки зрения, чем умножение, и оно также гораздо реже встречается в реальном коде - это означает, что для него, вероятно, выделено меньше ресурсов. Тенденция в современных чипах по-прежнему направлена на более быстрые делители, но даже современные топовые чипы занимают 10-40 циклов, чтобы разделить, и они только частично конвейерны. В целом, 64-битные деления даже медленнее, чем 32-битные. В отличие от большинства других операций, деление может занять разное количество циклов в зависимости от аргументов.
Избегайте делений и заменяйте их на сдвиги (или пусть компилятор сделает это, но вам может понадобиться проверить сборку), если можете!