Чи слід використовувати множення чи ділення?


118

Ось нерозумно веселе запитання:

Скажімо, нам належить виконати просту операцію, де нам потрібно половина значення змінної. Існують, як правило, два способи зробити це:

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

Якщо припустити, що ми використовуємо стандартні оператори, які надаються мовою, який з них має кращу продуктивність?

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

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


5
Ви виконували тест? Мова йде лише про десяток рядків коду. Що ви навчились із тестування еталону? [Підказка: робити це було б швидше, ніж розміщувати тут питання.]
С.Лотт

4
Чудове запитання, яке породило досить цікаві відповіді / дискусії. Дякую :)
stealthcopter

22
Навіть якби він дізнався відповідь, порівнявши її, це все-таки корисне питання і дало кілька цікавих і корисних відповідей. Також я б хотів, щоб люди дотримувалися суті і утримувалися від написання відповідей та коментарів до відповідей, пропонуючи нерелевантні поради щодо того, чи варто робити оптимізацію. Чому б не припустити, що ОП задає запитання так, як написане, а не припускати, що він або вона 'справді' бажає поради щодо перезапису ширшого масштабу.
Кевін Уайтфут

1
Ділення набагато повільніше, ніж множення. Але деякі розумні компілятори / VM перетворюють поділ на множення, тому ваші тести матимуть однакові результати (обидва тести тестового множення).
Іван Кукір

4
Трохи поза темою, але я просто хочу сказати, наскільки я згоден з @KevinWhitefoot. Немає нічого такого розчарування, як читання від сермонізаторів, а не страй-технічні відповіді на технічні питання. Дякую Кевін за ваш коментар!
Жан-Франсуа

Відповіді:


78

Пітон:

time python -c 'for i in xrange(int(1e8)): t=12341234234.234 / 2.0'
real    0m26.676s
user    0m25.154s
sys     0m0.076s

time python -c 'for i in xrange(int(1e8)): t=12341234234.234 * 0.5'
real    0m17.932s
user    0m16.481s
sys     0m0.048s

множення швидше на 33%

Луа:

time lua -e 'for i=1,1e8 do t=12341234234.234 / 2.0 end'
real    0m7.956s
user    0m7.332s
sys     0m0.032s

time lua -e 'for i=1,1e8 do t=12341234234.234 * 0.5 end'
real    0m7.997s
user    0m7.516s
sys     0m0.036s

=> немає реальної різниці

LuaJIT:

time luajit -O -e 'for i=1,1e8 do t=12341234234.234 / 2.0 end'
real    0m1.921s
user    0m1.668s
sys     0m0.004s

time luajit -O -e 'for i=1,1e8 do t=12341234234.234 * 0.5 end'
real    0m1.843s
user    0m1.676s
sys     0m0.000s

=> це лише на 5% швидше

висновки: у Python швидше множитися, ніж ділити, але коли ви наближаєтесь до процесора, використовуючи більш вдосконалені VM або JIT, перевага зникає. Цілком можливо, що майбутній Python VM зробить це неактуальним


Дякуємо за пораду щодо використання команди часу для тестування!
Едмундіто

2
Ваш висновок неправильний. Це стає більш актуальним, коли JIT / VM стає кращим. Відділення стає повільніше порівняно з нижньою вершиною ВМ. Пам'ятайте, що компілятори, як правило, не можуть сильно оптимізувати плаваючу крапку, щоб гарантувати точність.
rasmus

7
@rasmus: У міру того, як JIT стає кращим, стає більше шансів використовувати інструкцію щодо множення процесора, навіть якщо ви попросили поділу.
Бен Войгт

68

Завжди використовуйте все, що є найяснішим. Все, що ви робите, намагається перехитрити компілятор. Якщо компілятор взагалі розумний, він зробить все можливе, щоб оптимізувати результат, але нічого не може змусити наступного хлопця не ненавидіти вас за ваше шалене рішення про бітшифтинг (я, до речі, люблю трохи маніпуляції, це весело. Але весело! = Читабельно )

