Должен ли я использовать умножение или деление? - PullRequest
111 голосов
/ 22 октября 2008

Вот глупый забавный вопрос:

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

y = x / 2.0;
// or...
y = x * 0.5;

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

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

Хотя лично мне интересен ответ для Python 2.4-2.5, не стесняйтесь также публиковать ответ и для других языков! И если вы хотите, не стесняйтесь публиковать и другие причудливые способы (например, используя операторы побитового сдвига).

Ответы [ 25 ]

4 голосов
/ 30 июня 2012

На самом деле есть веская причина, по которой умножение большого пальца будет быстрее деления. Деление с плавающей запятой в аппаратных средствах выполняется либо с помощью алгоритмов сдвига и условного вычитания («длинное деление» с двоичными числами), либо, что более вероятно, в наши дни, с итерациями, такими как алгоритм Голдшмидта. Для сдвига и вычитания требуется, по меньшей мере, один цикл на бит точности (итерации практически невозможно распараллелить, как и умножение со сдвигом и сложением), и итерационные алгоритмы выполняют, по меньшей мере, одно умножение за итерацию . В любом случае весьма вероятно, что деление займет больше циклов. Конечно, это не учитывает причуд в компиляторах, перемещении данных или точности. В целом, однако, если вы кодируете внутренний цикл в чувствительной ко времени части программы, разумно сделать запись 0.5 * x или 1.0/2.0 * x, а не x / 2.0. Педантизм «код, что ясно» абсолютно верен, но все три из них настолько читаемы, что педантичность в данном случае просто педантична.

3 голосов
/ 22 октября 2008

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

2 голосов
/ 23 октября 2008

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

В контексте этого конкретного вопроса, лучше здесь означает «быстрее». Что не очень полезно.

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

См. Арифметика с плавающей запятой с анализом ошибок . См. Основные проблемы в арифметике с плавающей запятой и анализе ошибок .

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

Самые большие проблемы возникают при попытке манипулировать двумя почти равными числами. Самые правые биты (биты ошибок) доминируют в результатах.

>>> for i in range(7):
...     a=1/(10.0**i)
...     b=(1/10.0)**i
...     print i, a, b, a-b
... 
0 1.0 1.0 0.0
1 0.1 0.1 0.0
2 0.01 0.01 -1.73472347598e-18
3 0.001 0.001 -2.16840434497e-19
4 0.0001 0.0001 -1.35525271561e-20
5 1e-05 1e-05 -1.69406589451e-21
6 1e-06 1e-06 -4.23516473627e-22

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

2 голосов
/ 22 октября 2008

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

2 голосов
/ 22 октября 2008

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

Но я более склонен отвечать «это не имеет значения», если только профилирование не покажет, что деление является узким местом по сравнению с умножением. Однако я предполагаю, что выбор умножения и деления не окажет большого влияния на производительность вашего приложения.

2 голосов
/ 22 октября 2008

Я всегда учил, что умножение более эффективно.

1 голос
/ 22 октября 2008

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

Если это не становится проблемой с тем, что более легко обслуживаемо / читабельно - я ненавижу, когда люди говорят мне это, но это так.

1 голос
/ 29 мая 2018

Вот глупый забавный ответ:

x / 2.0 равно не эквивалентно x * 0.5

Допустим, вы написали этот метод 22 октября 2008 года.

double half(double x) => x / 2.0;

Теперь, спустя 10 лет, вы узнаете, что можете оптимизировать этот кусок кода. На метод ссылаются в сотнях формул по всему вашему приложению. Таким образом, вы изменили его и ощутимо улучшили производительность на 5%.

double half(double x) => x * 0.5;

Было ли это правильным решением изменить код? В математике два выражения действительно эквивалентны. В информатике это не всегда так. Пожалуйста, прочитайте Минимизация влияния проблем с точностью для более подробной информации. Если ваши вычисленные значения - в какой-то момент - сравниваются с другими значениями, вы измените результат граничных случаев. E.g.:

double quantize(double x)
{
    if (half(x) > threshold))
        return 1;
    else
        return -1;
}

Итог есть; как только вы согласитесь на любой из двух, затем придерживайтесь его!

1 голос
/ 29 июня 2012

Разница есть, но она зависит от компилятора. Сначала на vs2003 (c ++) я не получил существенной разницы для двойных типов (64-битная с плавающей точкой). Однако, выполнив тесты снова на vs2010, я обнаружил огромную разницу, в 4 раза быстрее для умножений. Отслеживая это, кажется, что vs2003 и vs2010 генерируют разные коды fpu.

На Pentium 4, 2,8 ГГц, по сравнению с 2003:

  • Умножение: 8.09
  • Отдел: 7,97

На Xeon W3530, версия 2003:

  • Умножение: 4,68
  • Разделение: 4,64

На Xeon W3530, версия 2010:

  • Умножение: 5,33
  • Разделение: 21,05

Кажется, что в vs2003 деление в цикле (таким образом, делитель использовался несколько раз) было переведено в умножение с обратным. На vs2010 эта оптимизация больше не применяется (я полагаю, потому что между этими двумя методами есть немного разные результаты). Также обратите внимание, что процессор выполняет деления быстрее, как только ваш числитель станет 0.0. Я не знаю точного алгоритма, встроенного в чип, но, возможно, он зависит от числа.

Редактировать 18-03-2013: наблюдение за 2010

1 голос
/ 11 октября 2011

Java android, профилированный на Samsung GT-S5830

public void Mutiplication()
{
    float a = 1.0f;

    for(int i=0; i<1000000; i++)
    {
        a *= 0.5f;
    }
}
public void Division()
{
    float a = 1.0f;

    for(int i=0; i<1000000; i++)
    {
        a /= 2.0f;
    }
}

Результаты

Multiplications():   time/call: 1524.375 ms
Division():          time/call: 1220.003 ms

Деление примерно на 20% быстрее, чем умножение (!)

...