Чи можна сказати провіснику гілок, наскільки ймовірно, що він стежить за гілкою?


74

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

В основному, у мене є твердження if, яке 99% часу оцінить як істинне, і я намагаюся вибити кожен останній годинник продуктивності, чи можу я виконати якусь команду компілятора (використовуючи GCC 4.1.2 та x86 ISA, якщо це важливо) сказати предиктору гілки, що він повинен кешувати цю гілку?


12
Компіляція з керованою оптимізацією профілю (-fprofile -generate, запуск на деяких тестових даних, -fprofile-use). Тоді gcc знатиме статистику для кожної гілки і матиме змогу оптимально розкласти код для швидкого шляху. Але вбудований_експект все ще є гарною ідеєю для місць, де це допоможе, якщо код скомпільований без PGO. Ядро Linux має кілька хороших макросів (наприклад, ймовірний () та малоймовірний ()) для цього, оскільки важко генерувати дані профілю для ядра.
Пітер Кордес

Відповіді:


60

Так. http://kerneltrap.org/node/4705

Це __builtin_expectметод, який gcc (версії> = 2.96) пропонує програмістам вказувати компілятору інформацію про передбачення гілок. Повернене значення __builtin_expect- це перший аргумент (який може бути лише цілим числом), переданий йому.


9
У середовищах Microsoft, якщо передбачається, що твердження завжди відповідають дійсності. Деякі версії мають оптимізовану керовану профілем.
Чарльз Бітті


75

Так, але це буде НЕ ефект. Виняток становлять старі (застарілі) архітектури до Netburst, і навіть тоді це не робить нічого вимірюваного.

Існує "підказка про розгалуження" коду Intel, представлений в архітектурі Netburst, і статичне прогнозування гілок за замовчуванням для стрибків холоду (прогнозований назад прийнятий, прогнозований вперед не прийнятий) на деяких старих архітектурах. GCC реалізує це за допомогою __builtin_expect (x, prediction), де передбачення, як правило, дорівнює 0 або 1. Опкод, виданий компілятором, ігнорується на всіх нових архітектурах процесорів (> = Core 2). Маленький кутовий випадок, коли це насправді щось робить, це випадок холодного стрибка старої архітектури Netburst. Intel рекомендує зараз не використовувати натяки на статичні гілки, можливо, тому, що вони вважають збільшення розміру коду більш шкідливим, ніж можливе граничне прискорення.

Окрім марної підказки гілки для провісника, __builtin_expect яка використовується, компілятор може змінити порядок коду, щоб покращити використання кешу або заощадити пам'ять.

Є кілька причин, чому це не працює належним чином.

  • Процесор може чудово передбачити невеликі цикли (n <64).
  • Процесор може чудово прогнозувати невеликі повторювані шаблони (n ~ 7).
  • Сам процесор може оцінити ймовірність гілки під час виконання краще, ніж компілятор / програміст під час компіляції.
  • Прогнозованість (= ймовірність філія отримає передбачила правильно) галузь є набагато більш важливим , ніж ймовірність того, що гілка береться. На жаль, це сильно залежить від архітектури, і передбачити передбачуваність гілки, як відомо, важко.

Докладніше про внутрішні роботи передбачення галузей читайте в посібниках Agner Fogs . Див. Також список розсилки gcc .


3
Було б непогано, якби ви могли процитувати / вказати точну частину, де сказано, що підказка ігнорується в нових архітектурах.
int3

6
Глава 3.12 "Статичне передбачення" за посиланням, яке я дав.
Gunther Piez

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

31

Pentium 4 (він же мікроархітектура Netburst) мав підказки для передбачення гілок як префікси до інструкцій jcc, але лише P4 ніколи з ними нічого не робив. Див. Http://ref.x86asm.net/geek32.html . І Розділ 3.5 чудового посібника для вибору асоціації Agner Fog з http://www.agner.org/optimize/ . У нього також є посібник з оптимізації на C ++.

Раніше та пізніші процесори x86 мовчки ігнорують ці префікс-байти. Чи є результати тесту продуктивності для використання ймовірних / малоймовірних підказок? згадує, що PowerPC має деякі інструкції щодо переходу, які мають підказку про передбачення гілок як частину кодування. Це досить рідкісна архітектурна особливість. Статично передбачити гілки під час компіляції дуже важко зробити точно, тому зазвичай краще залишити це на апаратному забезпеченні, щоб це зрозуміти.

Офіційно опубліковано не так багато інформації про те, як саме поводяться провісники гілок та буферні цільові буфери в останніх процесорах Intel і AMD. Посібники з оптимізації (які легко знайти на веб-сайтах AMD та Intel) дають деякі поради, але не документують конкретної поведінки. Деякі люди проводили тести, щоб спробувати зрозуміти реалізацію, наприклад, скільки записів BTB має Core2 ... У будь-якому випадку, ідея явного натякання на предиктор відмовлена ​​(на даний момент).

