Чому умовний переїзд не є вразливим щодо відмови прогнозування філій?


78

Прочитавши цю публікацію (відповідь на StackOverflow) (у розділі оптимізації), мені було цікаво, чому умовні переміщення не є вразливими до відмови передбачення гілок. Я знайшов статтю про умовні переїзди тут (PDF від AMD) . Також там вони заявляють про перевагу в експлуатації умовно. рухається. Але чому це? Я цього не бачу. На даний момент, коли оцінюється ця інструкція ASM, результат попередньої інструкції CMP поки не відомий.


7
До речі, вам може знатись, що з мого досвіду роботи з процесорами Intel Core2 і Core-i7, cmov не завжди виграє в продуктивності. У моїх тестах сама гілка була кращою, якщо рівень прогнозування був вище приблизно 99%. Це може здатися високим, але це досить поширене явище серед прогнозуючих галузей Intel. Зокрема, це трапляється з гілками-внутрішніми циклами: скажімо, гілка, яка повторюється 1000 разів, а в 999-й раз робить щось інше. Такий випадок завжди був би більш ефективним, використовуючи умовний стрибок, а не cmov.
jstine

1
На даний момент посилання PDF вимагає авторизації.
leewz

Для компілятора C ++ вони однакові: Дивіться додане зображення
Микола Трандафіл

1
@NikolaiTrandafil: Це повністю залежатиме від обраного вами компілятора, яких прапорів компіляції ви ввімкнули та цільової ISA.
Martijn Courteaux

Пов’язане: Чи вважається CMOVcc інструкцією розгалуження? - ні, це операція вибору ALU. Відповідь містить кілька посилань на деталі щодо компромісу продуктивності.
Пітер Кордес,

Відповіді:


64

Неправильно передбачені гілки дорогі

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

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

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

Ось чому неправильно передбачені гілки дорогі: вони коштують 15-20 циклів, які зазвичай невидимі через ефективний конвеєр інструкцій.

Умовний переїзд ніколи не буває дуже дорогим

Умовний хід не вимагає передбачення, тому він ніколи не може отримати цей штраф. Він має залежності від даних, як і звичайні інструкції. Насправді умовний перехід має більше залежностей даних, ніж звичайні інструкції, оскільки залежності даних включають як випадки “умова істина”, так і “умова помилка”. Після інструкції, яка умовно переходить r1до r2, вміст, r2здається, залежить як від попереднього значення, так r2і від r1. Добре передбачена умовна гілка дозволяє процесору робити більш точні залежності. Але для отримання даних залежностей зазвичай потрібен один-два цикли, якщо їм взагалі потрібен час.

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


1
Я погоджуюся з усім, що ви написали (або здається мені принаймні прийнятним), за винятком першого твердження. Чи можете ви пояснити, що центральний процесор виконуватиме три інструкції asm кожного циклу?
Martijn Courteaux

4
@MartijnCourteaux Типовий сучасний настільний процесор має всі етапи конвеєра, здатний обробляти близько 3 інструкцій, що в найкращому випадку призводить до 3 інструкцій / циклу. Наприклад, етап декодування може декодувати 16 байт інструкцій за цикл: це, як правило, 3 інструкції. Існує також достатньо одиниць виконання для обробки трьох незалежних інструкцій за один цикл. Деталі на agner.org/optimize/microarchitecture.pdf (до речі, відмінне посилання).
Паскаль Куок

@MartijnCourteaux Наприклад, сторінка 79: "Пропускна здатність решти конвеєра зазвичай становить 4 інструкції за тактовий цикл" (але ви майже ніколи не отримуєте теоретичних 4 інструкцій за цикл. Навіть 3 - це лише тоді, коли алгоритм дозволяє це і вимагає написаний, вирівняний вручну код для конкретної моделі процесора)
Паскаль Куок,

Отже, він може декодувати 4 інструкції, але обробляти 2 або 3 за один і той же цикл, залежно від того, наскільки нам пощастило з алгоритмом?
Martijn Courteaux

Він може декодувати лише 4 інструкції, якщо вони вміщуються в 16 байт, тож це залежить від того, наскільки вам пощастило з довжиною інструкцій, яка вам потрібна для початку. І він може виконати до 4, якщо у нього є всі необхідні одиниці, щоб виконати їх усі (якщо це ваша мета, для досягнення цього може знадобитися змішування обчислень з плаваючою крапкою та цілими числами), і якщо вхід одного не є результатом інший. Якщо ви дійсно зацікавлені, ви можете подивитися на цю техніку, щоб збільшити дрібнозернистий паралелізм, але я повинен попередити вас, що це рідко пришвидшує ситуацію на практиці: en.wikipedia.org/wiki/Software_pipelining
Паскаль Куок