Передчасна оптимізація - корінь усього зла. Завжди пам’ятайте про три правила оптимізації!

  1. Не оптимізуйте.
  2. Якщо ви експерт, див. Правило №1
  3. Якщо ви є експертом і можете виправдати необхідність, то використовуйте наступну процедуру:

    • Код це неоптимізовано
    • визначте, наскільки швидко "достатньо швидкий" - Зауважте, яка вимога / історія користувача вимагає цього показника.
    • Напишіть тест на швидкість
    • Перевірте наявний код - якщо він досить швидкий, ви закінчите.
    • Перекодируйте його оптимізовано
    • Перевірка оптимізованого коду. Якщо вона не відповідає метриці, киньте її та зберігайте оригінал.
    • Якщо він відповідає тесту, зберігайте оригінальний код у коментарях

Крім того, такі речі, як видалення внутрішніх циклів, коли вони не потрібні, або вибір пов'язаного списку через масив для сортування вставки - це не оптимізація, а просто програмування.


7
це не повна цитата Кнут; дивіться en.wikipedia.org/wiki/…
Jason S

Ні, існує близько 40 різних цитат на цю тему з стільки ж різних джерел. Я свого роду частину разом.
Білл К

У вашому останньому реченні незрозуміло, коли слід застосовувати правила №1 та №2, залишаючи нас там, де ми розпочали: нам потрібно вирішити, які оптимізації варті, а які ні. Прикинути відповідь очевидно, це не відповідь.
Метт

2
Це насправді так заплутано? Завжди застосовуйте правила 1 і 2, якщо ви фактично не відповідаєте специфікаціям клієнта і не знайомі з усією системою, включаючи мову та характеристики кешування процесора. У той момент, ТОЛЬКО дотримуйтесь процедури в 3, не думайте просто "Ей, якщо я кешувати цю змінну локально, а не викликати геттера, все, ймовірно, буде швидше. Спочатку доведіть, що це не досить швидко, потім протестуйте кожну оптимізацію окремо і викиньте тих, що не допомагають. Документи сильно
Білл К

49

Я думаю, що це стає настільки прискіпливим, що вам краще робити все, що робить код більш читабельним. Якщо ви не виконаєте операції тисячі, якщо не мільйони разів, я сумніваюся, що хтось коли-небудь помітить різницю.

Якщо вам справді доводиться робити вибір, то єдиний шлях - бенчмаркінг. Знайдіть, які функції створюють проблеми, а потім з’ясуйте, де у функції виникають проблеми, і виправте ці розділи. Однак я все ще сумніваюся, що одна математична операція (навіть одна повторена багато, багато разів) була б причиною будь-якого вузького місця.


1
Коли я робив радіолокаційні процесори, одна операція змінила значення. Але ми вручну оптимізували машинний код для досягнення продуктивності в режимі реального часу. За все інше я голосую за просте і очевидне.
С.Лотт

Я думаю, що для деяких речей ви можете піклуватися про одну операцію. Але я б очікував, що в 99% додатків, що там, це не має значення.
Томас Оуенс

27
Тим більше, що ОП шукала відповідь у Python. Я сумніваюся, що все, що потребує такої ефективності, було б написано на Python.
Ред С.

4
Поділ - це, мабуть, найдорожча операція в руслі перетину трикутника, яка є основою для більшості тракторів. Якщо ви зберігаєте зворотний і множите замість ділення, ви відчуєте багато разів прискорення.
solinent

@solinent - так, прискорення, але я сумніваюся "багато разів" - поділ і множення з плаваючою комою не повинні відрізнятися більш ніж приблизно 4: 1, якщо тільки процессор, про який йде мова, дійсно оптимізований для множення, а не поділу.
Jason S

39

Множення швидше, ділення точніше. Ви втратите деяку точність, якщо ваш номер не буде потужністю 2:

y = x / 3.0;
y = x * 0.333333;  // how many 3's should there be, and how will the compiler round?

