Чому додавання таке швидке, як бітові операції в сучасних процесорах?


72

Я знаю, що розрядні операції настільки швидкі на сучасних процесорах, оскільки вони можуть працювати 32 або 64 біти паралельно, тому бітові операції займають лише один тактовий цикл. Однак додаток - це складна операція, яка складається щонайменше з однієї і, можливо, до десятка розрядних операцій, тому я, природно, думав, що це буде в 3-4 рази повільніше. Я був здивований, побачивши після простого еталону, що додавання точно таке ж швидке, як і будь-які операції з біт-розумом (XOR, АБО, І т.д.). Хтось може пролити світло на це?




1
Так, розмноження було досить швидким і в моїх тестах. Це було лише приблизно в 2 рази повільніше, ніж додавання, тоді як ділення було приблизно в 30 разів (!) Разів повільніше.
SoloNasus

Компактний огляд впроваджених паралельно префікси дерева суматорів: Таксономія паралельних префікс мереж Девіда Харріс: pages.hmc.edu/harris/research/taxonomy.pdf
Франки

Більш докладно: докторська дисертація доктора Джуна Чена "Паралельно-префіксні структури для бінарних та модульних {2n − 1, 2n, 2n + 1} суматорів" digital.library.okstate.edu/etd/Chen_okstate_0664D_10070.pdf
Франки

Відповіді:


104

Додавання швидко, тому що дизайнери процесорів внесли в схему, необхідну для швидкої роботи. Це займає значно більше воріт, ніж бітові операції, але це досить часто, що дизайнери процесорів вважають, що це варто. Дивіться https://en.wikipedia.org/wiki/Adder_(electronics) .

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


Коментарі не для розширеного обговорення; ця розмова перенесена в чат .
DW

38

Є кілька аспектів.

  • Відносна вартість побітової операції та доповнення. Наївна добавка матиме заглиблення, яке лінійно залежить від ширини слова. Існують альтернативні підходи, більш затратні з точки зору воріт, які зменшують глибину (IIRC глибина потім залежить логарифмічно від ширини слова). Інші дають посилання на подібні методи, я лише зазначу, що різниця є також менш важливою, ніж те, що може здатися лише враховуючи вартість операції через необхідність логіки управління, яка додає затримок.

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

  • Нарешті, є мікро-архітектурні міркування: ви впевнені, що вимірюєте те, що хочете? В даний час процесори, як правило, конвеєрні, багатоскалярні, із виконанням поза замовленням і будь-що інше. Це означає, що вони здатні виконувати кілька інструкцій одночасно, на різних етапах завершення. Якщо ви хочете показати за допомогою вимірювань, що операція займає більше часу, ніж інша, вам слід врахувати цей аспект, оскільки їх мета - приховати ці відмінності. Ви можете мати однакову пропускну спроможність для операцій додавання та побітових операцій при використанні незалежних даних, але міра затримки або введення залежностей між операціями може показувати інакше. І ви також повинні бути впевнені, що вузьке місце вашої міри знаходиться у виконанні, а не, наприклад, у доступі до пам'яті.


6
+1. Так, більшість процесорів є тактовими, але декілька годинних процесорів є комерційно доступними.
Девід Кері

2
Інша можливість полягає в тому, що процесор може зберігати 64-розрядний регістр як один 16-бітний шматок і три 17-бітні шматки, де зайві біти кожного шматка, що містять відкладений, переносяться знизу. Доповнення, за яким слідує побізна операція або сховище, може вимагати 1-2 додаткових циклів для розповсюдження носіїв, але додавання, за яким слідує інше додавання, не буде. Крім того, у випадку "магазин" додатковий час розповсюдження може затримати продуктивність магазину, однак не потрібно буде код "чекати" його.
supercat

3
@supercat Pentium 4 зробив щось подібне, з подвійною швидкістю (по відношенню до решти процесора) ALU, який мав би низькі 16 або 32 біти, готові до наступної операції за півциклу до бітів верхньої половини.
Джеффрі Босбум

