it-swarm.com.ru

Действительно ли умножение и деление с использованием операторов сдвига в C быстрее?

Умножение и деление может быть достигнуто с помощью битовых операторов, например

i*2 = i<<1
i*3 = (i<<1) + i;
i*10 = (i<<3) + (i<<1)

и так далее.

Действительно ли быстрее использовать скажем (i<<3)+(i<<1) для умножения на 10, чем непосредственно i*10? Есть ли какие-либо входные данные, которые не могут быть умножены или разделены таким образом?

271
eku

Краткий ответ: маловероятно.

Длинный ответ: в вашем компиляторе есть оптимизатор, который знает, как умножать настолько быстро, насколько позволяет ваша целевая архитектура процессора. Лучше всего четко сообщить компилятору о своем намерении (т.е. i * 2, а не i << 1) и позволить ему решить, какая последовательность сборки сборки/машинного кода является самой быстрой. Возможно даже, что сам процессор реализовал инструкцию умножения в виде последовательности сдвигов и добавлений в микрокоде.

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

454
Drew Hall

Просто конкретная точка измерения: много лет назад я проверил две версии моего алгоритма хеширования:

unsigned
hash( char const* s )
{
    unsigned h = 0;
    while ( *s != '\0' ) {
        h = 127 * h + (unsigned char)*s;
        ++ s;
    }
    return h;
}

а также

unsigned
hash( char const* s )
{
    unsigned h = 0;
    while ( *s != '\0' ) {
        h = (h << 7) - h + (unsigned char)*s;
        ++ s;
    }
    return h;
}

На каждой машине, на которой я тестировал, первая была, по крайней мере, так же быстро, как и вторая. Несколько удивительно, но иногда это было быстрее (например, на Sun Sparc). Когда аппаратное обеспечение не поддерживало быстрое умножение (и большинство тогда не делало), компилятор преобразовывал умножение в соответствующие комбинации сдвигов и добавления/саб. И так как он знал конечную цель, он мог иногда делать это в меньшем количестве инструкций, чем когда вы явно писали сдвиги и добавления/сабы.