Навіть якщо ви дозволите компілятору зрозуміти перевернуту константу до ідеальної точності, відповідь все одно може бути іншою.

x = 100.0;
x / 3.0 == x * (1.0/3.0)  // is false in the test I just performed

Питання швидкості може мати значення лише на мовах C / C ++ або JIT, і навіть тоді, лише якщо операція знаходиться в циклі на вузькому місці.


Ділення точне, якщо ви ділите на цілі числа.
плінтус

7
Ділення з плаваючою комою з знаменником> чисельник повинен вводити безглузді значення в біти низького порядку; поділ зазвичай знижує точність.
С.Лотт

8
@ S.Lott: Ні, це неправда. Усі реалізації плаваючої точки, сумісні з IEEE-754, повинні добре округлювати результати кожної операції (тобто до найближчого номера з плаваючою точкою) щодо поточного режиму округлення. Помноження на зворотні завжди призведе до більшої помилки, хоча б тому, що має відбутися ще одне округлення.
Електро

1
Я знаю, що цій відповіді більше 8 років, але вона вводить в оману; Ви можете виконати поділ без значної втрати точності: y = x * (1.0/3.0);і компілятор, як правило, обчислює 1/3 під час компіляції. Так, 1/3 не є ідеально представленим в IEEE-754, але, виконуючи арифметику з плаваючою комою, ви все одно втрачаєте точність , чи ви робите множення чи ділення, оскільки біти низького порядку округлені. Якщо ви знаєте, що ваше обчислення чутливе до помилки округлення, ви також повинні знати, як найкраще вирішити проблему.
Jason S

1
@JasonS Я щойно залишив програму, що працює протягом ночі, починаючи з 1,0 і рахуючи на 1 ULP; Я порівнював результат множення на (1.0/3.0)ділення на 3.0. Я піднявся до 1.0000036666774155, і в цьому просторі 7,3% результатів були різними. Я припускаю, що вони відрізнялися лише на 1 біт, але оскільки арифметика IEEE гарантовано округляється до найближчого правильного результату, я вважаю, що поділ є більш точним. Чи важлива різниця, залежить від вас.
Марк Рансом

25

Якщо ви хочете оптимізувати свій код, але все ще зрозумілий, спробуйте:

y = x * (1.0 / 2.0);

Компілятор повинен бути в змозі зробити ділення в час компіляції, тому ви отримаєте множення під час виконання. Я б очікував, що точність буде такою ж, як у y = x / 2.0випадку.

Якщо це може мати значення, LOT знаходиться у вбудованих процесорах, де для обчислення арифметики з плаваючою комою необхідна емуляція з плаваючою комою.


12
Підійдіть собі (і хто б це зробив) - це стандартна практика у вбудованому світі, і інженери програмного забезпечення в цій галузі вважають це зрозумілим.
Jason S

4
+1 за те, що єдиний тут зрозумів, що компілятори не можуть оптимізувати операції з плаваючою комою, як би вони хотіли. Вони навіть не можуть змінити порядок операндів у множенні, щоб гарантувати точність (якщо тільки він не використовує розслаблений режим).
rasmus

1
OMG, є щонайменше 6 програмістів, які думають, що елементарна математика незрозуміла. Множення AFAIK, IEEE 754 є комутативним (але не асоціативним).
maaartinus

13
Можливо, ви пропускаєте суть. Це не має нічого спільного з алгебраїчною правильністю. В ідеальному світі ви просто зможете розділити на два: y = x / 2.0;але в реальному світі вам, можливо, доведеться спонукати компілятор виконувати менш дороге множення. Можливо, менш зрозуміло, чому y = x * (1.0 / 2.0);краще, і було б зрозуміліше y = x * 0.5;натомість. Але перейдіть 2.0на а, 7.0і я набагато краще побачу, y = x * (1.0 / 7.0);ніж y = x * 0.142857142857;.
Джейсон S

3
Це дійсно дає зрозуміти, чому можна більш розбірливо (і точно) використовувати ваш метод.
Хуан Мартінес

21