Документовано, наприклад, що Core2 має буфер історії гілок, який дозволяє уникнути неправильного прогнозування циклу-виходу, якщо цикл завжди виконує постійну коротку кількість ітерацій, <8 або 16 IIRC. Але не поспішайте розгортатись, тому що цикл, який вміщується в 64 байти (або 19uops на Penryn), не матиме вузьких місць для вибору інструкцій, оскільки він повторюється з буфера ... ідіть, читайте pdfs Агнера Фога, вони чудові .

Див. Також Чому за ці роки Intel змінила механізм прогнозування статичних гілок? : Intel, оскільки Sandybridge взагалі не використовує статичне прогнозування, наскільки ми можемо зрозуміти з експериментів з продуктивністю, які намагаються здійснити зворотне проектування того, що роблять центральні процесори. (Багато старих центральних процесорів мають статичне передбачення як запасний варіант, коли динамічне передбачення пропускає. Звичайним статичним прогнозуванням є прямі гілки, які не беруться, а зворотні гілки беруться (тому що зворотні гілки часто є гілками циклу).)


Ефект likely()/ unlikely()макросів із використанням GNU C __builtin_expect(як згадується відповідь Дракоші) не вводить безпосередньо підказки BP в asm . (Можливо, це можна зробити з gcc -march=pentium4, але не під час компіляції для чогось іншого).

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

Див. Яка перевага __builtin_expect GCC у твердженнях if else? для конкретного прикладу code-gen.

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

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


Оптимізація за профілем GCC зазвичай робить імовірні / малоймовірні макроси непотрібними. Компілятор збирає дані про час виконання, яким шляхом кожна гілка йшла для прийняття рішень щодо розміщення коду, а також для ідентифікації гарячих та холодних блоків / функцій. (наприклад, це буде розгортати петлі в гарячих, але не в холодних функціях.) Див. -fprofile-generateта -fprofile-use в посібнику GCC . Як використовувати оптимізовані керовані профілем в g ++?

В іншому випадку GCC повинен вгадати, використовуючи різні евристики, якщо ви не використовували ймовірні / малоймовірні макроси та не використовували PGO. -fguess-branch-probabilityувімкнено за замовчуванням на -O1і вище.

https://www.phoronix.com/scan.php?page=article&item=gcc-82-pgo&num=1 містить результати порівняльних показників для PGO порівняно зі звичайними з gcc8.2 на процесорі Xeon Scalable Server. (Skylake-AVX512). Кожен бенчмарк отримав хоча б невеликий пришвидшення, а деякі отримали вигоду на ~ 10%. (Більшість з них, ймовірно, відбувається з розгортання циклу в гарячих циклах, але, мабуть, це пов’язано з кращим розташуванням гілок та іншими ефектами.)


До речі, вам, мабуть, не потрібно використовувати вбудований_спект, якщо ви використовуєте оптимізацію, керовану профілями. PGO реєструє, яким шляхом рухалася кожна гілка, тому, коли ви компілюєте за допомогою -fprofile-use, gcc знає, який випадок є загальним для кожної гілки. Все одно не завадить використовувати вбудований_експект, щоб сказати йому швидкий шлях, якщо ваш код буде побудований без PGO.
Пітер Кордес

7

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

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

Я оптимізував програму від 1 години виконання до 2 хвилин за допомогою розгортання циклу та великих буферів вводу-виводу. Прогнозування галузі не дало б великої економії часу в цьому випадку.


1
Що ви маєте на увазі під "логічними методами програмування"?
someonewithpc

@someonewithrpc, який поєднує кілька випадків в один за допомогою побітових операцій. приклад (дурний, але все ж) приклад: замінити a = b & 1? 0: 1; за a = b & 1;
Саймон

1

Для цього випадку у програмі SUN C Studio визначено кілька прагм.

#pragma рідко_називається ()

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

Але немає можливості позначити загальне твердження if / while


-10

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

Також обов’язковий коментар щодо передчасної оптимізації та того, як це зло.

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


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

1
Якщо вам потрібен кожен цикл, чому б просто не використовувати вбудовану збірку?
rlbond

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

5
Прогнозувач гілок має статичне правило, коли він нічого не знає про гілку: візьміть гілки назад, не приймайте гілки вперед. Якщо ви подумаєте про те, як працює цикл for, ви зрозумієте, чому це має сенс, оскільки ви переходите назад до вершини циклу набагато частіше, ніж ні. Отже, макрос GCC керує тим, як GCC викладає коди операцій в пам’ять, щоб правило прогнозування гілок вперед / назад було найефективнішим.
Дон Нойфельд

1
Це просто неправильно, насправді існує команда збірки, щоб повідомити провісник гілок. Однак це ігнорується на всіх архітектурах, крім Netburst.
Гюнтер Пієз, 05

-10

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

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

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


1
Ні, для цього не може бути дійсних випадків використання. Наприклад, є компілятори, які виводять на c як негайний код і ставлять "if (break) break_into_debugger ()" у кожному рядку, щоб забезпечити рішення для налагодження, незалежне від платформи.
Лотар

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