Обратите внимание, что это было что-то вроде 15 лет назад. Надеемся, что с тех пор компиляторы стали лучше, так что вы можете в значительной степени рассчитывать на то, что компилятор поступит правильно, возможно, лучше, чем вы. (Кроме того, причина, по которой код выглядит так C'ish, в том, что он был более 15 лет назад. Я бы, очевидно, использовал сегодня std::string и итераторы.)

90
James Kanze

В дополнение ко всем другим хорошим ответам здесь, позвольте мне указать еще одну причину не использовать сдвиг, когда вы имеете в виду делить или умножать. Я никогда не видел, чтобы кто-то вводил ошибку, забывая об относительном приоритете умножения и сложения. Я видел ошибки, возникающие, когда программисты по обслуживанию забыли, что "умножение" с помощью сдвига логически умножение, но не синтаксически того же приоритета, что и умножение. x * 2 + z и x << 1 + z очень разные!

Если вы работаете с числа, используйте арифметические операторы, такие как + - * / %. Если вы работаете с массивами битов, используйте операторы битового переворота, такие как & ^ | >>. Не смешивайте их; Выражение, в котором есть как немного сложное, так и арифметическое, является ошибкой, ожидающей своего появления.

62
Eric Lippert

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

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

48
Jens

Действительно ли быстрее использовать say (i << 3) + (i << 1) для умножения на 10, чем напрямую использовать i * 10?

Это может быть или не быть на вашем компьютере - если вам все равно, измерьте в реальных условиях использования.

Тематическое исследование - от 486 до Core i7

Сравнительный анализ очень сложно сделать осмысленно, но мы можем взглянуть на несколько фактов. От http://www.penguin.cz/~literakl/intel/s.html#SAL и http://www.penguin.cz/~literakl/intel/i. html # IMUL мы получаем представление о тактовых циклах x86, необходимых для арифметического сдвига и умножения. Скажем, мы придерживаемся "486" (самый новый из перечисленных), 32-разрядных регистров и немедленных, IMUL занимает 13-42 цикла и IDIV 44. Каждая лицензия SAL занимает 2 и добавляя 1, так что даже если несколько из них вместе смещаются поверхностно как победитель.

В эти дни с ядром i7:

(из http://software.intel.com/en-us/forums/showthread.php?t=61481 )

Задержка составляет 1 цикл для сложения целых чисел и 3 цикла для умножения целых чисел . Вы можете найти задержки и значения в Приложении C "Справочного руководства по оптимизации архитектур Intel® 64 и IA-32", которое находится по адресу http://www.intel.com/products/processor/manuals/ .

(из какого-то объявления Intel)

Используя SSE, Core i7 может выдавать одновременные инструкции сложения и умножения, что приводит к пиковой частоте 8 операций с плавающей запятой (FLOP) за такт

Это дает вам представление о том, как далеко все зашло. Оптимистические мелочи - такие как сдвиг битов по сравнению с * - которые воспринимались всерьез даже в 90-е годы, просто устарели. Сдвиг битов все еще быстрее, но для не-степени двух муль/дел к тому времени, когда вы делаете все свои смены и добавляете результаты, это снова медленнее. Затем, больше инструкций означает больше ошибок кэша, больше потенциальных проблем в конвейерной обработке, более широкое использование временных регистров может означать большее сохранение и восстановление содержимого регистра из стека ... это быстро становится слишком сложным, чтобы количественно определить все воздействия, но они преимущественно отрицательный.

функциональность в исходном коде против реализации

В более общем плане, ваш вопрос помечен C и C++. Как языки третьего поколения, они специально разработаны, чтобы скрыть детали базового набора команд ЦП. Чтобы удовлетворить свои языковые стандарты, они должны поддерживать операции умножения и сдвига (и многие другие) , даже если базовое оборудование не . В таких случаях они должны синтезировать требуемый результат, используя множество других инструкций. Точно так же они должны обеспечивать программную поддержку для операций с плавающей запятой, если в процессоре этого нет, а FPU нет. Все современные процессоры поддерживают * и <<, так что это может показаться нелепо теоретическим и историческим, но важно то, что свобода выбора реализации идет в обоих направлениях: даже если у CPU есть инструкция, которая реализует операцию, запрошенную в исходном коде в В общем случае, компилятор может выбрать что-то еще, что он предпочитает, потому что это лучше для конкретного случая, с которым сталкивается компилятор.

Примеры (с гипотетическим языком ассемблера)

source           literal approach         optimised approach
#define N 0
int x;           .Word x                xor registerA, registerA
x *= N;          move x -> registerA
                 move x -> registerB
                 A = B * immediate(0)
                 store registerA -> x
  ...............do something more with x...............

Такие инструкции, как exclusive или (xor), не имеют отношения к исходному коду, но если что-то само по себе очищает все биты, то это может быть использовано для установки чего-либо на 0. Исходный код, который подразумевает адреса памяти, может не повлечь за собой никакого использования ,.

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

Таким образом, даже если сдвиг и добавление все еще быстрее на каком-то конкретном оборудовании, тогда разработчик компилятора, вероятно, сработал именно тогда, когда это и безопасно, и выгодно.

Ремонтопригодность

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

К счастью, хорошие компиляторы, такие как GCC, обычно могут заменить серию битовых сдвигов и арифметику прямым умножением, когда включена любая оптимизация (то есть ...main(...) { return (argc << 4) + (argc << 2) + argc; } -> imull $21, 8(%ebp), %eax), поэтому перекомпиляция может помочь даже без исправления кода, но это не гарантируется.

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

Общие решения против частичных решений

Если у вас есть некоторые дополнительные знания, такие как то, что ваше int действительно будет хранить только значения x, y и z, то вы сможете разработать некоторые инструкции, которые работают для этих значений, и вы получите свой результат быстрее, чем когда компилятор не имеет такого понимания и нуждается в реализации, которая работает для всех значений int. Например, рассмотрим ваш вопрос:

Умножение и деление могут быть достигнуты с помощью битовых операторов ...

Вы иллюстрируете умножение, но как насчет деления?

int x;
x >> 1;   // divide by 2?

Согласно стандарту C++ 5.8:

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

Итак, ваш битовый сдвиг имеет результат, определенный реализацией, когда x отрицателен: он может не работать одинаково на разных машинах. Но / работает гораздо более предсказуемо. (Может быть также идеально непротиворечивым, так как разные машины могут иметь разные представления отрицательных чисел, и следовательно, разные диапазоны, даже если в представлении присутствует одинаковое количество битов.)

Вы можете сказать: "Мне все равно ... что int хранит возраст сотрудника, он никогда не может быть отрицательным". Если у вас есть такая особая способность проникновения в суть, тогда да - ваша безопасная оптимизация >> может быть передана компилятором, если вы явно не сделаете это в своем коде. Но это рискованно и редко полезно, поскольку большую часть времени у вас не будет такого понимания, и другие программисты, работающие над тем же кодом, выиграют ' Не знаю, что вы поставили на карту некоторые необычные ожидания в отношении данных, с которыми вы будете работать ... что кажется совершенно безопасным изменением для них, может иметь неприятные последствия из-за вашей "оптимизации".

Есть ли какие-либо входные данные, которые не могут быть умножены или разделены таким образом?

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

36
Tony Delroy

Только что попробовал на моей машине компилировать это:

int a = ...;
int b = a * 10;

При разборке выдает результат:

MOV EAX,DWORD PTR SS:[ESP+1C] ; Move a into EAX
LEA EAX,DWORD PTR DS:[EAX+EAX*4] ; Multiply by 5 without shift !
SHL EAX, 1 ; Multiply by 2 using shift

Эта версия быстрее, чем ваш оптимизированный вручную код с чистым сдвигом и дополнением.

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

32
user703016

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

На самом деле трюк с разделением, известный как "магическое разделение", может принести огромные выгоды. Опять же, вы должны сначала профиль, чтобы увидеть, если это необходимо. Но если вы используете его, то есть полезные программы, которые помогут вам выяснить, какие инструкции необходимы для той же семантики деления. Вот пример: http://www.masm32.com/board/index.php?topic=12421.

Пример, который я поднял из потока OP на MASM32:

include ConstDiv.inc
...
mov eax,9999999
; divide eax by 100000
cdiv 100000
; edx = quotient

Будет генерировать:

mov eax,9999999
mov edx,0A7C5AC47h
add eax,1
.if !CARRY?
    mul edx
.endif
shr edx,16
21
Mike Kwan

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

Целочисленное деление все еще относительно медленное, поэтому использование сдвига вместо деления на степень 2 все еще является выигрышем, и большинство компиляторов будут реализовывать это как оптимизацию. Обратите внимание, что для того, чтобы эта оптимизация была действительной, дивиденд должен быть либо беззнаковым, либо должен быть известен как положительный. Для отрицательного дивиденда сдвиг и деление не эквивалентны!

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 5; i >= -5; --i)
    {
        printf("%d / 2 = %d, %d >> 1 = %d\n", i, i / 2, i, i >> 1);
    }
    return 0;
}