Просто додамо щось для параметра "інші мови".
С: Оскільки це справді лише академічна вправа не має ніякого значення, я подумав, що буду сприяти чомусь іншому.

Я збирався на збірку без оптимізацій і дивився на результат.
Код:

int main() {

    volatile int a;
    volatile int b;

    asm("## 5/2\n");
    a = 5;
    a = a / 2;

    asm("## 5*0.5");
    b = 5;
    b = b * 0.5;

    asm("## done");

    return a + b;

}

складений с gcc tdiv.c -O1 -o tdiv.s -S

поділ на 2:

movl    $5, -4(%ebp)
movl    -4(%ebp), %eax
movl    %eax, %edx
shrl    $31, %edx
addl    %edx, %eax
sarl    %eax
movl    %eax, -4(%ebp)

і множення на 0,5:

movl    $5, -8(%ebp)
movl    -8(%ebp), %eax
pushl   %eax
fildl   (%esp)
leal    4(%esp), %esp
fmuls   LC0
fnstcw  -10(%ebp)
movzwl  -10(%ebp), %eax
orw $3072, %ax
movw    %ax, -12(%ebp)
fldcw   -12(%ebp)
fistpl  -16(%ebp)
fldcw   -10(%ebp)
movl    -16(%ebp), %eax
movl    %eax, -8(%ebp)

Однак, коли я змінив ті intз доdouble s (що, мабуть, зробив би python), я отримав таке:

поділ:

flds    LC0
fstl    -8(%ebp)
fldl    -8(%ebp)
flds    LC1
fmul    %st, %st(1)
fxch    %st(1)
fstpl   -8(%ebp)
fxch    %st(1)

множення:

fstpl   -16(%ebp)
fldl    -16(%ebp)
fmulp   %st, %st(1)
fstpl   -16(%ebp)

Я не орієнтував жоден із цього коду, але, лише вивчивши код, можна побачити, що за допомогою цілих чисел ділення на 2 коротше, ніж множення на 2. Використання подвійних значень множення коротше, оскільки компілятор використовує опкоди з плаваючою комою процесора, які ймовірно, працює швидше (але насправді я не знаю), ніж не використовувати їх для тієї ж операції. Отже в кінцевому підсумку ця відповідь показала, що ефективність множини на 0,5 проти ділення на 2 залежить від реалізації мови та платформи, на якій вона працює. Зрештою, різниця незначна, і це те, про що ви практично ніколи не повинні турбуватися, крім випадків читабельності.

Як бічну примітку ви бачите, що в моїй програмі main()повертається a + b. Коли я знімаю мінливе ключове слово, ви ніколи не здогадаєтесь, як виглядає збірка (виключаючи налаштування програми):

## 5/2

## 5*0.5
## done

movl    $5, %eax
leave
ret

це робило як ділення, множення, так і додавання в одній інструкції! Зрозуміло, що вам не доведеться турбуватися з цього приводу, якщо оптимізатор будь-якого респектабельного.

Вибачте за занадто довгу відповідь.


1
Це не "єдина інструкція". Він просто згорнувся.
kvanberendonck

5
@kvanberendonck Звичайно, це одна інструкція. Порахуйте їх: movl $5, %eax назва оптимізації не важлива і навіть не має значення. Ви просто хотіли поблажливо відповісти на чотирирічну відповідь.
Карсон Майєрс

2
Характер оптимізації все ще важливий для розуміння, оскільки це залежно від контексту: воно застосовується лише в тому випадку, якщо ви додаєте / множуєте / ділить / тощо. константи часу компіляції, де компілятор може просто виконати всю математику заздалегідь і перемістити остаточну відповідь у регістр під час виконання. Ділення набагато повільніше, ніж множення в загальному випадку (дільники виконання), але я вважаю, що множення на зворотні допомагає лише в тому випадку, якщо ви в іншому випадку розділите один і той же знаменник більше, ніж один раз. Ви, мабуть, все це знаєте, але нові програмисти, можливо, знадобляться, тому ... про всяк випадок.
Майк S

10

