Деление с плавающей запятой против умножения с плавающей запятой

Есть ли (без микрооптимизации) прирост производительности за счет кодирования?

float f1 = 200f / 2

по сравнению с

float f2 = 200f * 0.5

Несколько лет назад мой профессор сказал мне, что деление с плавающей запятой происходит медленнее, чем умножение с плавающей запятой, без объяснения причин.

Верно ли это утверждение для современной архитектуры ПК?

Обновление1

Что касается комментария, пожалуйста, также рассмотрите этот случай:

float f1;
float f2 = 2
float f3 = 3;
for( i =0 ; i < 1e8; i++)
{
  f1 = (i * f2 + i / f3) * 0.5; //or divide by 2.0f, respectively
}

Обновление 2 Цитата из комментариев:

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


person sum1stolemyname    schedule 08.11.2010    source источник
comment
Настоящий способ найти ответ - попробовать оба и измерить время.   -  person sharptooth    schedule 08.11.2010
comment
Большинство компиляторов оптимизируют такое буквальное постоянное выражение, как это, поэтому это не имеет значения.   -  person Paul R    schedule 08.11.2010
comment
@sharptooth: Да, испытание себя решило бы проблему для моей машины разработчика, но я подумал, что если у кого-то из SO-толпы уже есть ответ для общего случая, он хотел бы поделиться;)   -  person sum1stolemyname    schedule 08.11.2010
comment
Пол по-прежнему прав; Большинство компиляторов превратят / 2.0 в * 0.5.   -  person Gabe    schedule 08.11.2010
comment
@ Гейб, я думаю, Пол имел в виду, что это превратит 200f / 2 в 100f.   -  person mikerobi    schedule 08.11.2010
comment
@mikerobi: они также меняют x / 2.f на x * 0.5f   -  person Johan Kotlinski    schedule 08.11.2010
comment
@Paul: Такая оптимизация возможна для степеней двойки, но не в целом. Помимо степеней двойки, ни одно число с плавающей запятой не имеет обратной величины, на которую можно умножать вместо деления.   -  person R.. GitHub STOP HELPING ICE    schedule 18.12.2010
comment
Умножение в Sandy Bridge может быть разбито на 5 шагов (и, следовательно, задержка в 5 циклов) 1. Отдельные мантисса и экспоненты   -  person ggulgulia    schedule 12.11.2017
comment
Я бы +1, но мне кажется, что 87 (в его нынешнем виде) - это как-то отличный результат для такого вопроса :) Кроме того, деление на 2,0f и умножение на 0,5 - это не одно и то же (среди прочего, упомянутого) в том смысле, что последний должен распространить операцию на double, что может сказаться на производительности. Даже если бы результат был бы таким же для таких, к сожалению, круглых чисел (пример с 10 против 0,1 был бы более интересным).   -  person Zeus    schedule 26.04.2021


Ответы (7)


Да, многие процессоры могут выполнять умножение за 1 или 2 такта, но деление всегда занимает больше времени (хотя деление FP иногда быстрее, чем целочисленное деление).

Если вы посмотрите этот ответ вы увидите, что деление может превышать 24 цикла.

Почему деление занимает намного больше времени, чем умножение? Если вы вспомните еще в начальной школе, вы можете вспомнить, что умножение может быть выполнено с множеством одновременных сложений. Деление требует итеративного вычитания, которое нельзя выполнять одновременно, поэтому требуется больше времени. Фактически, некоторые блоки FP ускоряют деление, выполняя обратное приближение и умножая на него. Это не так точно, но несколько быстрее.

person Gabe    schedule 08.11.2010
comment
Я думаю, ОП хочет знать, какие алгоритмические / архитектурные требования делают деление значительно более сложным с точки зрения аппаратного обеспечения, чем умножение. - person chrisaycock; 08.11.2010
comment
Насколько я помню, Cray-1 не беспокоился об инструкции деления, у него была обратная инструкция, и после этого вы ожидали, что вы умножитесь. Именно по этой причине. - person Mark Ransom; 08.11.2010
comment
Марк: Действительно, алгоритм 4-шагового деления описан на странице 3-28 Справочника по аппаратному обеспечению CRAY-1: обратное приближение, обратная итерация, числитель * приближение, частное половинной точности * поправочный коэффициент. - person Gabe; 08.11.2010
comment
Вопрос @Gabe, поскольку каждое деление fp может быть преобразовано в умножение, просто сделайте экспоненту отрицательной и умножьте, почему деление будет медленнее, чем умножение - person aaronman; 12.07.2013
comment
@aaronman: Если бы числа FP были сохранены как x ^ y, тогда умножение на x ^ -y было бы таким же, как деление. Однако номера FP хранятся как x * 2^y. Умножение на x * 2^-y - это просто умножение. - person Gabe; 12.07.2013
comment
@Gabe ничего себе забыл, давно не делал таких вещей, моя плохая - person aaronman; 12.07.2013
comment
Что за начальная школа? - person Pharap; 09.08.2016
comment
Конструкторы оборудования становятся умнее !! - person nimig18; 06.04.2017
comment
это можно принять, но это не обязательно объясняет ответ! - person ggulgulia; 12.11.2017
comment
@Pharap Начальная (или начальная) школа, по всей видимости, 1-6 классы в Великобритании, - person S.S. Anne; 28.03.2020