Результат:

5 / 2 = 2, 5 >> 1 = 2
4 / 2 = 2, 4 >> 1 = 2
3 / 2 = 1, 3 >> 1 = 1
2 / 2 = 1, 2 >> 1 = 1
1 / 2 = 0, 1 >> 1 = 0
0 / 2 = 0, 0 >> 1 = 0
-1 / 2 = 0, -1 >> 1 = -1
-2 / 2 = -1, -2 >> 1 = -1
-3 / 2 = -1, -3 >> 1 = -2
-4 / 2 = -2, -4 >> 1 = -2
-5 / 2 = -2, -5 >> 1 = -3

Поэтому, если вы хотите помочь компилятору, убедитесь, что переменная или выражение в дивиденде явно без знака.

11
Paul R

Это полностью зависит от целевого устройства, языка, цели и т.д.

Хруст пикселя в драйвере видеокарты? Очень вероятно, да!

.NET бизнес-приложение для вашего отдела? Абсолютно нет причин даже смотреть на это.

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

3
Brady Moritz

Не делайте этого, если только вам это абсолютно не нужно, и ваше намерение кода требует смещения, а не умножения/деления.

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

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

2
Kromster

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

Однако некоторые машины имеют математический процессор, который содержит специальные инструкции для умножения/деления.

