Це змусило мене замислитися, наскільки важливим є багатопотокове читання у поточному галузевому сценарії?
У критичних для продуктивності областях, де продуктивність не надходить від стороннього коду, який займається важким підйомом, але наш власний, то я схильний розглядати речі в цьому порядку важливості з точки зору процесора (GPU - це майна, яку я виграв не заходжу):
- Ефективність пам’яті (наприклад: місце відліку).
- Алгоритмічний
- Багатопотоковість
- SIMD
- Інші оптимізації (статичні підказки прогнозування гілок, наприклад)
Зауважте, що цей перелік базується не лише на важливості, але й на багатьох інших динаміках, таких як вплив, який вони мають на технічне обслуговування, наскільки простий (якщо ні, варто врахувати детальніше заздалегідь), їх взаємодії з іншими в списку тощо.
Ефективність пам'яті
Більшість може бути здивована моїм вибором ефективності пам'яті над алгоритмічною. Це тому, що ефективність пам’яті взаємодіє з усіма 4 іншими пунктами цього списку, і це тому, що її врахування дуже часто стосується категорії «дизайн», а не категорії «впровадження». Тут, мабуть, є трохи проблеми з куркою або яйцями, оскільки розуміння ефективності пам'яті часто вимагає врахування всіх 4 елементів у списку, тоді як усі 4 інші елементи також потребують врахування ефективності пам'яті. І все ж це в основі всього.
Наприклад, якщо у нас є потреба в структурі даних, яка пропонує лінійний час послідовного доступу та вставки постійного часу на задній план і нічого іншого для дрібних елементів, наївним вибором, до якого слід звернутися, буде пов'язаний список. Це ігнорування ефективності пам'яті. Коли ми розглядаємо ефективність пам’яті в поєднанні, то в кінцевому підсумку ми вибираємо більш суміжні структури за цим сценарієм, як, наприклад, розроблені структури на основі масиву або більш суміжні вузли (наприклад: один, що зберігає 128 елементів у вузлі), пов’язані між собою, або принаймні пов'язаний список, підтримуваний розподільником пулу. Вони мають драматичну перевагу, незважаючи на те, що мають однакову алгоритмічну складність. Крім того, ми часто вибираємо кварцовий масив масиву над сортуванням злиття, незважаючи на нижчу алгоритмічну складність, просто через ефективність пам'яті.
Крім того, ми не можемо мати ефективну багатопотоковість, якщо наші структури доступу до пам’яті настільки деталізовані та розсіяні в природі, що ми в кінцевому підсумку максимізуємо кількість помилкового обміну при блокуванні на найбільш деталізованих рівнях у коді. Таким чином, ефективність пам'яті примножує ефективність багатопотоковості. Це необхідна умова отримати максимум користі з ниток.
Кожен окремий елемент у списку має складну взаємодію з даними, і орієнтація на те, як представлені дані, є в кінцевому рахунку ефективністю пам'яті. Кожен з цих вище пунктів може бути вузьким місцем з невідповідним способом представлення або доступу до даних.
Ще одна причина ефективності пам’яті настільки важлива, що вона може застосовуватися протягом усієї бази коду. Взагалі, коли люди уявляють, що неефективність накопичується в місцях роботи, що тут трохи там, це ознака того, що їм потрібно захопити профілера. І все ж поля з низькою затримкою або ті, що мають справу з обмеженим обладнанням, насправді знайдуть навіть після профілювання сеанси, які вказують на відсутність чітких точних точок (кілька разів розповсюджених у всьому місці) у кодовій базі, яка начебто неефективна з способом розподілу, копіювання та доступ до пам'яті. Зазвичай мова йде про єдиний час, коли вся база коду може сприйняти занепокоєння щодо продуктивності, що може спричинити за собою цілий новий набір стандартів, застосовуваних по всій кодовій базі даних, а ефективність пам'яті часто лежить в основі цього.
Алгоритмічний
Це в значній мірі дано, оскільки вибір алгоритму сортування може змінити різницю між масовим входом, який займає кілька місяців для сортування, порівняно з секундами. Це робить найбільший вплив на всіх, якщо вибір між, скажімо, дійсно суб-номінальним квадратичним чи кубічним алгоритмами та лінійно-лімітним, або між лінійним та логарифмічним чи константним, принаймні, поки у нас не буде 1000 000 основних машин (у цьому випадку пам'ять ефективність стане ще важливішою).
Однак це не вгорі мого особистого списку, оскільки кожен, хто є компетентним у своїй галузі, знатиме, щоб використовувати структуру прискорення для вибивання фрустуму, наприклад, ми насичені алгоритмічними знаннями та знаємо такі речі, як використання варіанту трійки, наприклад дерево-радікс для пошуку на основі префіксу - це дитячі речі. Не маючи такого роду базових знань у галузі, в якій ми працюємо, тоді алгоритмічна ефективність, безумовно, підніметься до вершини, але часто алгоритмічна ефективність є тривіальною.
Також винайдення нових алгоритмів може стати необхідністю у деяких сферах (наприклад: при обробці сітки мені довелося вигадати сотні, оскільки їх або раніше не було, або реалізація подібних функцій в інших продуктах була власницькою таємницею, не опублікованою в статті ). Однак, коли ми минули частину вирішення проблем і знайдемо спосіб отримати правильні результати, і як тільки ефективність стане ціллю, єдиним способом дійсно досягти цього є розглянути, як ми взаємодіємо з даними (пам'яттю). Не розуміючи ефективності пам’яті, новий алгоритм може стати без зайвих зусиль безрезультатними зусиллями, щоб зробити його швидшим, коли єдине, що йому було потрібно - це трохи більше врахування ефективності пам’яті, щоб отримати простіший, елегантніший алгоритм.
Нарешті, алгоритми, як правило, більше входять до категорії «впровадження», ніж ефективність пам’яті. Їх часто простіше вдосконалити заднім числом навіть за допомогою неоптимального алгоритму, що використовується спочатку. Наприклад, низький алгоритм обробки зображень часто просто реалізується в одному локальному місці в кодовій базі. Пізніше його можна замінити на краще. Однак якщо всі алгоритми обробки зображень прив’язані до Pixel
інтерфейсу, який має неоптимальне представлення пам'яті, але єдиний спосіб виправити це - змінити спосіб представлення кількох пікселів (а не один), то ми часто SOL і доведеться повністю переписати кодову базу на anImage
інтерфейс. Те ж саме стосується заміни алгоритму сортування - зазвичай це деталізація про реалізацію, тоді як повне зміна базового подання даних про сортування або способу передачі через повідомлення може потребувати переробки інтерфейсів.
Багатопотоковість
Багатопотокове читання є складним у контексті продуктивності, оскільки це оптимізація мікрорівень, яка відповідає характеристикам апаратних засобів, але наша апаратура дійсно масштабує в цьому напрямку. Вже в мене є однолітки, які мають 32 ядра (у мене лише 4).
Але багатоочищення є однією з найнебезпечніших мікрооптимізацій, що, напевно, відома професіоналу, якщо мета використовується для прискорення програмного забезпечення. Стан гонки - це майже сама смертельна помилка, оскільки вона настільки недетермінована за своєю суттю (можливо, лише з’являється раз на кілька місяців на машині розробника у найзручніший час поза контекстом налагодження, якщо він є). Таким чином, це, мабуть, найнегативніше погіршення ремонтопридатності та потенційної коректності коду серед усіх цих, тим більше, що помилки, пов’язані з багатопотоковою програмою, можуть легко пролетіти під радари навіть найбільш ретельних тестів.
Тим не менш, це стає таким важливим. Незважаючи на те, що це не завжди може сподобатися на кшталт ефективності пам’яті (що іноді може зробити речі в сто разів швидшими), враховуючи кількість ядер, які ми маємо зараз, ми бачимо все більше ядер. Звичайно, навіть із 100-ядерними машинами я все-таки став би ефективність пам’яті на перше місце у списку, оскільки ефективність потоку взагалі неможлива. Програма може використовувати сто потоків на такій машині і все ще повільно бракує ефективного представлення пам’яті та шаблонів доступу (які прив’язуються до шаблонів блокування).
SIMD
SIMD також трохи незручно, оскільки реєстри насправді стають все ширшими, планують ще ширше. Спочатку ми бачили 64-бітні регістри MMX, а потім 128-бітні регістри XMM, здатні паралельно виконувати 4 SPFP-операції. Тепер ми бачимо 256-бітні регістри YMM, здатні 8 паралельно. І вже є плани на 512-бітні регістри, які дозволять паралельно 16.
Вони взаємодіють і примножують ефективність багатопотокової чистки. Проте SIMD може погіршити ремонтопридатність настільки ж, як і багатопотокове. Незважаючи на те, що помилки, пов’язані з ними, не обов'язково відтворювати та виправляти, як тупиковий стан або стан перегонів, портативність є незручною, а забезпечення того, що код може працювати на машині кожного (і використовуючи відповідні інструкції, виходячи з їх апаратних можливостей), є незграбний
Інша справа, що, хоча сьогодні компілятори зазвичай не б'ють в умело написаному SIMD-коді, вони легко перемагають наївні спроби. Вони можуть вдосконалитись до того, що нам більше не доведеться робити це вручну або, принаймні, не отримуючи настільки вручну, щоб писати властивості або прямий збірний код (можливо, лише невелике керівництво людини).
Знову ж таки, без макета пам'яті, ефективного для векторизованої обробки, SIMD марний. Ми в кінцевому підсумку просто завантажимо одне скалярне поле в широкий регістр, щоб виконати над ним одну операцію. В основі всіх цих пунктів лежить залежність від макетів пам’яті, щоб вони були справді ефективними.
Інші оптимізації
Це часто те, що я б запропонував сьогодні почати називати "мікро", якщо слово пропонує не тільки виходити за рамки алгоритмічного фокусування, але й зміни, які мають незначний вплив на продуктивність.
Часто намагаються оптимізувати галузеве передбачення вимагає зміни алгоритму чи ефективності пам’яті, наприклад, якщо це робиться лише через підказки та перестановку коду для статичного прогнозування, це має тенденцію лише до вдосконалення першого виконання такого коду, що робить ефекти сумнівними, якщо не часто відверто незначний.
Назад до багатопотокової роботи
Так чи інакше, наскільки важливою є багатопоточність з контексту виступу? На моїй чотирьохядерній машині він в ідеалі може робити речі приблизно в 5 разів швидше (що я можу отримати із гіперточенням). Було б значно важливіше моєму колезі, який має 32 ядра. І це стане все більш важливим у наступні роки.
Тож це досить важливо. Але марно просто кидати купу ниток на проблему, якщо ефективність пам’яті відсутня, щоб дозволити сумлінно використовувати блоки, зменшити помилковий обмін тощо.
Багатопотоковість поза продуктивністю
Багатопоточність не завжди стосується чистої продуктивності в прямому сенсі. Іноді його використовують для збалансування навантаження навіть за можливих витрат пропускної здатності, щоб покращити чутливість до користувача або дозволити користувачеві робити багатозадачність, не чекаючи, коли все закінчиться (наприклад: продовжуйте перегляд під час завантаження файлу).
У цих випадках я б припустив, що багатопотокове піднесення піднімається ще вище до вершини (можливо, навіть вище ефективності пам’яті), оскільки тоді йдеться про дизайн кінцевого користувача, а не про те, щоб максимально використати обладнання. Це часто переважає в дизайні інтерфейсів і тому, як ми структуруємо всю свою кодову базу в таких сценаріях.
Коли ми не просто паралелізовуємо тугий цикл, що отримує доступ до масивної структури даних, багатопотокове перехід до дійсно жорсткої категорії "дизайн", і дизайн завжди козирує втілення.
Тож у цих випадках, я б сказав, вважати багатопотоковість вперед абсолютно важливою, навіть більше, ніж представлення пам’яті та доступу до неї.