Будьте очень осторожны с разделением и по возможности избегайте его. Например, вытащите float inverse = 1.0f / divisor; из цикла и умножьте на inverse внутри цикла. (Если ошибка округления в inverse допустима)

Обычно 1.0/x не может быть точно представлен как float или double. Это будет точно, когда x будет степенью 2. Это позволяет компиляторам оптимизировать x / 2.0f до x * 0.5f без каких-либо изменений в результате.

Чтобы компилятор мог выполнить эту оптимизацию за вас, даже если результат не будет точным (или с делителем переменной времени выполнения), вам потребуются такие параметры, как _ 10_. В частности, -freciprocal-math (включено -funsafe-math-optimizations включено -ffast-math) позволяет компилятору заменять x / y на x * (1/y), когда это полезно. Другие компиляторы имеют аналогичные параметры, и ICC может включать некоторую «небезопасную» оптимизацию по умолчанию (я думаю, что это так, но я забываю).

-ffast-math часто важен для обеспечения автоматической векторизации циклов FP, особенно сокращений (например, суммирование массива в одну скалярную сумму), потому что математика FP не ассоциативна. Почему GCC не оптимизирует a * a * a * a * a * a до (а * а * а) * (а * а * а)?

Также обратите внимание, что компиляторы C ++ могут сворачивать + и * в FMA в некоторых случаях (при компиляции для поддерживающей его цели, например -march=haswell), но они не могут этого сделать с /.


У деления задержка хуже, чем у умножения или сложения (или FMA) в 2 раза. 4 на современных процессорах x86 и худшая пропускная способность в 6-40 раз 1 (для плотного цикла, выполняющего только деление вместо только < / em> умножение).

Блок div / sqrt не является полностью конвейерным по причинам, описанным в Ответ @ NathanWhitehead. Наихудшие соотношения для векторов размером 256 байт, потому что (в отличие от других исполнительных единиц) блок деления обычно не является полноширинным, поэтому широкие векторы должны быть разделены на две половины. Не полностью конвейерная исполнительная единица настолько необычна, что в процессорах Intel есть аппаратный счетчик производительности arith.divider_active, который поможет вам найти код, который ограничивает пропускную способность делителя, а не узкие места обычного интерфейса или порта выполнения. (Или, чаще, узкие места в памяти или длинные цепочки задержек, ограничивающие параллелизм на уровне команд, из-за чего пропускная способность составляет менее ~ 4 за такт).

Однако разделение FP и sqrt на процессорах Intel и AMD (кроме KNL) реализовано как единый uop, поэтому это не обязательно оказывает большое влияние на пропускную способность окружающего кода. Наилучший случай для деления - это когда выполнение вне очереди может скрыть задержку, и когда есть много умножений и сложений (или другой работы), которые могут происходить параллельно с делением.

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

Так, например, это будет очень плохо:

for ()
    a[i] = b[i] / scale;  // division throughput bottleneck

// Instead, use this:
float inv = 1.0 / scale;
for ()
    a[i] = b[i] * inv;  // multiply (or store) throughput bottleneck

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

Такое сокращение, как accumulator /= b[i], будет ограничивать задержку деления или умножения, а не пропускную способность. Но с несколькими накопителями, которые вы делите или умножаете в конце, вы можете скрыть задержку и при этом увеличить пропускную способность. Обратите внимание, что sum += a[i] / b[i] узкие места по add задержке или div пропускной способности, но не div задержке, потому что разделение не находится на критическом пути (цепочка зависимостей, переносимая петлей).


Но в чем-то вроде этого (аппроксимация функции типа log(x) соотношением двух многочленов) деление может быть довольно дешево:

for () {
    // (not shown: extracting the exponent / mantissa)
    float p = polynomial(b[i], 1.23, -4.56, ...);  // FMA chain for a polynomial
    float q = polynomial(b[i], 3.21, -6.54, ...);
    a[i] = p/q;
}

Для log() в диапазоне мантисс соотношение двух многочленов порядка N имеет гораздо меньшую ошибку, чем один многочлен с 2N коэффициентами, а параллельное вычисление 2 дает вам некоторый параллелизм на уровне инструкций в пределах одного тела цикла вместо одного массового длинная цепочка dep, что НАМНОГО упрощает выполнение вне очереди.

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

Мы не ограничиваем пропускную способность деления, пока наши многочлены достаточно велики, чтобы у нас было только одно деление на каждые 10 инструкций FMA или около того. (И в реальном log() сценарии использования требуется много работы по извлечению экспоненты / мантиссы и повторному объединению вещей вместе, так что между делениями нужно проделать еще больше работы.)


Когда вам действительно нужно разделить, обычно лучше просто разделить вместо rcpps

x86 имеет примерно-обратную инструкцию (rcpps), которая дает вам только 12 бит точности. (AVX512F имеет 14 бит, а AVX512ER - 28 бит.)

Вы можете использовать это, чтобы сделать x / y = x * approx_recip(y), не используя фактическую инструкцию деления. (rcpps itsef работает довольно быстро; обычно немного медленнее, чем умножение. Он использует поиск по таблице из внутренней для ЦП таблицы. Аппаратное обеспечение делителя может использовать ту же таблицу в качестве отправной точки.)

Для большинства целей x * rcpps(y) слишком неточно, и требуется итерация Ньютона-Рафсона для удвоения точности. Но это стоит вам 2 умножения и 2 FMA и имеет задержку, примерно равную фактической инструкции деления. Если все, что вы делаете, - это разделение, то это может быть выигрыш в пропускной способности. (Но вам следует в первую очередь избегать такого цикла, если вы можете, возможно, выполняя деление как часть другого цикла, который выполняет другую работу.)

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

