Які опкоди швидші на рівні процесора? [зачинено]


19

У кожній мові програмування є набори опкодів, які рекомендуються над іншими. Я намагався перерахувати їх тут, в порядку швидкості.

  1. Побітові
  2. Ціле додавання / віднімання
  3. Множення / Відділення цілого числа
  4. Порівняння
  5. Контрольний потік
  6. Поплавкове додавання / віднімання
  7. Помноження на поплавці / Відділення

Там, де вам потрібен високоефективний код, C ++ можна оптимізувати в зборі, використовувати інструкції SIMD або більш ефективний потік управління, типи даних тощо. Тому я намагаюся зрозуміти, чи тип даних (int32 / float32 / float64) або операція використовується ( *, +, &) впливає на продуктивність на рівні процесора.

  1. Чи одне множення повільніше у процесорі, ніж додавання?
  2. У теорії MCU ви дізнаєтесь, що швидкість дії коду визначається кількістю циклів процесора, необхідних для виконання. Так це означає, що множення займає 4 цикли, а додавання займає 2?
  3. Які саме швидкісні характеристики основних математичних та контрольних потоків використовуються?
  4. Якщо два опкоди мають однакову кількість циклів для виконання, то обидва можна використовувати взаємозамінно без будь-якого посилення / втрати продуктивності?
  5. Вдячні за будь-які інші технічні деталі, якими ви можете поділитися щодо продуктивності процесора x86

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

3
Помноження та поділ поплавця - це абсолютно різні речі, не слід ставити їх до однієї категорії. Для n-бітних чисел множення є процесом O (n), а ділення - процесом O (nlogn). Це робить поділ приблизно в 5 разів повільніше, ніж множення на сучасних процесорах.
sam hocevar

1
Єдиною реальною відповіддю є "профіль".
Тетрад

1
Розширюючись на відповідь Роя, складання оптимізації вручну майже завжди буде чистим збитком, якщо ви насправді не виняткові. Сучасні процесори - це дуже складні звіри і хороші оптимізатори компіляторів витягують кодові трансформації, які абсолютно не очевидні і не банальні для кодування вручну. Навіть для SSE / SIMD завжди використовуйте внутрішні тексти C / C ++, і дозвольте компілятору оптимізувати їх використання для вас. Використання необробленої збірки вимикає оптимізацію компілятора, і ви втрачаєте великі.
Шон Міддлічч

Не потрібно вручну оптимізувати збірку, щоб використовувати SIMD. SIMD дуже корисна для оптимізації залежно від ситуації, але існує здебільшого стандартна конвенція (вона працює як мінімум для GCC та MSVC) для використання SSE2. Що стосується вашого списку, то на сучасному багатоканальному процесорі надмісячних значень залежність даних та тиск у регістрі викликають більше проблем, ніж вихідні цілі чисельні, а іноді й показники з плаваючою комою; те саме стосується місцевості даних. До речі, ціле ділення те саме, що множення на сучасний х86
OrgnlDave

Відповіді:


26

Посібники з оптимізації Agner Fog чудові. У нього є путівники, таблиці термінів інструкцій та документи з мікроархітектури всіх останніх процесорів x86 процесора (починаючи від Intel Pentium). Дивіться також деякі інші ресурси, пов’язані з /programming//tags/x86/info

Для задоволення я відповім на деякі запитання (номери з останніх процесорів Intel). Вибір опції не є головним фактором оптимізації коду (якщо ви не зможете уникнути поділу.)

Чи одне множення повільніше у процесорі, ніж додавання?

Так (якщо це не сила 2). (3–4 рази затримка, лише одна на пропускну здатність в Intel.) Не виходьте далеко з шляху, щоб уникнути цього, оскільки це так швидко, як 2 або 3 додає.

Які саме швидкісні характеристики основних математичних та контрольних протоколів потоку?

Дивіться таблиці інструкцій Agner Fog та посібник з мікроархітектури, якщо ви хочете точно знати : P. Будьте обережні при умовних стрибках. Безумовні стрибки (як виклики функцій) мають невеликі накладні витрати, але не дуже.

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

