Что быстрее: х << 1 или х << 10? - PullRequest
82 голосов
/ 20 ноября 2010

Я не хочу ничего оптимизировать, клянусь, я просто хочу задать этот вопрос из любопытства. Я знаю, что на большинстве аппаратных средств есть команда сборки bit-shift (например, shl, shr), которая является единственной командой. Но имеет ли значение (с наносекундной или тактовой частотой процессора), сколько бит вы сдвигаете. Другими словами, является ли одно из следующих быстрее на любом процессоре?

x << 1;

и

x << 10;

И, пожалуйста, не ненавидите меня за этот вопрос. :)

Ответы [ 9 ]

83 голосов
/ 20 ноября 2010

Потенциально зависит от ЦП.

Однако все современные ЦП (x86, ARM) используют «переключатель барреля» - аппаратный модуль, специально разработанный для выполнения произвольных сдвигов в постоянное время.

Итак, суть в том ... нет.Без разницы.

62 голосов
/ 20 ноября 2010

Некоторые встроенные процессоры имеют только инструкцию «сдвиг на единицу». На таких процессорах компилятор изменит x << 3 на ((x << 1) << 1) << 1.

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

Intel 8051, который имеет много современных производных, также не может сдвигать произвольное количество бит.

28 голосов
/ 20 ноября 2010

Есть много случаев по этому поводу.

  1. У многих высокоскоростных MPU есть бочкообразная электронная схема, похожая на мультиплексор, которая выполняет любое смещение за постоянное время.

  2. Если MPU имеет только 1-битный сдвиг x << 10, как правило, медленнее, так как в основном это происходит при 10 сменах или копировании байтов с 2 сменами.

  3. Но известен общий случай, когда x << 10 будет даже быстрее , чем x << 1. Если x равен 16 битам, то заботятся только о младших 6 битах (все остальные будут смещены), поэтому MPU нужно загружать только младший байт, что делает только один цикл доступа к 8-битной памяти, тогда как x << 10 нужно два циклы доступа. Если цикл доступа медленнее, чем сдвиг (и очистить младший байт), x << 10 будет быстрее. Это может относиться к микроконтроллерам с быстрым встроенным ПЗУ для программ при доступе к медленным ОЗУ внешних данных.

  4. В дополнение к случаю 3, компилятор может позаботиться о количестве значащих бит в x << 10 и оптимизировать дальнейшие операции для меньших по ширине, например, заменить умножение 16x16 на 16x8 (так как младший байт всегда равен нулю).

Обратите внимание, что некоторые микроконтроллеры вообще не имеют инструкции shift-left, вместо них они используют add x,x.

9 голосов
/ 22 ноября 2010

Вот мой любимый процессор , в котором x<<2 занимает вдвое больше времени, чем x<<1:)

9 голосов
/ 20 ноября 2010

На ARM это может быть сделано как побочный эффект другой инструкции.Так что потенциально ни у одного из них нет задержек.

7 голосов
/ 07 декабря 2010

Возможно, что на 8-битном процессоре x<<1 может быть намного медленнее , чем x<<10 для 16-битного значения.

Например, разумныйперевод x<<1 может быть:

byte1 = (byte1 << 1) | (byte2 >> 7)
byte2 = (byte2 << 1)

, тогда как x<<10 будет более простым:

byte1 = (byte2 << 2)
byte2 = 0

Обратите внимание, как x<<1 смещается чаще и даже дальше, чем x<<10,Кроме того, результат x<<10 не зависит от содержимого байта1.Это может ускорить операцию.

7 голосов
/ 21 ноября 2010

Это зависит как от процессора, так и от компилятора.Даже если базовый ЦП имеет произвольный сдвиг битов со сдвигом бочек, это произойдет только в том случае, если компилятор воспользуется этим ресурсом.