1
iammilind

Я согласен с помеченным ответом Дрю Холла. Ответ может использовать некоторые дополнительные заметки, хотя.

Для подавляющего большинства разработчиков программного обеспечения процессор и компилятор больше не имеют отношения к данному вопросу. Большинство из нас далеко за 8088 и MS-DOS. Это возможно только для тех, кто все еще разрабатывает для встроенных процессоров ...

В моей компании по разработке программного обеспечения Math (add/sub/mul/div) должен использоваться для всей математики. Хотя Shift следует использовать при преобразовании между типами данных, например. ushort to byte как n >> 8 и not n/256.

1
deegee

Я думаю, что в одном случае, когда вы хотите умножить или разделить на степень два, вы не ошибетесь с использованием операторов битового сдвига, даже если компилятор преобразует их в MUL/DIV, потому что некоторые процессоры микрокодируют (на самом деле, макрос) их в любом случае, так что в этих случаях вы добьетесь улучшения, особенно если сдвиг больше 1. Или, более конкретно, если у ЦПУ нет операторов сдвига битов, это будет MUL/DIV в любом случае, но если ЦП имеет операторы bithift, вы избегаете ветки микрокода, и это на несколько инструкций меньше.

Я сейчас пишу некоторый код, который требует много операций удвоения/деления пополам, потому что он работает на плотном двоичном дереве, и есть еще одна операция, которая, я подозреваю, может быть более оптимальной, чем сложение - левая (степень двойного умножения) ) сдвиг с дополнением. Это можно заменить на сдвиг влево и xor, если сдвиг шире, чем количество бит, которое вы хотите добавить, простой пример (i << 1) ^ 1, который добавляет единицу к удвоенному значению. Это, конечно, не относится к сдвигу вправо (степень двойного деления), потому что только сдвиг влево (с прямым порядком байтов) заполняет пробел нулями.

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

Кроме того, в алгоритмах, которые я пишу, они визуально представляют движения, которые происходят, поэтому в этом смысле они на самом деле более ясны. Левая часть бинарного дерева больше, а правая меньше. Кроме того, в моем коде нечетные и четные числа имеют особое значение, и все левые дочерние элементы в дереве являются нечетными, а все правые дочерние элементы и корень четными. В некоторых случаях, с которыми я еще не сталкивался, но, может, я даже и не думал об этом, x & 1 может быть более оптимальной операцией по сравнению с x% 2. x & 1 на четном числе будет давать ноль, но будет давать 1 для нечетного числа.

Пройдя немного дальше, чем просто нечетная/четная идентификация, если я получу ноль для x & 3, я знаю, что 4 является фактором нашего числа, и то же самое для x% 7 для 8 и так далее. Я знаю, что эти случаи, вероятно, имеют ограниченную полезность, но приятно знать, что вы можете избежать операции модуля и использовать вместо этого побитовую логическую операцию, потому что побитовые операции почти всегда самые быстрые и наименее вероятно будут неоднозначными для компилятора.

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

0
Louki Sumirniy

Тест Python, выполняющий одинаковое умножение 100 миллионов раз против одинаковых случайных чисел.

>>> from timeit import timeit
>>> setup_str = 'import scipy; from scipy import random; scipy.random.seed(0)'
>>> N = 10*1000*1000
>>> timeit('x=random.randint(65536);', setup=setup_str, number=N)
1.894096851348877 # Time from generating the random #s and no opperati

>>> timeit('x=random.randint(65536); x*2', setup=setup_str, number=N)
2.2799630165100098
>>> timeit('x=random.randint(65536); x << 1', setup=setup_str, number=N)
2.2616429328918457