Ні, вони можуть змагатися за той самий порт виконання, що і щось інше, а можуть і ні. Це залежить від того, над якими ланцюгами залежностей CPU може працювати паралельно. (На практиці зазвичай не може бути прийнято жодного корисного рішення. Іноді з'являється, що ви можете використовувати зсув вектора або перетасування векторів, які працюють у різних портах процесорів Intel. Але зміна байтів всього реєстру ( PSLLDQі т. д.) працює в блоці переміщення.)

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

Документи мікроарха Agner Fog описують конвеєри процесорів Intel та AMD досить докладно, щоб точно визначити, скільки циклів повинно пройти цикл за ітерацію та чи є вузьке місце загальною пропускною здатністю, ланцюгом залежності або суперечкою для одного порту виконання. Дивіться деякі мої відповіді на StackOverflow, на кшталт цієї чи цієї .

Крім того, http://www.realworldtech.com/haswell-cpu/ (і подібні до попередніх конструкцій) цікаво читати, якщо вам подобається дизайн процесора.

Ось ваш список, відсортований за процесором Haswell, на основі моїх найкращих оцінок. Це насправді не корисний спосіб роздуму над речами ні для чого, окрім налаштування циклу зору. Ефекти прогнозування кешу / гілки зазвичай домінують, тому пишіть свій код, щоб мати гарні зразки. Числа дуже ручні, і намагайтеся врахувати високу затримку, навіть якщо пропускна здатність не є проблемою, або для отримання більшої кількості Uops, які засмічують трубу, щоб інші речі паралельно відбувалися. Esp номери кешу / гілки дуже складні. Затримка має значення для петельних залежностей, пропускна здатність має значення, коли кожна ітерація є незалежною.

TL: DR: ці цифри складаються з урахуванням того, що я малюю для "типового" випадку використання, що стосується компромісів між затримками, вузькими місцями у виконанні портів та пропускною спроможністю (або стійлами для речей, таких як пропуски відділення ). Будь ласка, не використовуйте ці номери для будь-якого серйозного аналізу персоналу .

  • 0,5 - 1 побітове / додавання цілих чисел / віднімання /
    зсув та обертання (кількість компіляцій часу / компіляції) /
    векторні версії всіх цих (1 до 4 за пропускну здатність циклу, 1 затримка циклу)
  • 1 вектор хв, макс, порівняння-рівне, порівняння-більший (для створення маски)
  • 1,5 переміщення вектор. У Haswell і новіших є лише один порт перетасовки, і мені здається, що зазвичай потрібно багато перетасувати, якщо вам це потрібно, тому я зважую його трохи вище, щоб заохотити думати про використання меншої кількості перетасовок. Вони не вільні, особливо. якщо вам потрібна маска керування pshufb з пам'яті.
  • 1,5 завантаження / зберігання (потрапляння кешу L1. Пропускна здатність краща за затримку)
  • 1.75 Множення цілого числа (затримка 3c / один на 1c tput в Intel, 4c lat на AMD і лише один на 2c tput). Невеликі константи навіть дешевші за допомогою LEA та / або ADD / SUB / shift . Але звичайно константи часу компіляції завжди хороші і часто можуть оптимізуватися в інші речі. (А множення в циклі часто компілятором може бути зменшено міцність, tmp += 7а не циклічно tmp = i*7)
  • 1.75 деякі перемикання 256b у векторному режимі (додаткова затримка в інтернах, які можуть переміщувати дані між 128b смугами AVX-вектора). (Або від 3 до 7 на Ryzen, де перетасовка смуги, що перетинає смугу руху, потребує набагато більше Uops)
  • 2 fp add / sub (та векторні версії того самого) (1 або 2 за цикл, затримка 3 - 5 циклів). Може бути повільним, якщо ви обмежуєте затримку, наприклад, підсумовуючи масив лише з 1 sumзмінною. (Я міг би зважити це і fp mul на рівні від 1 або до 5, залежно від випадку використання).
  • 2 векторні fp mul або FMA. (x * y + z коштує так само дешево, як або mul або додавання, якщо ви компілюєте з увімкненою підтримкою FMA).
  • 2 вставлення / вилучення регістрів загального призначення у векторні елементи ( _mm_insert_epi8тощо)
  • 2,25 векторного int mul (16-бітні елементи або pmaddubsw роблять 8 * 8 -> 16-бітові). Дешевше на Skylake, з кращою пропускною здатністю, ніж скалярний мул
  • 2,25 зсув / обертання за змінним підрахунком (2c затримка, один на 2c пропускної здатності в Intel, швидше в AMD або з BMI2)
  • 2.5 Порівняння без розгалуження ( y = x ? a : bабо y = x >= 0) ( test / setccабо cmov)
  • 3 int-> float перетворення
  • 3 ідеально передбачуваний потік управління (передбачувана гілка, виклик, повернення).
  • 4 векторні int mul (32-бітні елементи) (2 Uops, 10c затримка на Haswell)
  • 4 ціле ділення або %константа часу компіляції (не-потужність 2).
  • 7 векторних горизонтальних опцій (наприклад, PHADDдодавання значень у вектор)
  • 11 (вектор) FP-відділення (затримка 10-13c, одна на пропускну здатність 7c або гірше). (Може бути дешевим, якщо використовується рідко, але пропускна здатність на 6 - 40 разів гірша, ніж FP муль)
  • 13? Контрольний потік (погано прогнозована гілка, можливо 75% передбачувана)
  • 13 int поділ ( так дійсно , він повільніше, ніж поділ FP, і не може векторизувати). (зауважте, що компілятори діляться на постійну, використовуючи mul / shift / add з магічною константою , а div / mod потужностями 2 дуже дешево.)
  • 16 (вектор) FP sqrt
  • 25? завантаження (хіт L3 кеша). (магазини кеш-міс коштують дешевше, ніж вантажі.)
  • 50? FP trig / exp / log. Якщо вам потрібно багато exp / log і не потрібна повна точність, ви можете торгувати точністю для швидкості за допомогою коротшого полінома та / або таблиці. Можна також SIMD-векторизацію.
  • 50-80? завжди - передбачувана галузь, вартістю 15-20 циклів
  • 200-400? завантажувати / зберігати (пропустити кеш)
  • 3000 ??? читати сторінку з файлу (хіт кешу диска ОС) (тут складаються номери)
  • 20000 ??? сторінка для читання диска (пропуск кеш-диска ОС, швидкий SSD) (повністю складений номер)

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

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

Майте на увазі, що звести речі до одновимірної вартості дійсно неможливо . Окрім кеш-пропусків та помилок гілок, вузьким місцем у блоці коду може бути затримка, загальна пропускна здатність (frontend) або пропускна здатність певного порту (порт виконання).

"Повільна" операція, як поділ FP, може бути дуже дешевою, якщо навколишній код заважає процесора займатися іншою роботою . (вектор FP div або sqrt - це 1 взагалі кожен, у них просто погана затримка та пропускна здатність. Вони блокують лише одиницю поділу, а не весь порт виконання, на якому він працює. Integer div - це кілька упп.) Отже, якщо у вас є лише один поділ FP на кожні ~ 20 мкм і додайте, і для роботи процесора є інша робота (наприклад, незалежна ітерація циклу), тоді "вартість" FP div може бути приблизно такою самою, як муль FP. Це, мабуть, найкращий приклад чогось із низькою пропускною здатністю, коли це все ви робите, але дуже добре поєднується з іншим кодом (коли затримка не є фактором) через низьку загальну кількість Uops.

Зауважте, що ціле ділення не є настільки сприятливим для оточуючого коду: у Haswell це 9 уп, один на пропускну здатність 8-11c та затримку 22-29c. (64-бітний поділ набагато повільніше, навіть у Skylake.) Отже, затримка та пропускна здатність дещо схожі на FP div, але FP div - лише один загалом.

Для прикладів аналізу короткої послідовності інсів для пропускної здатності, затримки та загальної величини UPS, див. Деякі мої відповіді:

IDK, якщо інші люди пишуть відповіді ТА, включаючи подібний аналіз. Мені набагато простіше шукати свій власний час, бо я знаю, що часто впадаю в цю деталь, і я пам'ятаю, що я написав.


"Прогнозована гілка" на 4 має сенс - якою має бути насправді "передбачувана гілка" на 20-25? (Я думав, що неправильно передбачувані гілки (перераховані близько 13) набагато дорожчі за це, але саме тому я перебуваю на цій сторінці, щоб дізнатися щось ближче до істини - дякую за чудовий стіл!)
Метт

@Matt: Я думаю, що це була помилка редагування, і вона мала бути "непередбачуваною гілкою". Дякуємо, що вказали на це. Зауважте, що 13 - це недосконало передбачена гілка, а не завжди непередбачувана гілка, тому я уточнив це. Я знову зробив рукоділля і зробив деякі зміни. : P
Пітер Кордес

16

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

  1. Розрядне додавання, віднімання, порівняння, множення
  2. Відділ
  3. Контрольний потік (див. Відповідь 3)

Залежно від процесора може бути значна плата за роботу з 64-ти бітовими типами даних.

Ваші запитання:

  1. Зовсім ні чи не помітно на сучасному процесорі. Залежить від процесора.
  2. Ця інформація є чимось на зразок 20 - 30 років застарілою (шкода відстій, тепер у вас є доказ), сучасні процесори обробляють різну кількість інструкцій на добу, скільки залежить від того, що планувач придумає.
  3. Ділення трохи повільніше, ніж решта, контрольний потік дуже швидкий, якщо прогноз гілки правильний, і дуже повільний, якщо він неправильний (щось на зразок 20 циклів, залежить від процесора). Результат полягає в тому, що багато коду обмежується переважно контрольним потоком. Не робіть того, ifщо ви розумно можете зробити з арифметикою.
  4. Немає фіксованого числа, скільки циклів займає будь-яка інструкція, але іноді дві різні інструкції можуть виконувати однаково, розміщувати їх в іншому контексті, а може, і не робити, запускати їх на іншому процесорі, і ви, ймовірно, побачите 3-й результат.
  5. Крім потоку управління, інша велика витрата часу втрачає кеш, коли ви намагаєтесь прочитати дані, які не знаходяться в кеші, процесору доведеться чекати, коли він вийде з пам'яті. Як правило, ви повинні намагатися одночасно обробляти дані один до одного, а не збирати дані з усього місця.

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


Я також хотів би зазначити, що FPU досить чортово швидкий: особливо в Intel - так що фіксована точка справді потрібна лише в тому випадку, якщо ви хочете детермінованих результатів.
Джонатан Дікінсон

2
Я хотів би зробити більше уваги на останній частині - зробити гарну гру. Це допомагає чітко визначити код - ось чому 3. застосовується лише тоді, коли ви фактично вимірюєте проблему з продуктивністю. Завжди легко змінити ті, якщо вони є необхідними, на щось краще. З іншого боку, 5. хитріше - я безумовно погоджуюся, що це випадок, коли ви дійсно хочете подумати спочатку, оскільки це зазвичай означає зміну архітектури.
Луань

3

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

додати --- 116 мікросекунд

sub ---- 116 мікросекунд

mul ---- 1036 мікросекунд

div ---- 13037 мікросекунд

наведені вище дані вже зменшили накладні витрати, викликані циклом,


2

Інструкції з процесорів Intel - це безкоштовне завантаження з їх веб-сайту. Вони досить великі, але технічно можуть відповісти на ваше запитання. Зокрема, ви користуєтеся посібником з оптимізації, але інструкція з експлуатацією має також терміни та затримки для більшості основних ліній CPU для simd-інструкцій, оскільки вони змінюються від чіпа до чіпа.

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


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