Имейте в виду, что смещение чего-либо за пределы ширины в битах данных "не определено"поведение "в C и C ++.Сдвиг вправо подписанных данных также «определяется реализацией».Вместо того, чтобы слишком беспокоиться о скорости, имейте в виду, что вы получаете один и тот же ответ в разных реализациях.

Цитирование из раздела ANSI C 3.3.7:

3.3.7 Операции побитового сдвига

Синтаксис

      shift-expression:
              additive-expression
              shift-expression <<  additive-expression
              shift-expression >>  additive-expression

Ограничения

Каждый из операндов должен иметь целочисленный тип.

Семантика

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

Результатом E1 << E2 является E1, сдвинутый влево, E2битовые позиции;освобожденные биты заполнены нулями.Если E1 имеет тип без знака, значение результата равно E1, умноженному на величину 2, возведенную в степень E2, уменьшенную по модулю ULONG_MAX + 1, если E1 имеет тип unsigned long, в противном случае UINT_MAX + 1.(Константы ULONG_MAX и UINT_MAX определены в заголовке.) </p>

Результатом E1 >> E2 являются E1-сдвинутые вправо битовые позиции E2.Если E1 имеет тип без знака или E1 имеет тип со знаком и неотрицательное значение, значение результата является неотъемлемой частью отношения E1, деленного на величину 2, возведенную в степень E2.Если E1 имеет тип со знаком и отрицательное значение, результирующее значение определяется реализацией.

Итак:

x = y << z;

"<<": y × 2 <sup>z ( undefined в случае переполнения);

x = y >> z;

">>": определено реализацией для подписи (чаще всего это результатарифметический сдвиг: y / 2 z ).

5 голосов
/ 21 ноября 2010

На некоторых поколениях процессоров Intel (P2 или P3? Не AMD, хотя, если я правильно помню), операции сдвига битов смехотворно медленны. Сдвиг на 1 бит всегда должен быть быстрым, поскольку он может использовать только сложение. Другой вопрос, который следует рассмотреть, заключается в том, быстрее ли сдвиги битов на постоянное число битов, чем сдвиги переменной длины. Даже если коды операций имеют одинаковую скорость, на x86 непостоянный правый операнд смещения битов должен занимать регистр CL, что налагает дополнительные ограничения на распределение регистров и может также замедлять выполнение программы.

3 голосов
/ 08 октября 2017

Как всегда, это зависит от окружающего контекста кода : например, используете ли вы x<<1 в качестве индекса массива?Или добавить это к чему-то еще?В любом случае, небольшое число сдвигов (1 или 2) часто может оптимизировать даже больше, чем если бы компилятору пришлось просто сдвиг.Не говоря уже о полной пропускной способности в сравнении с задержкой и компромиссом между узкими местами.Производительность крошечного фрагмента не одномерна.

Инструкции по аппаратному сдвигу - не единственная опция компилятора для компиляции x<<1, но другие ответы в основном предполагают, что.


x << 1 в точности эквивалентно x+x для беззнаковых и для 2-х чисел со знаком со знаком.Компиляторы всегда знают, на какое оборудование они нацелены, во время компиляции, поэтому они могут воспользоваться такими приемами, как этот.