>>> timeit('x=random.randint(65536); x*10', setup=setup_str, number=N)
2.2799630165100098
>>> timeit('x=random.randint(65536); (x << 3) + (x<<1)', setup=setup_str, number=N)
2.9485139846801758

>>> timeit('x=random.randint(65536); x // 2', setup=setup_str, number=N)
2.490908145904541
>>> timeit('x=random.randint(65536); x / 2', setup=setup_str, number=N)
2.4757170677185059
>>> timeit('x=random.randint(65536); x >> 1', setup=setup_str, number=N)
2.2316000461578369

Таким образом, при выполнении сдвига, а не умножения/деления на степень два в python, есть небольшое улучшение (~ 10% для деления; ~ 1% для умножения). Если это не сила двух, вероятно, значительное замедление.

Опять же, эти # будут меняться в зависимости от вашего процессора, вашего компилятора (или интерпретатора - для простоты это сделано в python).

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

0
dr jimbob

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

Ниже приведен пример кода C++, который может выполнить более быстрое деление, выполняя 64-битное "Умножение на обратную". Числитель и знаменатель должны быть ниже определенного порога. Обратите внимание, что он должен быть скомпилирован для использования 64-битных инструкций, чтобы быть на самом деле быстрее, чем обычное деление.

#include <stdio.h>
#include <chrono>

static const unsigned s_bc = 32;
static const unsigned long long s_p = 1ULL << s_bc;
static const unsigned long long s_hp = s_p / 2;

static unsigned long long s_f;
static unsigned long long s_fr;

static void fastDivInitialize(const unsigned d)
{
    s_f = s_p / d;
    s_fr = s_f * (s_p - (s_f * d));
}

static unsigned fastDiv(const unsigned n)
{
    return (s_f * n + ((s_fr * n + s_hp) >> s_bc)) >> s_bc;
}

static bool fastDivCheck(const unsigned n, const unsigned d)
{
    // 32 to 64 cycles latency on modern cpus
    const unsigned expected = n / d;

    // At least 10 cycles latency on modern cpus
    const unsigned result = fastDiv(n);

    if (result != expected)
    {
        printf("Failed for: %u/%u != %u\n", n, d, expected);
        return false;
    }

    return true;
}

int main()
{
    unsigned result = 0;

    // Make sure to verify it works for your expected set of inputs
    const unsigned MAX_N = 65535;
    const unsigned MAX_D = 40000;

    const double ONE_SECOND_COUNT = 1000000000.0;

    auto t0 = std::chrono::steady_clock::now();
    unsigned count = 0;
    printf("Verifying...\n");
    for (unsigned d = 1; d <= MAX_D; ++d)
    {
        fastDivInitialize(d);
        for (unsigned n = 0; n <= MAX_N; ++n)
        {
            count += !fastDivCheck(n, d);
        }
    }
    auto t1 = std::chrono::steady_clock::now();
    printf("Errors: %u / %u (%.4fs)\n", count, MAX_D * (MAX_N + 1), (t1 - t0).count() / ONE_SECOND_COUNT);

    t0 = t1;
    for (unsigned d = 1; d <= MAX_D; ++d)
    {
        fastDivInitialize(d);
        for (unsigned n = 0; n <= MAX_N; ++n)
        {
            result += fastDiv(n);
        }
    }
    t1 = std::chrono::steady_clock::now();
    printf("Fast division time: %.4fs\n", (t1 - t0).count() / ONE_SECOND_COUNT);

    t0 = t1;
    count = 0;
    for (unsigned d = 1; d <= MAX_D; ++d)
    {
        for (unsigned n = 0; n <= MAX_N; ++n)
        {
            result += n / d;
        }
    }
    t1 = std::chrono::steady_clock::now();
    printf("Normal division time: %.4fs\n", (t1 - t0).count() / ONE_SECOND_COUNT);

    getchar();
    return result;
}
0
user2044859

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

0
harold