Чому для покращення продуктивності часто віддають перевагу багатопотоковість?


23

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

Я розглядаю тут два основні підходи:

  • асинхронний підхід, заснований на сигналах, або просто асинхронний підхід, про який називають багато паперів та мов, наприклад, новий C # 5.0, наприклад, та "супутню нитку", яка керує політикою вашого трубопроводу
  • паралельний підхід або підхід з багатопотоковою різьбою

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

Я протестував багатопотокові програми та програму async на старій машині з чотирьохядерним процесором Intel, який не пропонує контролер пам'яті всередині процесора, пам'ять повністю управляється материнською платою, і в цьому випадку продуктивність жахлива багатопотокове додаток, навіть відносно невелика кількість потоків, таких як 3-4-5, може бути проблемою, додаток не відповідає і просто повільний і неприємний.

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

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

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

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

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

Відповідно до моделі пам'яті X86, чому більшість людей пропонують використовувати паралельність із C ++, а не просто асинхронний підхід? Крім того, чому б не розглянути найгірший сценарій комп'ютера, коли контекстний комутатор, ймовірно, дорожчий, ніж самі обчислення?


2
Одним із способів порівняння було б поглянути на світ JavaScript, якби не було потоку різьби та все агресивно асинхронно, використовуючи зворотні дзвінки. Це працює, але у нього є свої проблеми.
Гурт Робота

2
@StevenBurnap Як ви називаєте працівників мережі?
user16764

2
"навіть відносно невелика кількість потоків, таких як 3-4-5, може бути проблемою, додаток не відповідає і просто повільний і неприємний." => Це може бути пов’язано з поганим дизайном / неналежним використанням ниток. Зазвичай ви стикаєтеся з такою ситуацією, коли ваші потоки постійно обмінюються даними, і в цьому випадку багаторядкова нарізка може бути не правильною відповіддю, або вам може знадобитися перерозподіл даних.
ассілій

1
@assylias Побачення значного уповільнення потоку інтерфейсу вказує на надмірну кількість блокування в потоках. У вас або погана реалізація, або ви намагаєтесь забити квадратний кілочок у круглий отвір.
Еван Плейс

5
Ви кажете: "Схоже, програмісти люблять паралельність і багатопотокові програми взагалі", я сумніваюся в цьому. Я б сказав: "програмісти ненавиджу це" ... але часто це єдине корисне, що потрібно зробити ...
johannes

Відповіді:


34

У вас є кілька ядер / процесорів, використовуйте їх

Асинхронний є найкращим для цього важкого IO , пов'язаних з переробкою , але як щодо важкої роботи процесора обробки?

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

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

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

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

Ви можете побачити тенденцію на основних хмарних платформах, оскільки вони запропонують екземпляри, спеціалізовані для обробки процесорів, та екземпляри, спеціалізовані на обробці пов'язаних з IO.

Приклади:

  • Зберігання (наприклад, Amazon S3, Google Cloud Drive) пов'язане з процесором
  • Веб-сервери прив'язані до IO (Amazon EC2, Google App Engine)
  • Бази даних є обома, процесор пов'язаний для запису / індексування та IO для читання

Щоб поставити це в перспективу ...

Веб-сервер - це ідеальний приклад платформи, яка сильно пов'язана з IO. Багатопотоковий веб-сервер, який призначає один потік на з'єднання, не масштабується добре, оскільки кожен потік має більше накладних витрат через збільшення кількості перемикання контексту та блокування потоку на спільних ресурсах. Тоді як веб-сервер async використовує єдиний простір адрес.

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

Найкращий додаток часто використовує комбінацію обох. Наприклад, веб-сервер може використовувати nginx (тобто асинхронізацію однопотокових) як балансир завантаження для управління торетом вхідних запитів, аналогічний веб-сервер async (колишній Node.js) для обробки запитів http та набір багатопотокових серверів обробляти завантаження / потокове / кодування вмісту тощо ...

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

Звідси випливає та сама лінія мислення, яка виправдовує паралельне використання архітектури GPU та CPU. Дві спеціалізовані системи, що працюють в концерті, можуть мати набагато більше вдосконалення, ніж єдиний монолітний підхід.

