Ви стаєте жертвою невдач прогнозування галузей .
Що таке галузеве передбачення?
Розглянемо залізничний вузол:
Зображення Меканісмо, через Wikimedia Commons. Використовується за ліцензією CC-By-SA 3.0 .
Тепер заради аргументації, припустимо, це ще в 1800-х роках - до міжміських або радіозв'язку.
Ви оператор перехрестя і чуєте, як їде поїзд. Ви не маєте уявлення, яким шляхом він повинен піти. Ви зупиняєте поїзд, щоб запитати машиніста, в якому напрямку вони хочуть. А потім ви належним чином встановите вимикач.
Потяги важкі і мають велику інерційність. Тому вони вічно запускаються і сповільнюються.
Чи є кращий спосіб? Ви здогадуєтесь, в якому напрямку поїзд поїде!
- Якщо ви правильно здогадалися, це продовжується.
- Якщо ви здогадалися не так, капітан зупиниться, створить резервну копію і кричить на вас, щоб перемкнути перемикач. Тоді він може перезапустити інший шлях.
Якщо ви гадаєте щоразу правильно , поїзд ніколи не зупинятиметься.
Якщо ви занадто часто здогадуєтесь , потяг витратить багато часу на зупинку, резервне копіювання та повторний пуск.
Розглянемо твердження if: На рівні процесора це гілка інструкція:
Ви процесор, і ви бачите гілку. Ви не маєте уявлення, яким шляхом він піде. Що ти робиш? Ви зупиняєте виконання та чекаєте, поки попередні інструкції не будуть виконані. Потім ви продовжуєте вниз правильним шляхом.
Сучасні процесори складні і мають довгі трубопроводи. Тож вони вічно беруться «зігріватися» і «гальмувати».
Чи є кращий спосіб? Ви здогадуєтесь, в якому напрямку піде гілка!
- Якщо ви правильно здогадалися, продовжуєте виконувати.
- Якщо ви здогадалися не так, вам потрібно промити трубопровід і відкотити до гілки. Потім ви можете перезапустити інший шлях.
Якщо ви гадаєте щоразу правильно , страта ніколи не припинятиметься.
Якщо ви гадаєте, що помиляєтеся занадто часто , ви витрачаєте багато часу на затримку, відкат і перезапуск.
Це галузеве передбачення. Я визнаю, це не найкраща аналогія, оскільки поїзд міг просто сигналізувати напрямок прапором. Але в комп’ютерах процесор не знає, в якому напрямку буде йти гілка до останнього моменту.
Тож як би ви стратегічно здогадалися мінімізувати кількість разів, коли поїзд повинен повертатися назад та йти другим шляхом? Ви дивитесь на минулу історію! Якщо поїзд їде залишив 99% часу, то ви здогадаєтесь ліворуч. Якщо воно чергується, то ви чергуєте свої здогадки. Якщо вона піде в один бік кожні три рази, ви здогадаєтесь те саме ...
Іншими словами, ви намагаєтесь визначити зразок і слідувати за ним. Це більш-менш, як працюють галузеві прогнози.
Більшість додатків мають добре відомі гілки. Тож сучасні галузеві передбачувачі зазвичай досягають> 90% частоти показів. Але стикаючись з непередбачуваними гілками без впізнавальних зразків, передбачувачі галузей практично марні.
Подальше читання: стаття "Провізор гілки" у Вікіпедії .
Як натякано зверху, винуватцем цього є if-заява:
if (data[c] >= 128)
sum += data[c];
Зауважте, що дані розподіляються рівномірно між 0 і 255. Коли дані сортуються, приблизно перша половина ітерацій не буде входити в оператор if. Після цього всі вони введуть if-заяву.
Це дуже доброзичливо відноситься до передбачувача галузей, оскільки філія послідовно йде в той же напрямок багато разів. Навіть простий лічильник насичення правильно спрогнозує гілку, за винятком кількох ітерацій після перемикання напрямку.
Швидка візуалізація:
T = branch taken
N = branch not taken
data[] = 0, 1, 2, 3, 4, ... 126, 127, 128, 129, 130, ... 250, 251, 252, ...
branch = N N N N N ... N N T T T ... T T T ...
= NNNNNNNNNNNN ... NNNNNNNTTTTTTTTT ... TTTTTTTTTT (easy to predict)
Однак, коли дані є абсолютно випадковими, передбачувач гілки стає марним, оскільки він не може передбачити випадкових даних. Таким чином, мабуть, буде близько 50% непередбачуваності (не краще, ніж випадкові здогадки).
data[] = 226, 185, 125, 158, 198, 144, 217, 79, 202, 118, 14, 150, 177, 182, 133, ...
branch = T, T, N, T, T, T, T, N, T, N, N, T, T, T, N ...
= TTNTTTTNTNNTTTN ... (completely random - hard to predict)
Отже, що можна зробити?
Якщо компілятор не в змозі оптимізувати гілку під умовний хід, ви можете спробувати деякі хаки, якщо ви готові пожертвувати читанністю для продуктивності.
Замінити:
if (data[c] >= 128)
sum += data[c];
з:
int t = (data[c] - 128) >> 31;
sum += ~t & data[c];
Це виключає гілку і замінює її деякими побітовими операціями.
(Зверніть увагу, що цей хак не є строго еквівалентним вихідному if-оператору. Але в цьому випадку він дійсний для всіх вхідних значень data[]
.)
Орієнтири: Core i7 920 при 3,5 ГГц
C ++ - Visual Studio 2010 - x64 Випуск
// Branch - Random
seconds = 11.777
// Branch - Sorted
seconds = 2.352
// Branchless - Random
seconds = 2.564
// Branchless - Sorted
seconds = 2.587
Java - NetBeans 7.1.1 JDK 7 - x64
// Branch - Random
seconds = 10.93293813
// Branch - Sorted
seconds = 5.643797077
// Branchless - Random
seconds = 3.113581453
// Branchless - Sorted
seconds = 3.186068823
Спостереження:
- За допомогою Відділення: Існує величезна різниця між відсортованими та несортованими даними.
- З Hack: Немає різниці між відсортованими та несортованими даними.
- У випадку C ++ хак насправді набирає повільніше, ніж у гілці, коли дані сортуються.
Загальне правило - уникати залежних від даних розгалужень у критичних петлях (як, наприклад, у цьому прикладі).
Оновлення:
GCC 4.6.1 з -O3
або -ftree-vectorize
на x64 здатний генерувати умовний хід. Тож різниці між відсортованими та несортованими даними немає - обидва швидкі.
(Або дещо швидко: для вже відсортованого випадку cmov
може бути повільніше, особливо якщо GCC ставить його на критичний шлях замість просто add
, особливо в Intel перед Бродвеллом, де cmov
затримка 2 циклу: прапор оптимізації gcc -O3 робить код повільніше, ніж -O2 )
VC ++ 2010 не в змозі генерувати умовні рухи для цієї гілки навіть під /Ox
.
Компілятор Intel C ++ (ICC) 11 робить щось чудотворне. Він перетинає дві петлі , тим самим підключаючи непередбачувану гілку до зовнішньої петлі. Тож не тільки він несприйнятливий до помилок, він також удвічі швидший, ніж будь-який VC ++ та GCC може генерувати! Іншими словами, ICC скористався тест-циклом, щоб перемогти тест ...
Якщо ви даєте компілятору Intel безрозгалужувальний код, він просто невірно векторизує його ... і так само швидко, як і з гілкою (з обміном циклу).
Це свідчить про те, що навіть зрілі сучасні компілятори можуть сильно відрізнятися у своїй здатності оптимізувати код ...