(Например, Knight's Landing, см. Ниже. KNL поддерживает AVX512ER, так что для float векторы, VRCP28PS результат уже достаточно точен, чтобы просто умножить его без итерации Ньютона-Рафсона. float Размер мантиссы составляет всего 24 бита.)


Конкретные числа из таблиц Агнера Фога:

В отличие от любой другой операции ALU, задержка разделения / пропускная способность зависят от данных на некоторых процессорах. Опять же, это потому, что он такой медленный и не полностью конвейерный. Планирование вне очереди проще с фиксированными задержками, потому что оно позволяет избежать конфликтов обратной записи (когда один и тот же порт выполнения пытается выдать 2 результата в одном цикле, например, при выполнении 3-тактной инструкции, а затем двух 1-тактных операций) .

Как правило, самые быстрые случаи - это когда делитель представляет собой "круглое" число, например 2.0 или 0.5 (то есть представление base2 float имеет много завершающих нулей в мантиссе).

float задержка (циклы) / пропускная способность (циклов на инструкцию, выполняемых последовательно с независимыми входами):

                   scalar & 128b vector        256b AVX vector
                   divss      |  mulss
                   divps xmm  |  mulps           vdivps ymm | vmulps ymm

Nehalem          7-14 /  7-14 | 5 / 1           (No AVX)
Sandybridge     10-14 / 10-14 | 5 / 1        21-29 / 20-28 (3 uops) | 5 / 1
Haswell         10-13 / 7     | 5 / 0.5       18-21 /   14 (3 uops) | 5 / 0.5
Skylake            11 / 3     | 4 / 0.5          11 /    5 (1 uop)  | 4 / 0.5

Piledriver       9-24 / 5-10  | 5-6 / 0.5      9-24 / 9-20 (2 uops) | 5-6 / 1 (2 uops)
Ryzen              10 / 3     | 3 / 0.5         10  /    6 (2 uops) | 3 / 1 (2 uops)

 Low-power CPUs:
Jaguar(scalar)     14 / 14    | 2 / 1
Jaguar             19 / 19    | 2 / 1            38 /   38 (2 uops) | 2 / 2 (2 uops)

Silvermont(scalar)    19 / 17    | 4 / 1
Silvermont      39 / 39 (6 uops) | 5 / 2            (No AVX)

KNL(scalar)     27 / 17 (3 uops) | 6 / 0.5
KNL             32 / 20 (18uops) | 6 / 0.5        32 / 32 (18 uops) | 6 / 0.5  (AVX and AVX512)

double задержка (циклы) / пропускная способность (циклов на инструкцию):

                   scalar & 128b vector        256b AVX vector
                   divsd      |  mulsd
                   divpd xmm  |  mulpd           vdivpd ymm | vmulpd ymm

Nehalem         7-22 /  7-22 | 5 / 1        (No AVX)
Sandybridge    10-22 / 10-22 | 5 / 1        21-45 / 20-44 (3 uops) | 5 / 1
Haswell        10-20 /  8-14 | 5 / 0.5      19-35 / 16-28 (3 uops) | 5 / 0.5
Skylake        13-14 /     4 | 4 / 0.5      13-14 /     8 (1 uop)  | 4 / 0.5

Piledriver      9-27 /  5-10 | 5-6 / 1       9-27 / 9-18 (2 uops)  | 5-6 / 1 (2 uops)
Ryzen           8-13 /  4-5  | 4 / 0.5       8-13 /  8-9 (2 uops)  | 4 / 1 (2 uops)

  Low power CPUs:
Jaguar            19 /   19  | 4 / 2            38 /  38 (2 uops)  | 4 / 2 (2 uops)

Silvermont(scalar) 34 / 32    | 5 / 2
Silvermont         69 / 69 (6 uops) | 5 / 2           (No AVX)

KNL(scalar)      42 / 42 (3 uops) | 6 / 0.5   (Yes, Agner really lists scalar as slower than packed, but fewer uops)
KNL              32 / 20 (18uops) | 6 / 0.5        32 / 32 (18 uops) | 6 / 0.5  (AVX and AVX512)

Айвибридж и Бродвелл тоже разные, но я хотел, чтобы стол был маленьким. (Core2 (до Nehalem) имеет лучшую производительность делителя, но его максимальные тактовые частоты были ниже.)

Atom, Silvermont и даже Knight's Landing (Xeon Phi на основе Silvermont) имеют исключительно низкую производительность деления, и даже вектор 128b медленнее, чем скаляр. Процессор AMD Jaguar с низким энергопотреблением (используемый в некоторых консолях) похож. Высокопроизводительный делитель занимает большую площадь кристалла. Xeon Phi имеет низкое энергопотребление на ядро ​​, а упаковка большого количества ядер на кристалле дает более жесткие ограничения по площади кристалла, чем у Skylake-AVX512. Похоже, что AVX512ER rcp28ps / pd - это то, что вы «должны» использовать на KNL.

(См. этот результат InstLatx64 для Skylake-AVX512 aka. Numbers-X. для vdivps zmm: 18c / 10c, то есть половина пропускной способности ymm.)


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


Сноска 1: как я вычислил соотношение производительности div и mul:

Соотношение между FP и множественной производительностью даже хуже, чем в процессорах с низким энергопотреблением, таких как Silvermont и Jaguar, и даже в Xeon Phi (KNL, где вам следует использовать AVX512ER).

Фактическое соотношение пропускной способности деления / умножения для скалярных (не векторизованных) double: 8 на Ryzen и Skylake с их усиленными делителями, но 16–28 на Haswell (зависит от данных и, скорее всего, 28 цикл заканчивается, если ваши делители не являются круглыми числами). Эти современные процессоры имеют очень мощные делители, но их пропускная способность с умножением на 2 на такт просто сногсшибательна. (Тем более, когда ваш код может автоматически векторизоваться с 256-битными векторами AVX). Также обратите внимание, что при правильных параметрах компилятора это умножение пропускной способности также применимо к FMA.

Числа из таблиц инструкций http://agner.org/optimize/ для Intel Haswell / Skylake и AMD Ryzen для Скаляр SSE (не включая x87 fmul / fdiv) и для 256b AVX SIMD-векторов float или double. См. Также вики-страницу с тегами x86.

person Peter Cordes    schedule 26.08.2017

Деление по своей сути является гораздо более медленной операцией, чем умножение.

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

double d1 = 7 / 10.;
double d2 = 7 * 0.1;

не семантически идентичны - 0.1 не может быть точно представлен как double, поэтому в конечном итоге будет использовано немного другое значение - замена деления на умножение в этом случае даст другой результат!

person Michael Borgwardt    schedule 08.11.2010
comment
С g ++ 200.f / 10 и 200.f * 0.1 испускают точно такой же код. - person Johan Kotlinski; 08.11.2010
comment
@kotlinski: это делает g ++ неправильным, а не мое утверждение. Я полагаю, можно было бы возразить, что если разница имеет значение, вам не следует использовать числа с плавающей запятой в первую очередь, но это определенно то, что я бы сделал только на более высоких уровнях оптимизации, если бы я был автором компилятора. - person Michael Borgwardt; 08.11.2010
comment
@ Майкл: Неправильно по какому стандарту? - person Johan Kotlinski; 08.11.2010
comment
@kotlinski: неправильно по стандарту, что оптимизация производительности, выполняемая автоматически компилятором, никогда не должна изменять семантическое поведение кода. - person Michael Borgwardt; 08.11.2010
comment
@Michael: Я не вижу причин, по которым в этом случае он будет вычислять неправильный результат. Я бы поспорил, что результат правильный и одинаковый в обоих выражениях. - person Johan Kotlinski; 08.11.2010
comment
@kotlinski: тогда вы не понимаете, как работают типы данных с плавающей запятой. Сколько вы готовы поставить? - person Michael Borgwardt; 08.11.2010
comment
-1, неправильная логика. Основное предположение здесь состоит в том, что, поскольку 0.1f не может быть точно представлен, компилятор или чип выберут совершенно другое число. Конечно, не будет, он выберет число, очень, очень близкое к 0,1f - на самом деле, он, вероятно, выберет лучшее приближение. В результате результат 200f*0.1 будет очень близок к 20f. И это число нужно округлить, так как 200f*0.1 не может быть точно представлен. Теперь, учитывая, что оно близко к 20f, угадайте, до чего оно будет округлено? - person MSalters; 08.11.2010
comment
@Michael: Скажите, пожалуйста, какое было бы правильное значение f2, если не 0x41a00000? - person Johan Kotlinski; 08.11.2010
comment
@MSalters: значение, наиболее близкое к результату вычислений, которое может быть представлено числом с плавающей запятой - и это НЕ обязательно 20f! - person Michael Borgwardt; 08.11.2010
comment
@kotlinski: Возможно, пример был выбран не очень удачно, но попробуйте 7/10 против 7 * 0,1 с двойной точностью ... - person Michael Borgwardt; 08.11.2010
comment
вы правы, но по неправильным причинам. 7/10 указывает компилятору выполнить целочисленное деление, поэтому результат будет 0 - person sum1stolemyname; 09.11.2010
comment
@ sum1stolemyname: это не должно было быть кодом. Попробуйте это как двойное деление и посмотрите на полученный битовый узор. - person Michael Borgwardt; 09.11.2010
comment
если вы попробуете его честно (что не позволяет компилятору оптимизировать или заменить), вы обнаружите, что 7/10 и 7 * 0,1 с использованием двойной точности не дают одинакового результата. Умножение дает неправильный ответ, оно дает число больше, чем деление. с плавающей запятой речь идет о точности, если даже один бит отключен, это неправильно. то же самое касается 7/5! = 7 / 0,2, но возьмите число, которое вы можете представить 7/4 и 7 * 0,25, что даст тот же результат. IEEE поддерживает несколько режимов округления, поэтому вы можете решить некоторые из этих проблем (если вы заранее знаете ответ). - person old_timer; 09.11.2010
comment
обратите внимание, что ответ может быть неточным, что нормально для большинства, но не для всех, и беспокойтесь о реальном вопросе. почему умножение происходит быстрее ... - person old_timer; 09.11.2010
comment
Кстати, в этом случае умножение и деление одинаково быстры - они вычисляются во время компиляции. - person Johan Kotlinski; 09.11.2010
comment
На всякий случай, если люди задаются вопросом об этом, я знаю, что VC ++ позволяет вам настраивать, если вы хотите, чтобы компилятор выполнял эти оптимизации. Я предполагаю, что большинство современных компиляторов C ++ предоставляют вам аналогичные возможности. См. Страницу MSDN о флаге / fp. - person Aidiakapi; 12.11.2014

да. Каждый известный мне FPU выполняет умножение намного быстрее, чем деление.

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

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

person T.E.D.    schedule 08.11.2010
comment
Недостаточно сделать код читабельным. Иногда возникают требования по оптимизации, которые обычно затрудняют понимание кода. Хороший разработчик сначала напишет хорошие модульные тесты, а затем оптимизирует код. Читаемость - это хорошо, но не всегда достижимая цель. - person BЈовић; 08.11.2010
comment
@VJo - Либо вы пропустили мое предпоследнее предложение, либо вы не согласны с моими приоритетами. Если последнее, боюсь, мы обречены на разногласия. - person T.E.D.; 08.11.2010
comment
Компиляторы обычно хороши в оптимизации кода, но у них есть некоторые ограничения. Вы бы поверили, что какой-то алгоритм можно с помощью уловок ускорить в несколько раз? cortstratton.org/articles/OptimizingForSSE.php - person BЈовић; 08.11.2010
comment
дох выложил случайно. В любом случае, код в этой ссылке не читается, но он намного быстрее читаемого оригинала. - person BЈовић; 08.11.2010
comment
Компиляторы не могут оптимизировать это за вас. Им не разрешено делать это, потому что результаты будут другими и несовместимыми (относительно IEEE-754). gcc предоставляет для этой цели параметр -ffast-math, но он ломает многие вещи и не может использоваться в целом. - person R.. GitHub STOP HELPING ICE; 18.12.2010
comment
Полагаю, это немного некрокомментарий, но разделение обычно не конвейерно. Так что это действительно может сильно повлиять на производительность. Во всяком случае, конвейерная обработка делает разницу в производительности умножения и деления еще больше, потому что одна конвейерная обработка, а другая - нет. - person harold; 01.08.2012
comment
Компиляторам C разрешено оптимизировать это, потому что как деление на 2,0, так и умножение на 0,5 являются точными при использовании двоичной арифметики, поэтому результат тот же. См. Раздел F.8.2 стандарта ISO C99, в котором именно этот случай показан как допустимое преобразование при использовании привязок IEEE-754. - person njuffa; 04.09.2012

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

Для деления вы начинаете с x из 2n бит и y из n бит, вы хотите вычислить x / y. Самый простой метод - деление в столбик, но в двоичном формате. На каждом этапе вы проводите сравнение и вычитание, чтобы получить еще один бит частного. Это займет n шагов.

Некоторые отличия: на каждом шаге умножения нужно смотреть только на 1 бит; на каждом этапе деления во время сравнения нужно смотреть n битов. Каждый этап умножения не зависит от всех других этапов (не имеет значения, в каком порядке вы добавляете частичные продукты); для деления каждый шаг зависит от предыдущего шага. Это очень важно для оборудования. Если что-то может быть сделано независимо, то это может происходить одновременно в течение тактового цикла.

person Nathan Whitehead    schedule 16.03.2011
comment
Последние процессоры Intel (начиная с Broadwell) используют делитель radix-1024, чтобы выполнить деление за меньшее количество шагов. В отличие от всего остального, блок разделения не является полностью конвейерным (потому что, как вы говорите, отсутствие независимости / параллелизма имеет большое значение для оборудования). например Упакованное в Skylake деление с двойной точностью (vdivpd ymm) имеет пропускную способность в 16 раз хуже, чем умножение (vmulpd ymm), и хуже в более ранних процессорах с менее мощным оборудованием деления. agner.org/optimize - person Peter Cordes; 26.08.2017

Newton Rhapson решает целочисленное деление со сложностью O (M (n)) с помощью аппроксимации линейной алгебры. Быстрее, чем сложность в противном случае O (n * n).

В коде Метод содержит 10mults 9adds 2bitwiseshifts.

Это объясняет, почему деление примерно в 12 раз больше, чем умножение.

person ollj    schedule 02.04.2016

Ответ зависит от платформы, для которой вы программируете.

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

person BЈовић    schedule 08.11.2010
comment
Но и другие ответы тоже хороши. Деление обычно медленнее или равно умножению, но это зависит от платформы. - person BЈовић; 08.11.2010
comment
к настоящему моменту есть инструкции по разделению для SSE - person Andre Holzner; 13.08.2013
comment
divps является частью оригинального SSE1, представленного в PentiumIII. Инструкции деления SIMD целочисленного не существует, но разделение SIMD FP действительно существует. Блок деления иногда имеет даже худшую пропускную способность / задержку для широких векторов (особенно 256b AVX), чем для скалярных или 128b векторов. Даже Intel Skylake (со значительно более быстрым делением FP, чем Haswell / Broadwell) имеет divps xmm (4 упакованных числа с плавающей запятой): задержка 11c, одна на пропускную способность 3c. divps ymm (8 упакованных чисел с плавающей запятой): задержка 11c, одна на пропускную способность 5c. (или для упакованных двойников: один на 4c или один на 8c) См. вики-страницу по тегам x86 для получения ссылок на perf. - person Peter Cordes; 02.04.2016