Не краще, тому що обидва мають своє використання. Використовуйте найкращий інструмент для роботи.

Оновлення:

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

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

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

Оновлення2:

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


чому програміст повинен проходити багатопроцесорний процес? Я маю на увазі, я припускаю, що з більш ніж 1 процесом вам також потрібна якась міжпроцесорна комунікація, яка може додати значні витрати, чи це щось на зразок старого способу роботи Windows-програміста? коли я повинен перейти багатопроцесорний? Дякуємо за вашу відповідь, до речі, дійсно гарна картина того, для чого асинхронні та багатопотокові корисні.
користувач1849534

1
Ви припускаєте, що міжпроцесовий зв'язок збільшить загальні витрати. Однак якщо стан обробки є непорушним або потрібно лише обробляти синхронізацію після запуску / завершення. може бути набагато ефективніше роздумувати над більш паралельними завданнями. Акторський зразок - хороший приклад, і якщо ви про нього ще не читали - його дійсно варто прочитати далі. akka.io
сильванаар

1
@ user1849534 Кілька потоків можуть спілкуватися один з одним за допомогою спільної пам'яті + блокування або IPC. Блокування простіше, але важче налагодити, якщо ви помилитесь (наприклад, пропущений замок, мертвий замок). IPC найкраще, якщо у вас багато робочих ниток, тому що блокування не масштабується. У будь-якому випадку, якщо ви використовуєте багатопотоковий підхід, важливо звести зв’язок / синхронізацію через потоки до абсолютного мінімуму (тобто мінімізувати накладні витрати).
Еван Плейс

1
@ akka.io Ви абсолютно праві. Незмінність - це один із способів мінімізувати / усунути накладні витрати блокування, але ви все ще несете часові витрати на зміну контексту. Якщо ви хочете розширити відповідь, щоб включити подробиці про те, як незмінність може вирішити проблеми синхронізації потоків, не соромтеся. Основним моментом, який я мав на меті проілюструвати, є випадки, коли асинхронна комунікація має явну перевагу перед багатопотоковою / процесою і навпаки.
Еван Плейс

(продовження) Але, якщо чесно, якщо мені було потрібно багато процесорних можливостей обробки, я пропустив акторську модель і побудував її до здатності масштабувати до кількох мережевих вузлів. Найкраще рішення, яке я бачив для цього, - це використання моделі вентилятора завдань 0MQ над комунікаціями на рівні розетки. Див. Фіг.5 @ zguide.zeromq.org/page:all .
Еван Плейс

13

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

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

Багатопоточність для чуйності

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

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

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

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

"Асинхронна" альтернатива

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

Замість створення нового потоку для очікування отримання мережевого ресурсу (наприклад, завантаження зображення) використовується asyncметод, який awaitстає доступним для зображення, а тим часом поступається методу виклику.

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

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

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

Багатопоточність для продуктивності

Оскільки ви говорите про "продуктивність", я також хотів би поговорити про те, як багатопотоковість може бути використана для підвищення продуктивності, що абсолютно неможливо при однопотоковому асинхронному підході.

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

Тривіальний паралелізм

Звичайно, іноді може бути легко отримати реальну швидкість від багатопотокової.

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

Практична багатопоточність для продуктивності

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

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

Додатковий потік означає більше витрат на налаштування / відмову, а також більше контекстних комутаторів або більше комунікаційних витрат між CPU. Якщо вона не проводить достатньо роботи, щоб компенсувати ці витрати, якщо вони знаходяться в окремому процесорі, і не потрібно бути окремим потоком з міркувань чутливості, це сповільнить справи без користі.

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

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

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

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

Однак не забудьте профілювати нитки та переконайтесь, що вони виконують достатню роботу, щоб компенсувати витрати в якийсь момент.

Паралельні алгоритми

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

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

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


+1 за очевидно продуману відповідь. Хоча я б став обережно приймати пропозиції Microsoft за номіналом. Майте на увазі, що .NET є синхронною першою платформою, тому екосистема упереджена для надання кращих засобів / документації, що підтримують побудову синхронних рішень. Зрозуміло б, що для перших платформ асинхроніки, таких як Node.js.
Еван Плейс

3

додаток не реагує і просто повільний і неприємний.

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

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

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


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