2
ти впевнений, що ти вимірюєш те, що хочеш? У цьому випадку висновок ОП з вимірювань виявляється правильним для переважної більшості процесорів. Додавання настільки поширене, що суперскалярні процесори мають додавати одиниці у всі порти виконання, а булеві настільки дешеві для реалізації (за кількістю транзисторів), що вони також є у всіх портах. Тож додавання та булеви майже завжди мають однакову пропускну здатність (наприклад, 4 на такт в Intel Haswell).
Пітер Кордес

2
Ціле додавання SIMD часто нижча пропускна здатність, ніж булева SIMD, хоча вони зазвичай мають однакову затримку. Процесорні процесори Intel від PentiumII до Broadwell можуть запускати лише вектор-int-добавки (наприклад paddw) на 2 за такт, а булеві (наприклад pand) на 3 за такт. (Skylake ставить векторну сумацію на всі три порти векторного виконання.)
Пітер Кордес,

24

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

Наприклад, простий процесор може мати 3 кроки для кожної інструкції: завантаження, виконання та зберігання. У будь-який час опрацьовуються 3 інструкції: одна отримується, одна виконується і зберігається її результати. Це називається трубопроводом і має в цьому прикладі 3 ступені. Сучасні процесори мають трубопроводи з більш ніж 15 ступенями. Однак, крім того, як і більшість арифметичних операцій, як правило, виконується в один етап (я кажу про операцію додавання 2 чисел АЛУ, а не про саму інструкцію - залежно від архітектури процесора, інструкція може вимагати більше циклів для отримання аргументів із пам'яті, виконання умов, збереження результатів у пам'яті).

Тривалість циклу визначається найдовшим критичним шляхом. В основному, це найдовша кількість часу, необхідна для завершення певного етапу трубопроводу. Якщо ви хочете зробити процесор швидшим, вам необхідно оптимізувати критичний шлях. Якщо зменшити критичний шлях як такий не представляється можливим, його можна розділити на 2 етапи конвеєра, і тепер ви зможете тактирувати ваш процесор майже вдвічі більше частоти (якщо припустити, що немає іншого критичного шляху, який заважає вам це робити ). Але це відбувається із накладними витратами: вам потрібно вставити регістр між етапами трубопроводу. Це означає, що ви насправді не набираєте 2x швидкості (реєстру потрібен час для зберігання даних), і ви ускладнили весь дизайн.

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

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


3
Великі накладні витрати від більш довгих трубопроводів - це більше циклів, щоб відновитись від гілкової непередбачуваності! Витрата транзисторів на буфер даних між етапами в ці дні незначний. Навіть звичайний конвеєрний процесор повинен бути витягнутим / розшифровувати до початку інструкцій, які фактично виконуються. Якщо процесор виявить, що фронт працював над неправильним кодом, оскільки філія пішла іншим шляхом, ніж було передбачено (або якась інша помилка), вона повинна викинути цю роботу і почати з правильної інструкції. Все погіршується лише із суперскалярними процесорами, що вийшли з ладу, у яких може виникнути багато неповноцінних польотів.
Пітер Кордес

12

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

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

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


Коментарі не для розширеного обговорення; ця розмова перенесена в чат .
Жиль

1
Короткий зміст обговорення: деякі процесори поза замовленнями обробляють MOV спеціально з перейменуванням реєстру з ефективно нульовою затримкою. Див. Чи може MOV x86 дійсно бути "безкоштовним"? Чому я взагалі не можу це відтворити? для отримання детальної інформації про те, що MOV дійсно коштує.
Пітер Кордес

12

Додавання є досить важливим, щоб не довелося чекати, коли біт переносу пройде через 64-розрядний акумулятор: термін для цього - add-lookahead adder, і вони в основному є частиною 8-бітових процесорів (та їх ALU) і вище. Дійсно, сучасним процесорам, як правило, не потрібно більше часу на виконання для повного множення.