48

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

cmov

    add     eax, ebx
    cmp     eax, 0x10
    cmovne  ebx, ecx
    add     eax, ecx

На даний момент, коли оцінюється ця інструкція ASM, результат попередньої інструкції CMP поки не відомий.

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

Наступну інструкцію можна навіть виконати перед цим cmov(у моєму прикладі це було б безпечно)

відділення

    add     eax, ebx
    cmp     eax, 0x10
    je      .skip
    mov     ebx, ecx
.skip:
    add     eax, ecx

У цьому випадку, коли декодер процесора побачить, je .skipйому доведеться вибрати, чи продовжувати попереднє отримання / декодування інструкцій або 1) з наступної інструкції, або 2) з цілі переходу. Процесор здогадається, що цієї умовної гілки не відбудеться, тому наступна інструкція mov ebx, ecxпіде в конвеєр.

Через пару циклів je .skipвиконується і береться гілка. Ось лайно! Зараз наш конвеєр містить кілька випадкових сміття, які ніколи не слід виконувати. Процесор повинен очистити всі кешовані інструкції і почати все з самого початку .skip:.

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


4
Я можу зрозуміти, що це, швидше за все, синтаксис Intel з кодом операції, адресою, джерелом, але було б чудово, якщо б ви прямо вказали свій стандарт складання.
Zan Lynx

18

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

Розглянемо цей приклад:

cmoveq edx, eax
add ecx, ebx
mov eax, [ecx]

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

Версія з розгалуженням може бути:

    jne skip
    mov edx, eax
skip:
    add ecx, ebx
    mov eax, [ecx]

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


2
Я можу зрозуміти, що це, швидше за все, синтаксис Intel з кодом операцій, призначенням, джерелом, але було б чудово, якби ви прямо вказали свій стандарт збірки.
Zan Lynx

3

Ви повинні прочитати їх. За допомогою Fog + Intel просто шукайте CMOV.

Критика Лінуса Торвальда щодо CMOV близько 2007 року
Агнер Фог порівняння мікроархітектур
Intel® 64 та IA-32 Довідковий посібник з оптимізації архітектур

Коротка відповідь, правильні прогнози є "безкоштовними", тоді як умовні неправильні прогнози можуть коштувати 14-20 циклів на Haswell. Однак CMOV ніколи не є безкоштовним. Проте я думаю, що CMOV зараз НАБАГАТО кращий, ніж тоді, коли Торвальдс побіг. Не існує жодної правильної за весь час відповіді на всі процесори.


3
Ні, cmovце як і раніше залежність даних, тому може створювати ланцюжки залежностей, що несуть цикл, які передбачення гілок приховували б. Intel Broadwell / Skylake розшифровують його до одного uop замість 2 (Haswell та раніше), тому зараз це трохи дешевше. Sandybridge та пізніший кеш uop означає, що покарання за пропускну здатність декодування для вправ multi-uop, як правило, теж не є фактором. Проте це не змінює принципової різниці між даними та залежністю управління. Крім того, x86 cmovвсе ще не має форми з безпосереднім операндом, тому x = x<3 ? x : 3все ще незграбний для реалізації.
Пітер Кордес,

1
Крім того, я думаю, у вас є помилка редагування в: "CMOV ніколи не буває дуже дорогим, якщо не передбачені гілки". Це речення є абсолютною нісенітницею, оскільки cmov не передбачається. Ось чому воно не може страждати від непередбачуваних прогнозів.
Пітер Кордес,

Щиро дякую за корисні посилання.
Максим Масютін

Ще одне посилання, яке може зацікавити: gcc.gnu.org/bugzilla/show_bug.cgi?id=56309
Макс

0

Я маю цю ілюстрацію зі слайда [Peter Puschner et al.], Яка пояснює, як вона трансформується в єдиний код шляху, і пришвидшує виконання.

введіть тут опис зображення


1
Інструкція порівняння-і-предикат-наступна була б непоганою, але справжні архітектури, як правило, потребували б 3 інструкції і для попередньої послідовності. ( За винятком ARM 32-біт, який може cmp/ swplt, якби він мав своп / аероплан обміну.) У всякому разі, сучасні процесори не зазвичай мають бульбашок з прийнятих гілок, у них є пляшечки зі mispredicts : stackoverflow.com/questions/11227809 / ... . У високопродуктивному коді, правильно передбачені прийняті гілки можуть дещо зменшити декодування / пропускну здатність інтерфейсу.
Пітер Кордес,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.