"IF" дорого?


98

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

Модуль "Структури даних та алгоритми", і він сказав нам щось на зразок:

ifЗатвердження є найдорожчим [що - то]. [щось] реєструє [щось].

Так, у мене є жахлива пам’ять, і я справді дуже шкодую, але я гуглив годинами, і нічого не з’явилося. Будь-які ідеї?


29
Запитати свого вчителя варіант?
Майкл Майерс

7
Чому б вам не надіслати електронною поштою своєму вчителю? Навряд чи хтось із ТО знає, що сказав твій вчитель, якщо тільки він не був там у той час (або твій вчитель сам не читає ТО).
Білл Карвін,

11
І звичайно посилання на обов’язкову залізничну відповідь
bobobobo

Якщо оператори або особливо "?:" Вирази у мовах, що мають вплив на C, фігурні дужки можуть бути реалізовані за допомогою спеціальних інструкцій умовного виконання, наприклад, на процесорах x86 та arm. Це інструкції, які роблять або не роблять певної операції на основі попереднього тесту. Використання цих чудових інструкцій дозволяє уникнути необхідності в умовних інструкціях щодо переходу / гілки / переходу. Величезне покращення продуктивності в деяких ситуаціях, завдяки чому потік програми стає цілком передбачуваним, оскільки він просто орієнтується прямо, не маючи (можливо, непередбачуваного) стрибка в різні точки коду.
Сесіл Уорд,

Хорошому компілятору іноді може знадобитися трохи натискання у правильному напрямку, щоб він використовував умовні вказівки замість того, щоб бути німими та використовуючи умовні стрибки, шляхом реорганізації коду та, можливо, використовуючи розумну арифметику у виразі чи a? : вираз. Не грайте з цим, якщо ви насправді не знаєте свого asm і не прочитали, наприклад, посібники з оптимізації Agner Fog. Іноді компілятори все правильно розуміють, незалежно від того, чи це твердження чи? : використовуються вирази.
Сесіл Уорд

Відповіді:


185

На найнижчому рівні (в апаратному забезпеченні), так, якщо s дорогі. Щоб зрозуміти, чому, ви повинні зрозуміти, як працюють трубопроводи .

Поточна інструкція, що виконується, зберігається у щось, що зазвичай називається покажчиком інструкцій (IP) або програмним лічильником (ПК); ці терміни є синонімами, але різні терміни використовуються в різних архітектурах. Для більшості інструкцій ПК наступної інструкції - це лише поточний ПК плюс довжина поточної інструкції. Для більшості архітектур RISC інструкції мають постійну довжину, тому ПК можна збільшити на постійну суму. Для архітектур CISC, таких як x86, інструкції можуть бути змінної довжини, тому логіка, яка декодує інструкцію, повинна з’ясувати, як довго поточна інструкція знаходить місце розташування наступної інструкції.

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

Умовний проти безумовного легко зрозуміти - умовна гілка береться лише за умови виконання певної умови (наприклад, чи однакове число дорівнює іншому); якщо гілка не береться, контроль переходить до наступної інструкції після гілки, як звичайно. Для безумовних гілок завжди береться гілка. Умовні гілки відображаються в ifоператорах та контрольних тестах forі whileциклів. Безумовні гілки відображаються у нескінченних циклах, викликах функцій, поверненнях функцій breakта continueоператорах, сумнозвісному gotoтвердженні та багатьох інших (ці списки далеко не вичерпні).

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

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

Таким чином, причина того, що виписки є дорогими, пов’язана з неправильними прогнозами галузей . Це лише на найнижчому рівні. Якщо ви пишете код високого рівня, вам зовсім не потрібно турбуватися про ці деталі. Вам слід піклуватися про це, лише якщо ви пишете надзвичайно важливий для продуктивності код на мові C або в збірці. У цьому випадку написання коду без гілок часто може перевершувати код, який гілкується, навіть якщо потрібні ще кілька інструкцій. Є деякі цікаві бітові-крутив трюки , які ви можете зробити , щоб обчислити такі речі, як abs(), min()і max()без розгалуження.


20
Це не просто неправильні прогнози галузі. Гілки також перешкоджають впорядкуванню інструкцій на рівні компілятора, а також певною мірою на рівні центрального процесора (звичайно, для непрацюючого ЦП). Хороша детальна відповідь.
jalf

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

18

"Дорогий" - це дуже відносний термін, особливо стосовно " if" виписки, оскільки ви також повинні враховувати вартість стану. Це може варіюватися від кількох коротких інструкцій процесора до тестування результату функції, яка викликає віддалену базу даних.

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


1
Безумовно відносно ... cmp / cond jmp все-таки швидше, ніж mul на багатьох процесорах.
Брайан Ноблауч,

4
Так, я згоден, що мене це не повинно турбувати. Я не намагаюся щось оптимізувати тут. Я просто намагаюся це дізнатись і навчитися. ;)
пек,

15

Гілки, особливо на мікропроцесорах архітектури RISC, є одними з найдорожчих інструкцій. Це пояснюється тим, що у багатьох архітектурах компілятор передбачає, який шлях виконання буде скоріш за все скоріше, і поміщає ці інструкції далі у виконуваний файл, тому вони вже будуть у кеші центрального процесора, коли відбудеться гілка. Якщо гілка йде іншим шляхом, вона повинна повернутися до основної пам’яті та отримати нові інструкції - це досить дорого. У багатьох архітектурах RISC всі інструкції складають один цикл, крім розгалуження (що часто становить 2 цикли). Тут ми не говоримо про великі витрати, тому не турбуйтеся про це. Крім того, компілятор оптимізує краще, ніж ви робите в 99% випадків: ) Однією з дійсно приголомшливих речей в архітектурі EPIC (на прикладі Itanium) є те, що вона кешує (і починає обробляти) інструкції з обох сторін гілки, а потім відкидає набір, який їй не потрібний, коли результат гілки буде відомі. Це економить додатковий доступ до пам'яті типової архітектури в тому випадку, якщо вона розгалужується по непередбачуваному шляху.