Множення цілого числа, безумовно, більша затримка та менша пропускна здатність, ніж ADD на x86. Але це вражаюче швидко, враховуючи, скільки суматорів потрібно, щоб створити швидкий множник: наприклад, для Intel з моменту Nehalem, і AMD з моменту Ryzen, 8/16/32/64-бітне скалярне цілочисельне множення - це 3 затримки циклу, з пропускною здатністю 1c (один повністю конвеєрний блок виконання). Це відстійно порівняно з пропускною здатністю 3 або 4 за такт, але дивовижно порівняно із затримкою IMUL 9 циклів в Intel Pentium P5. Речі схожі для SIMD: множення векторного int - це більша затримка та менша пропускна здатність, ніж додавання, але все ж швидко.
Пітер Кордес

Так що так, багаторазово коливалося набагато дорожче порівняно з іншими інструкціями, ніж зараз. Уникати цього вартістю понад 2 інструкції, як правило, не варто, а іноді навіть 2-інструкційний замінник не вартий цього (наприклад, із leaінструкцією shift + add ).
Пітер Кордес

9

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

(Інструкційний цикл, а не тактовий цикл - наприклад, 6502 займає мінімум два тактових цикли за інструкцію через те, що він не є конвеєрним та не має кеш-інструкцій)

Справжня концепція, яка вам може бути відсутня, - це критичний шлях : всередині мікросхеми найдовша операція, яка може бути виконана протягом одного циклу, на апаратному рівні диктує, наскільки швидко чіп може працювати.

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


Це не керовані користувачем побітові операції, але деякі інструкції щодо 8086 (наприклад, очищення прапора переривання ) зайняли менше циклів, ніж додавання цілого числа. Більш абстрактно, система RISC, де всі інструкції мають розмір одного слова, може використовувати простий двійковий лічильник для ПК, який був би набагато швидшим ланцюгом, ніж суматор загального призначення.
Марк

Додавання на лічильник програм, як правило, дуже просте порівняно з арифметичною інструкцією додавання, оскільки один з операндів невеликий (або розмір інструкції, або відносне зміщення стрибка, яке також обмежене розміром)
Бен Войгт

6502 був конвеєрним - він читав перший байт наступної інструкції протягом останнього циклу попереднього. В іншому випадку отримання / декодування / виконання було б принаймні три цикли.
gnasher729

8

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

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

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

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


7

Сучасні процесори тактовані: кожна операція займає деяку цілісну кількість тактових циклів. Конструктори процесора визначають тривалість тактового циклу. Тут є два міркування: одне - швидкість обладнання, наприклад, вимірюється як затримка одного NAND-шлюзу. Це залежить від використовуваної технології та від компромісів, таких як швидкість та енергоспоживання. Це не залежить від конструкції процесора. По-друге, дизайнери вирішують, що тривалість тактового циклу дорівнює n затримкам одного NAND-шлюзу, де n може бути 10, або 30, або будь-якого іншого значення.

Цей вибір n обмежує, наскільки складні операції можуть бути оброблені за один цикл. Будуть операції, які можна зробити за 16, але не за 15 затримок NAND. Отже, вибір n = 16 означає, що таку операцію можна виконати за цикл, вибір n = 15 означає, що її неможливо зробити.

Дизайнери вибрали так, що багато важливих операцій можна буде виконати за один чи, може, два-три цикли. n буде обрано локально оптимальним: Якщо ви замінили n на n-1, то більшість операцій було б трохи швидше, але деякі (ті, що дійсно потребують повних n затримок NAND), будуть повільнішими. Якщо мало операцій сповільниться, щоб загальне виконання програми було в середньому швидшим, то ви вибрали б n-1. Ви також могли вибрати n + 1. Це робить більшість операцій трохи повільнішими, але якщо у вас багато операцій, які неможливо виконати протягом n затримок, але їх можна виконати протягом n + 1 затримок, це зробить процесор загалом швидше.

Тепер ваше запитання: Додавання і віднімання настільки поширені операції, що ви хочете мати можливість виконувати їх за один цикл. Як результат, не має значення, що AND, OR тощо може виконати швидше: їм все одно потрібен цей один цикл. Звичайно, у блоку "обчислення" ІЛИ АБО і т. Д. Є багато часу, щоб скрутити великі пальці, але це не допоможе.

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

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