Вкл. Intel Haswell , add имеет 4 на тактовую частоту,но shl с немедленным счетом имеет только 2 на тактовую пропускную способность.(Таблицы инструкций см. http://agner.org/optimize/ и другие ссылки в вики-теге ).Векторные сдвиги SIMD равны 1 за такт (2 в Skylake), но целочисленные добавления векторов SIMD равны 2 за такт (3 в Skylake).Задержка та же, но: 1 цикл.

Существует также специальная кодировка сдвига на единицу shl, где счетчик подразумевается в коде операции.У 8086 не было смены немедленного счета, только на единицу и на cl регистр.Это в основном относится к сдвигам вправо, потому что вы можете просто добавить сдвиги влево, если вы не сдвигаете операнд памяти.Но если значение понадобится позже, лучше сначала загрузить его в регистр.Но в любом случае shl eax,1 или add eax,eax на один байт короче shl eax,10, и размер кода может напрямую (узкие места декодирования / внешнего интерфейса) или косвенно (пропуски кэша кода L1I) влиять на производительность.

В более общем смысле, небольшие значения сдвига иногда можно оптимизировать в масштабированный индекс в режиме адресации на x86.В настоящее время большинство других широко используемых архитектур - это RISC, и в них нет режимов адресации с масштабируемым индексом, но x86 - достаточно распространенная архитектура, о которой стоит упомянуть.(например, если вы индексируете массив 4-байтовых элементов, есть возможность увеличить масштабный коэффициент на 1 для int arr[]; arr[x<<1]).


Необходимость копирования + сдвига является обычной в ситуациях, когдаоригинальное значение x все еще необходимо.Но большинство целочисленных инструкций x86 работают на месте. (Адресат является одним из источников таких инструкций, как add или shl.) Соглашение о вызовах x86-64 System V передает аргументы в регистрах, спервый аргумент в edi и возвращаемое значение в eax, поэтому функция, которая возвращает x<<10, также заставляет компилятор выдавать код копирования + сдвига.

Инструкция LEA позволяет вам сдвигать-and-add (со счетчиком сдвигов от 0 до 3, потому что он использует машинное кодирование в режиме адресации).Результат помещается в отдельный регистр.

gcc и clang оптимизируют эти функции одинаково, как вы можете видеть в проводнике компилятора Godbolt :

int shl1(int x) { return x<<1; }
    lea     eax, [rdi+rdi]   # 1 cycle latency, 1 uop
    ret

int shl2(int x) { return x<<2; }
    lea     eax, [4*rdi]    # longer encoding: needs a disp32 of 0 because there's no base register, only scaled-index.
    ret

int times5(int x) { return x * 5; }
    lea     eax, [rdi + 4*rdi]
    ret

int shl10(int x) { return x<<10; }
    mov     eax, edi         # 1 uop, 0 or 1 cycle latency
    shl     eax, 10          # 1 uop, 1 cycle latency
    ret

LEA с 2 компонентами имеет задержку в 1 цикл и пропускную способность 2 в такт на современных процессорах Intel и AMD.(Песчаный мост и Бульдозер / Рызен).На Intel это только 1 пропускная способность на тактовую частоту с задержкой 3c для lea eax, [rdi + rsi + 123].(Связано: Почему этот код C ++ быстрее, чем моя рукописная сборка для проверки гипотезы Коллатца? подробно расскажет об этом.)

В любом случае для копирования + сдвига на 10 требуется отдельныйmov инструкция.Это может быть нулевая задержка на многих современных процессорах, но она по-прежнему требует пропускной способности и размера кода.( Может ли MOV x86 действительно быть "свободным"? Почему я вообще не могу воспроизвести это? )

Также связано: Как умножить регистр на 37, используя только 2 последовательныхинструкции в x86? .


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

Например, if(x<<1) { } может использовать and для проверки всех бит, кроме старшего бита.На x86 вы бы использовали инструкцию test, например test eax, 0x7fffffff / jz .false вместо shl eax,1 / jz.Эта оптимизация работает для любого числа смен, и она также работает на машинах, где изменения большого количества медленные (например, Pentium 4) или вообще отсутствуют (некоторые микроконтроллеры).инструкции выходят за рамки простогоНапример, PowerPC содержит много инструкций извлечения / вставки битовых полей.Или ARM имеет сдвиги исходных операндов как часть любой другой инструкции.(Таким образом, инструкции сдвига / поворота - это просто специальная форма move, использующая смещенный источник.)

Помните, C не является языком ассемблера .Всегда обращайте внимание на оптимизированный вывод компилятора, когда вы настраиваете исходный код для эффективной компиляции.

...