По-перше, якщо ви не працюєте на C або ASSEMBLY, ви, мабуть, володієте мовою вищого рівня, де затримки пам'яті та загальні накладні виклику абсолютно усунуть різницю між множенням та поділом до суттєвого значення. Отже, просто виберіть те, що читається краще в такому випадку.

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

Якщо ви все ще цікаві, з точки зору оптимізації низького рівня:

Розділення, як правило, має значно довший трубопровід, ніж примноження. Це означає, що для отримання результату потрібно більше часу, але якщо ви можете тримати процесор зайнятим незалежними завданнями, це не призведе до того, що коштує вам більше, ніж примноження.

Тривалість різниці трубопроводів залежить від обладнання. Останнє обладнання, яке я використав, було щось на зразок 9 циклів для множення FPU та 50 циклів для поділу FPU. Звучить багато, але тоді ви втратите 1000 циклів за промах пам’яті, так що це може поставити речі в перспективу.

Аналогія - це поставити пиріг у мікрохвильову піч під час перегляду телешоу. Загальний час, який вам відвів від телешоу, - це те, як довго потрібно було покласти його в мікрохвильову піч і вийняти з мікрохвильової печі. Решту часу ви все ще дивилися телешоу. Тож якщо для приготування пирога знадобилося 10 хвилин замість 1 хвилини, він фактично не витрачав більше часу на ваш телевізор.

На практиці, якщо ви збираєтеся досягти рівня турботи про різницю між множенням та діленням, вам потрібно зрозуміти конвеєри трубопроводів, кеш-пам'ять, гілки відділень, передбачення поза замовленнями та залежність від конвеєра. Якщо це виглядає не так, як ви мали намір піти з цим питанням, то правильна відповідь - ігнорувати різницю між ними.

Багато (багато) років тому було абсолютно критично уникати ділень і завжди використовувати множення, але тоді хіти пам’яті були менш актуальними, а ділення були набагато гіршими. Сьогодні я оцінюю читабельність вище, але якщо різниці в читанні немає, я вважаю, що це добра звичка вибирати множення.


7

Пишіть те, що чіткіше визначає ваші наміри.

Після роботи програми з’ясуйте, що повільно, і зробіть це швидше.

Не робіть цього навпаки.


6

Робіть все, що вам потрібно. Спочатку подумайте про свого читача, не турбуйтеся про ефективність, поки не будете впевнені, що у вас є проблеми з продуктивністю.

Дозвольте компілятору зробити виставу за вас.


5

Якщо ви працюєте з цілими чи іншими типами з плаваючою комою, не забувайте своїх операторів бітсіфтинга: << >>

    int y = 10;
    y = y >> 1;
    Console.WriteLine("value halved: " + y);
    y = y << 1;
    Console.WriteLine("now value doubled: " + y);

7
ця оптимізація автоматично виконується за кадром у будь-якому сучасному компіляторі.
Дастін Гетц