13

Перегляньте статтю Краща ефективність за рахунок усунення гілок щодо ефективності клітин. Ще однією цікавою є ця публікація про вибір без гілок у блозі виявлення зіткнень у реальному часі.

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

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


Справді! Можна також додати, що при кодуванні мовою, яка заохочує дзвінки (в основному, що-небудь, крім асемблера або C без stdlib), перешкоди конвеєра від звичайних методів програмування перевершать будь-які питання щодо умовного розгалуження.
Росс Паттерсон,

10

ifсама по собі не повільна. Повільність завжди є відносною. Я ставив за своє життя, що ви ніколи не відчували "накладних витрат" висловлювання. Якщо ви збираєтеся створити високопродуктивний код, ви все одно хочете уникати гілок. Що робить ifповільним, так це те, що процесор попередньо завантажує код після після, ifбазуючись на деякій евристиці та чомусь іншому. Це також зупинить конвеєри від виконання коду безпосередньо після ifвказівки гілки в машинному коді, оскільки процесор ще не знає, який шлях буде пройдений (у конвеєрному процесорі кілька інструкцій чергуються та виконуються). Виконаний код може бути виконаний у зворотному порядку (якщо була взята інша гілка. Це називається branch misprediction), абоnoop заповнюватися там, щоб цього не сталося.

Якщо ifє зло, то switchзло теж, і &&, ||теж. Не хвилюйтеся з цього приводу.


7

На найнижчому з можливих рівнів ifскладається (після обчислення всіх передумов для конкретного додатка if):

  • деякі інструкції до тесту
  • перейти в якесь місце в коді, якщо тест вдався, інакше рухайтеся вперед.

Витрати, пов'язані з цим:

  • порівняння на низькому рівні - зазвичай це 1 процесор, надзвичайно дешево
  • потенційний стрибок - що може бути дорогим

Причина, чому стрибки дорогі:

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

Отже, підсумовуємо:

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

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

7

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

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

Пентіум 4 (Прескотт) мав знаменитий довгий трубопровід з 31 ступеня.

Більше на Вікіпедії


3
+1 для метафори товарного поїзда - я пам’ятаю, що наступного разу мені потрібно пояснити конвеєри процесорів.
Даніель Приден

6

Можливо, розгалуження вбиває попереднє завантаження інструкцій процесора?


Під час мого ... "дослідження" я дізнався про таблиці переходів та розгалуження для операторів switch, але нічого про оператори if. Не могли б Ви трохи детальніше це описати?
пек,

IIRC, центральний процесор, як правило, попередньо отримує інструкції по одному ймовірному шляху виконання, але оператор if, який викликає гілку із передбаченого шляху виконання, призведе до недійсності попередньо завантажених інструкцій, і попередження потрібно буде перезапустити.
activout.se

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

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

6

Також зверніть увагу, що всередині циклу не обов’язково дуже дорого.

Сучасний центральний процесор припускає, що під час першого відвідування твердження if слід взяти "if-body" (або сказати по-іншому: він також передбачає, що тіло циклу береться кілька разів) (*). Під час другого та наступних відвідувань, він (ЦП) може заглянути в таблицю історії відгалужень і побачити, як це було востаннє (чи правда це було, чи було воно помилковим?). Якщо останнього разу воно було хибним, тоді спекулятивне виконання перейде до "іншого" пункту if або за цикл.

(*) Правилом є насправді " пряма гілка не взята, зворотна гілка взята ". У операторі if є лише стрибок [вперед] (до точки після if-body ), якщо умова оцінюється як false (пам’ятайте: центральний процесор у будь-якому випадку припускає, що не бере гілку / стрибок), але в циклі , можливо, є пряма гілка до позиції після циклу (не береться) і зворотна гілка при повторенні (береться).

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


5

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

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


4

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

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

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


Я чув, що умовні вказівки ARM гальмують ІЛП, тому вони можуть просто підштовхувати проблему.
JD

3

Процесори глибоко конвеюються. Будь-яка інструкція з розгалуження (if / for / while / switch / etc) означає, що центральний процесор насправді не знає, яку інструкцію завантажувати та запускати далі.

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

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

Удачі в класі.

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


2

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


1

Деякі процесори (наприклад, X86) забезпечують передбачення гілок до рівня програмування, щоб уникнути такої затримки передбачення гілок.

Деякий компілятор виставляє (як GCC) їх як розширення до мов програмування вищого рівня (наприклад, C / C ++).

Перегляньте макроси ймовірних () / малоймовірних () у ядрі Linux - як вони працюють? Яка їх користь? .


0

Я якось посперечався з одним своїм другом. Він використовував дуже наївний алгоритм кола, але стверджував, що він швидший за мій (той, який обчислює лише 1/8 окружності кола), оскільки мій використовував if. Зрештою, оператор if було замінено на sqrt, і це якось швидше. Можливо, тому, що в FPU вбудовано sqrt?


-1

Найдорожчий з точки зору використання ALU? Він використовує регістри ЦП для зберігання значень, що підлягають порівнянню, і займає час для отримання та порівняння значень кожного разу, коли виконується оператор if.

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

Просто намагаюся інтерпретувати ваші відсутні слова.

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