6

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

Я знаю, що побітові операції настільки швидкі на сучасних процесорах, оскільки вони можуть працювати 32 або 64 біти паралельно,

Це правда. Позначення CPU як "XX" біт зазвичай (не завжди) означає, що більшість його загальних структур (ширини реєстру, адреси RAM тощо) мають розмір XX біт (найчастіше "+/- 1" або колись). Що стосується вашого питання, ви можете сміливо припускати, що процесор з 32 або 64 бітами буде виконувати будь-які основні бітові операції з 32 або 64 бітами в постійний час.

тому бітові операції займають лише один тактовий цикл.

Цей висновок не обов'язково має місце. Особливо процесори з багатим набором інструкцій (google CISC проти RISC) можуть легко взяти більше одного циклу навіть для простих команд. За допомогою перемежування навіть симпатичні команди можуть розбиватися на fetch-exec-store з 3-ма тактовими годинниками (як приклад).

Однак додавання - це складна операція

Ні, додавання цілого числа - це проста операція; віднімання. Реалізувати добавники в повному обсязі обладнання дуже просто, і вони роблять їх так само миттєво, як основні бітові операції.

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

Він займе в 3-4 рази більше транзисторів, але порівняно з великою картиною, що нехтують.

Я був здивований, побачивши після простого еталону, що додавання відбувається точно так само швидко, як і будь-яка операція по побиттю (XOR, АБО, І т.д.). Хтось може пролити світло на це?

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

Якщо ви хочете додати більше біт, ніж ваша архітектура процесора, ви несете штраф за необхідність робити це поетапно. Але це на іншому рівні складності (рівень мови програмування, а не рівень складання / машинного коду). Це було поширеною проблемою в минулому (або сьогодні на невеликих вбудованих процесорах). Для ПК тощо їх 32 або 64 біта є достатніми для найбільш поширених типів даних, щоб вони почали стати точкою суперечки.


Цікаво відзначити, що зменшення витрат на додавання в часі від O (N) до O (sqrt (N)) не суттєво збільшує необхідну кількість транзисторів або складність маршрутизації (на кожному етапі потрібно лише дозволити одному провідниковій проводці прокрастись знизу , і потрібно провести додаткові етапи злиття sqrt (N). Часова вартість може бути зменшена до O (lgN) за рахунок транзисторів O (lgN), але в багатьох випадках може бути корисно обробити щось на зразок 64- бітове доповнення, як, наприклад, вісім 8-бітних додавань (з використанням перенаправлення sqrtN), об'єднаних з трьома шарами логіки злиття, а не як 64 1-розрядні додавання з шести шарами злиття.
supercat

Так, добавки досить прості. Що насправді вражає, це сучасні процесори x86 із повністю конвеєрним 64-розрядним цілочисельним множником 3-х циклів затримки . (наприклад imul rax, rcx, тривалість затримки 3с і пропускна здатність на 1с для сімейства Intel Sandybridge та AMD Ryzen). Навіть 64-бітове повне множення (даючи 128-бітовий результат у rdx: rax) має однакову затримку та пропускну здатність, але реалізується як 2 Uops (які працюють паралельно на різних портах). (Див. Agner.org/optimize щодо інструкційних таблиць та відмінний посібник з мікроарха).
Пітер Кордес

[add-with-carry] знаходиться на іншому рівні складності (рівень мови програмування, а не рівень складання / машинного коду . Це залежить від мови. Компілятор змінного струму, орієнтований на 16-бітний процесор, повинен створювати додавання / adc для вас під час його компіляції додавання двох uint32_tзначень. Це актуально і сьогодні для int64_t на 32-бітних цілях. AVR - це 8-бітний мікроконтролер RISC, тому для 32-розрядних цілих чисел потрібні 4 інструкції: godbolt.org/g/wre0fM
Пітер Кордес

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