Хтось перевіряв, чи перевіряє (використовуючи біт ops), чи має операнд (?) Версію, що змінюється, щоб використовувати це замість цього? функція mul (a, b) {якщо (b дорівнює 2) повернути << 1; якщо (b дорівнює 4) повернути << 2; // ... і т.д. повернути a * b; } Я здогадуюсь, що ІФ такий дорогий, він був би менш ефективним.
Крістофер Лайтфут

Це не друкувалося ніде близько до того, що я уявляв; Не звертай уваги.
Крістофер Лайтфут

Для операцій const звичайний компілятор повинен виконувати роботу; але тут ми використовуємо python, тому я не впевнений, чи достатньо розумний його знати? (Вона повинна бути).
Крістофер Лайтфут

Хороший ярлик, за винятком того, що не відразу зрозуміло, що насправді відбувається. Більшість програмістів навіть не розпізнають операторів бітшіфта.
Blazemonger

4

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


3

Я завжди дізнавався, що множення є ефективнішим.


"ефективний" - це неправильне слово. Це правда, що більшість процесорів розмножуються швидше, ніж діляться. Однак у сучасних конвеєрних архітектурах ваша програма може не бачити різниці. Як говорять багато інших, вам справді набагато краще робити те, що найкраще читається людині.
ТЕД

3

Розмноження зазвичай швидше - звичайно ніколи повільніше. Однак, якщо вона не є критичною для швидкості, пишіть те, що найясніше.


2

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

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


2

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


2

Будьте обережні, "як правило, краще відгадувати множення, тому я намагаюся дотримуватися цього, коли кодую",

У контексті цього конкретного питання краще тут означає «швидше». Що не дуже корисно.

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

Див. Арифметику з плаваючою комою з аналізом помилок . Див. Основні проблеми з арифметики та аналізу помилок з плаваючою комою .

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

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

>>> 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

У цьому прикладі ви бачите, що в міру зменшення значень різниця між майже рівними числами створює ненульові результати, де правильна відповідь дорівнює нулю.


1

Я десь читав, що множення ефективніше в C / C ++; Ніякої ідеї щодо інтерпретованих мов - різниця, мабуть, незначна через усі інші накладні витрати.

Якщо це не стане проблемою з тим, що є більш ретельним / читабельним - я ненавиджу це, коли люди говорять мені про це, але це так правда.


1

Я б запропонував множення в цілому, тому що вам не доведеться проводити цикли, гарантуючи, що ваш дільник не дорівнює 0. Це, звичайно, не стосується, якщо ваш дільник постійний.


1

Андроїд Java, профільований на 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% швидше, ніж множення (!)


1
Щоб бути реалістичним, слід перевірити a = i*0.5, а не a *= 0.5. Саме так більшість програмістів будуть використовувати операції.
Blazemonger

1

Як і у дописах №24 (множення швидше) та №30 - але іноді їх обох так само просто зрозуміти:

1*1e-6F;

1/1e6F;

~ Я вважаю їх обох так само простими для читання, і їм доводиться повторювати їх мільярди разів. Тож корисно знати, що множення зазвичай швидше.


1

Існує різниця, але це залежить від компілятора. Спочатку vs vs 2003 (c ++) у мене не було суттєвої різниці для подвійних типів (64-бітна плаваюча точка). Однак знову запускаючи тести на vs2010, я виявив величезну різницю - до коефіцієнта 4 швидше для множення. Відстежуючи це, схоже, що vs2003 та vs2010 генерують різні коди fpu.

На Pentium 4, 2,8 ГГц по відношенню до 2003 року:

  • Множення: 8.09
  • Відділ: 7.97

На Xeon W3530, vs 2003:

  • Множення: 4,68
  • Відділ: 4,64

На Xeon W3530, vs2010:

  • Множення: 5.33
  • Відділ: 21.05

Здається, що vs vs 2003 р. Поділ у циклі (тому дільник було використано кілька разів) було переведено на множення з оберненою. У відношенні vs2010 ця оптимізація більше не застосовується (я вважаю, оскільки між двома методами є дещо різний результат). Зауважте також, що процесор виконує поділи швидше, як тільки ваш чисельник дорівнює 0,0. Я не знаю точного алгоритму, вбудованого в мікросхему, але, можливо, це залежить від числа.

Редагувати 18-03-2013: спостереження за vs2010


Цікаво, чи є якась причина, яку компілятор не міг би замінити, наприклад n/10.0, виразом форми (n * c1 + n * c2)? Я б очікував, що для більшості процесорів поділ займе довше, ніж два множення та ділення, і я вважаю, що поділ на будь-яку константу може дати правильний округлий результат у всіх випадках із застосуванням зазначеної рецептури.
supercat

1

Ось дурно весела відповідь:

х / 2,0 це НЕ еквівалентно х * 0,5

Скажімо, ви писали цей метод 22 жовтня 2008 року.

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

Тепер, через 10 років, ви дізнаєтесь, що можете оптимізувати цей фрагмент коду. Цей метод посилається у сотнях формул у всій програмі. Таким чином, ви змінюєте це та відчуваєте чудове 5% -ве покращення продуктивності.

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

Чи було правильним рішенням змінити код? У математиці два вирази дійсно рівнозначні. У інформатиці це не завжди відповідає дійсності. Будь ласка, прочитайте Мінімізація ефекту проблем з точністю для отримання більш детальної інформації. Якщо ваші обчислені значення - у якийсь момент - порівняно з іншими значеннями, ви зміните результат кращих випадків. Наприклад:

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

Підсумок є; як тільки ви влаштуєтесь на будь-який із двох, тоді дотримуйтесь цього!


1
Downvote? Як щодо коментаря, що пояснює ваші думки? Ця відповідь, безумовно, на 100% актуальна.
l33t

В інформатиці множення / ділення значень з плаваючою комою на потужності 2 не втрачає, якщо значення не стане денормалізованим або переповненим.
Сун

Оскільки плаваюча точка не є без втрат під час поділу, це не має особливого значення, якщо ваше твердження вірно. Хоча я був би дуже здивований, якби це було.
l33t

1
"Плаваюча точка не втрачається під час поділу" лише тоді, коли ви будуєте стародавній компілятор, який випромінює застарілий код x87. На сучасному апаратному забезпеченні, що має змінну float / double, без втрат, або 32, або 64 бітний IEEE 754: en.wikipedia.org/wiki/IEEE_754 Через те, як працює IEEE 754, коли ділишся на 2 або множиш на 0,5, ти зменшується показник на 1, решта бітів (знак + мантіса) не змінюються. І як 2і 0.5числа можуть бути представлені в IEEE 754 точно, без втрати точності ( в відміну , наприклад , 0.4або 0.1вони не можуть).
Сун

0

Добре, якщо припустити, що операція додавання / віднімання коштує 1, то множення витрат становить 5 і поділ витрат приблизно на 20.


Звідки ви взяли ці номери? досвід? шосте відчуття? стаття в Інтернеті? як вони змінюватимуться для різних типів даних?
kroiz

0

Після такої тривалої та цікавої дискусії тут я прийму це питання: остаточної відповіді на це питання немає. Як зазначають деякі люди, це залежить як від апаратного забезпечення (cf piotrk і gast128 ), так і від компілятора (cf @ Jaavier 's тести). Якщо швидкість не є критичною, якщо вашій програмі не потрібно обробляти в режимі реального часу величезну кількість даних, ви можете вибрати чіткість, використовуючи поділ, тоді як якщо швидкість обробки або завантаження процесора є проблемою, множення може бути найбезпечнішим. Нарешті, якщо ви точно не знаєте, на якій платформі буде розгорнута ваша програма, орієнтир не має сенсу. А для ясності коду один коментар зробив би цю роботу!


-3

Технічно не існує поділу, як поділ, є просто множення на обернені елементи. Наприклад, ви ніколи не ділите на 2, ви насправді множите на 0,5.

"Відділення" - давайте переконаємося, що воно існує на секунду - завжди важче це множення, тому що для "поділу" xна yодне спочатку потрібно обчислити таке значення y^{-1}, y*y^{-1} = 1а потім зробити множення x*y^{-1}. Якщо ви вже знаєте, y^{-1}тоді не обчислювати його, yповинно бути оптимізацією.


3
Що повністю ігнорує реальність обох команд, що існують у кремнію.
NPSF3000

@ NPSF3000 - я не дотримуюся. Зважаючи на те, що обидві операції існують, вона просто стверджує, що операція ділення неявно передбачає обчислення мультиплікативного зворотного та множення, що завжди буде складніше, ніж просто робити одне множення. Кремній - це деталь реалізації.
сатнхак

@ BTyler. Якщо обидві команди існують у кремнії, і обидві команди займають однакову кількість циклів [як можна було б очікувати], ніж наскільки відносно складними є ці інструкції, абсолютно не має значення щодо продуктивності POV.
NPSF3000

@ NPSF3000 - але вони не беруть однакової кількості циклів, тому що множення відбувається швидше.
снтхак
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.