Коротка відповідь
Алгоритм chunksize Пула є евристичним. Він надає просте рішення для всіх можливих сценаріїв проблем, які ви намагаєтесь вбити в методи Пулу. Як наслідок, його не можна оптимізувати для будь-якого конкретного сценарію.
Алгоритм довільно ділить ітерабельність приблизно в чотири рази більше шматків, ніж наївний підхід. Більша кількість фрагментів означає більше накладних витрат, але збільшена гнучкість планування. Як покаже ця відповідь, це в середньому призводить до більшого використання працівників, але без гарантії коротшого загального часу обчислень для кожного випадку.
"Це приємно знати", можна подумати, "але як знання цього мені допомагає у вирішенні конкретних проблем багатопроцесорної обробки?" Ну, це не так. Більш чесна коротка відповідь: "короткої відповіді немає", "багатопроцесорність - це складно" і "це залежить". Спостережуваний симптом може мати різне коріння, навіть для подібних сценаріїв.
Ця відповідь намагається надати вам основні поняття, які допоможуть вам отримати більш чітке уявлення про чорний ящик планування пулу. Він також намагається надати вам декілька основних інструментів для розпізнавання та уникнення потенційних скель, наскільки вони пов'язані з ущільненням.
Зміст
Частина І
- Визначення
- Цілі паралелізації
- Сценарії паралелізації
- Ризики Chunksize> 1
- Алгоритм Chunksize басейну
Кількісна оцінка ефективності алгоритму
6.1 Моделі
6.2 Паралельний графік
6.3 Ефективність
6.3.1 Абсолютна ефективність розподілу (ADE)
6.3.2 Відносна ефективність розподілу (RDE)
Частина ІІ
- Наївний проти алгоритму Чункізі Пула
- Перевірка реальності
- Висновок
Спочатку необхідно уточнити деякі важливі терміни.
1. Визначення
Шматок
iterable
Шматок тут - це частка аргументу, зазначена у виклику методу пулу. Тема цієї відповіді - те, як обчислюється збивання та які наслідки це може мати.
Завдання
Фізичне представлення завдання в робочому процесі з точки зору даних можна побачити на малюнку нижче.
На малюнку показаний приклад виклику pool.map()
, що відображається вздовж рядка коду, взятого з multiprocessing.pool.worker
функції, де завдання, прочитане з файлу, inqueue
розпаковується. worker
є основною основною функцією у процесі MainThread
пулу-працівника. func
-Argument вказано в басейні-методі буде тільки збігатися з func
-змінного всередині worker
-функцією для методів одного виклику , як apply_async
і для imap
з chunksize=1
. Для решти методів пулу з chunksize
-параметром функцією обробки func
буде функція відображення ( mapstar
або starmapstar
). Ця функція відображає вказаний користувачем func
-параметр на кожному елементі переданого фрагмента ітерабельного (-> "map-tasks"). Час, який це займає, визначає завданнятакож як одиниця роботи .
Таскель
Хоча використання слова "завдання" для всієї обробки одного фрагмента відповідає коду всередині multiprocessing.pool
, немає жодних вказівок на те , яким повинен бути один виклик до вказаного користувачем func
, з одним елементом фрагмента в якості аргументів. що стосується. Щоб уникнути плутанини, що виникає внаслідок конфліктів імен (подумайте про maxtasksperchild
-параметр __init__
методу Pool- методу), ця відповідь буде стосуватися окремих одиниць роботи в рамках завдання, як taskel .
Taskel (від завдання + ель ня) є найменшою одиницею роботи в рамках завдання . Це одноразове виконання функції, зазначеної з func
-параметром Pool
-методу, що викликається з аргументами, отриманими з одного елемента переданого фрагмента . Завдання складається з chunksize
taskels .
Накладні витрати на паралелізацію (PO)
PO складається з внутрішніх службових та накладних витрат на Python для міжпроцесорного зв'язку (IPC). Накладні витрати на завдання в Python постачаються з кодом, необхідним для упаковки та розпакування завдань та їх результатів. Накладні витрати IPC постачаються з необхідною синхронізацією потоків та копіюванням даних між різними адресними просторами (потрібні два кроки копіювання: батьківський -> черга -> дочірній). Сума накладних витрат на IPC залежить від ОС, апаратного забезпечення та розміру даних, що ускладнює узагальнення щодо впливу.
2. Цілі паралелізації
При використанні багатопроцесорної роботи, загальною метою (очевидно) є мінімізація загального часу обробки всіх завдань. Для досягнення цієї загальної мети нашою технічною метою повинна бути оптимізація використання апаратних ресурсів .
Деякі важливі підцілі для досягнення технічної мети:
- мінімізувати накладні витрати на розпаралелювання (найвідоміше, але не поодинці: IPC )
- висока ефективність використання всіх процесорних ядер
- обмеження використання пам’яті, щоб запобігти надмірному підкачуванню ( сміттю ) ОС
Спочатку завдання повинні бути достатньо обчислювальними (інтенсивними), щоб повернути ПЗ, ми повинні заплатити за розпаралелювання. Актуальність PO зменшується із збільшенням абсолютного часу обчислення на таскель. Або, якщо сказати навпаки, чим більший абсолютний час обчислення на таскель для вашої проблеми, тим менш доречною стає потреба у зменшенні PO. Якщо ваші обчислення займуть години на клавішу, накладні витрати IPC будуть незначними в порівнянні. Тут головним завданням є запобігання холостим робочим процесам після розподілу всіх завдань. Зберігаючи всі завантажені ядра засобами, ми проводимо паралелізм, наскільки це можливо.
3. Сценарії паралелізації
Які фактори визначають оптимальний аргумент chunksize для таких методів, як багатопроцесорність.Pool.map ()
Основним фактором, про який йде мова, є те, на скільки час обчислень може змінюватись для окремих завдань. Щоб назвати це, вибір оптимального збивання визначається коефіцієнтом варіації ( CV ) для часу обчислення на таскель.
Два масштабні сценарії масштабу, що випливають із масштабів цієї варіації:
- Усім завданням потрібен абсолютно однаковий час обчислення.
- На закінчення клавіш може знадобитися секунди або дні.
Для кращого запам'ятовування я буду називати ці сценарії наступними:
- Щільний сценарій
- Широкий сценарій
Щільний сценарій
У щільному сценарії бажано розподілити всі завдання одразу, щоб мінімальний рівень IPC та перемикання контексту був мінімальним. Це означає, що ми хочемо створити лише стільки фрагментів, скільки робочих процесів. Як вже було зазначено вище, вага PO зростає із зменшенням часу обчислення на таскель.
Для максимальної пропускної здатності ми також хочемо, щоб усі робочі процеси були зайняті, поки всі завдання не будуть оброблені (без робочих режимів). Для цієї мети розподілені шматки повинні бути однакового розміру або близькі до.
Широкий сценарій
Основним прикладом широкого сценарію може бути проблема оптимізації, коли результати або швидко збігаються, або обчислення можуть зайняти години, а то й дні. Зазвичай непередбачувано, яку суміш "легких кистей" і "важких кистей" міститиме завдання в такому випадку, отже, не бажано розподіляти занадто багато кишень в пакеті завдань одночасно. Розподіл одразу менше завдань, ніж це можливо, означає збільшення гнучкості планування. Це потрібно тут для досягнення нашої підцілі - високого використання всіх ядер.
Якщо Pool
методи за замовчуванням будуть повністю оптимізовані для щільного сценарію, вони все частіше створюватимуть неоптимальні терміни для кожної проблеми, розташованої ближче до широкого сценарію.
4. Ризики Chunksize> 1
Розглянемо цей спрощений приклад псевдокоду Широкого сценарію -ітерабельного, який ми хочемо передати в метод пулу:
good_luck_iterable = [60, 60, 86400, 60, 86400, 60, 60, 84600]
Замість фактичних значень ми робимо вигляд, що бачимо необхідний час обчислення в секундах, для простоти лише 1 хвилину або 1 день. Ми вважаємо, що пул має чотири робочі процеси (на чотирьох ядрах) і chunksize
встановлено значення 2
. Оскільки наказ буде дотримано, шматки, що надсилаються робітникам, будуть такими:
[(60, 60), (86400, 60), (86400, 60), (60, 84600)]
Оскільки у нас достатньо робітників, а час обчислення досить великий, ми можемо сказати, що кожен робочий процес отримає шматок, над яким слід працювати в першу чергу. (Це не обов'язково має бути для швидко виконуваних завдань). Далі ми можемо сказати, що вся обробка триватиме близько 86400 + 60 секунд, оскільки це найвищий загальний час обчислення для фрагмента в цьому штучному сценарії, і ми розподіляємо фрагменти лише один раз.
Тепер розглянемо цей ітерабель, який має лише один елемент, що змінює своє положення порівняно з попереднім ітератором:
bad_luck_iterable = [60, 60, 86400, 86400, 60, 60, 60, 84600]
... та відповідні шматки:
[(60, 60), (86400, 86400), (60, 60), (60, 84600)]
Просто невдача з сортуванням нашого ітеративного майже подвоївся (86400 + 86400) наш загальний час обробки! Робітник, який отримує злісний (86400, 86400) шматок, блокує другу важку кисть у своєму завданні від розподілу до одного з бездіяльних робітників, який вже закінчив свої (60, 60) шматки. Ми, очевидно, не ризикували б таким неприємним результатом, якби встановили chunksize=1
.
Це ризик більших шматочків. З більшими розмірами ми торгуємо гнучкістю планування для менших накладних витрат, і у таких випадках, як вище, це погана угода.
Як ми побачимо у розділі 6. Кількісна оцінка ефективності алгоритму , більші шматки також можуть призвести до неоптимальних результатів для щільних сценаріїв .
5. Chunksize-Алгоритм басейну
Нижче ви знайдете дещо змінену версію алгоритму всередині вихідного коду. Як бачите, я відрізав нижню частину і обернув її функцією для chunksize
зовнішнього обчислення аргументу. Я також замінив 4
на factor
параметр і передав len()
дзвінки на аутсорсинг .
def calc_chunksize(n_workers, len_iterable, factor=4):
"""Calculate chunksize argument for Pool-methods.
Resembles source-code within `multiprocessing.pool.Pool._map_async`.
"""
chunksize, extra = divmod(len_iterable, n_workers * factor)
if extra:
chunksize += 1
return chunksize
Щоб переконатися, що ми всі на одній сторінці, ось що divmod
:
divmod(x, y)
- це вбудована функція, яка повертається (x//y, x%y)
.
x // y
є поділом підлоги, повертаючи зменшений округлений коефіцієнт від x / y
, тоді
x % y
як операція за модулем повертає залишок від x / y
. Звідси, наприклад, divmod(10, 3)
повернення (3, 1)
.
Тепер , коли ви подивіться на chunksize, extra = divmod(len_iterable, n_workers * 4)
, ви побачите n_workers
тут є дільником y
в x / y
і множення 4
, без додаткового регулювання через if extra: chunksize +=1
пізніше, призводить до первісного chunksize , по крайней мере в чотири рази менше (для len_iterable >= n_workers * 4
) , ніж це було б в іншому випадку.
Для перегляду ефекту множення 4
на на проміжний результат збиття розглянемо цю функцію:
def compare_chunksizes(len_iterable, n_workers=4):
"""Calculate naive chunksize, Pool's stage-1 chunksize and the chunksize
for Pool's complete algorithm. Return chunksizes and the real factors by
which naive chunksizes are bigger.
"""
cs_naive = len_iterable // n_workers or 1
cs_pool1 = len_iterable // (n_workers * 4) or 1
cs_pool2 = calc_chunksize(n_workers, len_iterable)
real_factor_pool1 = cs_naive / cs_pool1
real_factor_pool2 = cs_naive / cs_pool2
return cs_naive, cs_pool1, cs_pool2, real_factor_pool1, real_factor_pool2
Функція, наведена вище, обчислює наївний chunksize ( cs_naive
) та першого кроку chunksize алгоритму chunksize Пула ( cs_pool1
), а також chunksize для повного алгоритму Pool ( cs_pool2
). Далі він обчислює реальні коефіцієнти rf_pool1 = cs_naive / cs_pool1
і rf_pool2 = cs_naive / cs_pool2
, які повідомляють нам, у скільки разів наївно обчислені розміри більші, ніж внутрішні версії (-и) Пулу.
Нижче ви бачите дві фігури, створені за допомогою цієї функції. На лівій фігурі просто показані шматки n_workers=4
до ітерируваної довжини 500
. На правому малюнку показано значення для rf_pool1
. Для ітерируемой довжини 16
дійсним коефіцієнтом стає >=4
(для len_iterable >= n_workers * 4
), а його максимальним значенням є 7
ітерирувана довжина 28-31
. Це значне відхилення від початкового фактора, 4
до якого збігається алгоритм, для більш тривалих ітерацій. "Довше" тут відносне і залежить від кількості вказаних робітників.
Пам'ятайте, у chunksize cs_pool1
все ще не вистачає -коригування extra
з залишком, що divmod
міститься в cs_pool2
повному алгоритмі.
Алгоритм продовжується:
if extra:
chunksize += 1
Зараз у випадках, коли є залишок (an extra
від операції divmod), збільшення зменшення на 1, очевидно, не може працювати для кожного завдання. Зрештою, якби це сталося, для початку не було б залишку.
Як ви можете бачити на малюнку нижче, то « екстра-лікування » має ефект, що реальний фактор для rf_pool2
нині сходиться в напрямку 4
від нижче 4
і відхилення дещо м'якше. Стандартне відхилення для n_workers=4
і len_iterable=500
падає від 0.5233
для rf_pool1
до 0.4115
для rf_pool2
.
Зрештою, збільшення chunksize
на 1 має наслідком, що останнє передане завдання має лише розмір len_iterable % chunksize or chunksize
.
Тим цікавішим і як ми побачимо пізніше, тим більше наслідком буде ефект додаткової обробки, проте можна спостерігати за кількістю сформованих шматків ( n_chunks
). Для достатньо тривалих ітерацій завершений алгоритм chunksize пулу ( n_pool2
на малюнку нижче) стабілізує кількість шматків на n_chunks == n_workers * 4
. На відміну від цього, наївний алгоритм (після початкової відрижки) постійно чергується між собою n_chunks == n_workers
і n_chunks == n_workers + 1
в міру збільшення довжини ітерабельного.
Нижче ви знайдете дві розширені інформаційні функції для Pool і наївний алгоритм chunksize. Результат цих функцій знадобиться в наступному розділі.
from collections import namedtuple
Chunkinfo = namedtuple(
'Chunkinfo', ['n_workers', 'len_iterable', 'n_chunks',
'chunksize', 'last_chunk']
)
def calc_chunksize_info(n_workers, len_iterable, factor=4):
"""Calculate chunksize numbers."""
chunksize, extra = divmod(len_iterable, n_workers * factor)
if extra:
chunksize += 1
n_chunks = len_iterable // chunksize + (len_iterable % chunksize > 0)
last_chunk = len_iterable % chunksize or chunksize
return Chunkinfo(
n_workers, len_iterable, n_chunks, chunksize, last_chunk
)
Нехай вас не бентежить, мабуть, несподіваний вигляд calc_naive_chunksize_info
. Значення extra
from divmod
не використовується для обчислення chunksize.
def calc_naive_chunksize_info(n_workers, len_iterable):
"""Calculate naive chunksize numbers."""
chunksize, extra = divmod(len_iterable, n_workers)
if chunksize == 0:
chunksize = 1
n_chunks = extra
last_chunk = chunksize
else:
n_chunks = len_iterable // chunksize + (len_iterable % chunksize > 0)
last_chunk = len_iterable % chunksize or chunksize
return Chunkinfo(
n_workers, len_iterable, n_chunks, chunksize, last_chunk
)
6. Кількісна оцінка ефективності алгоритму
Тепер, після того, як ми побачили, як висновок Pool
алгоритму chunksize виглядає по-різному порівняно з результатом наївного алгоритму ...
- Як визначити, чи підхід Пула насправді щось покращує ?
- І що саме це могло що - то бути?
Як показано в попередньому розділі, для довших ітерацій (більша кількість завдань) алгоритм chunksize Пула приблизно ділить ітерабель на чотири рази більше шматків, ніж наївний метод. Менші шматки означають більше завдань, а більше завдань - більше накладних витрат на паралелізацію (PO) , вартість яких повинна бути порівняна з перевагою підвищеної гнучкості планування ( нагадуйте "Ризики Chunksize> 1" ).
З цілком очевидних причин базовий алгоритм chunksize не може зрівняти гнучкість планування з PO . Накладні витрати IPC залежать від ОС, апаратного забезпечення та розміру даних. Алгоритм не може знати, на якому обладнанні ми запускаємо наш код, і не має уявлення про те, скільки часу буде потрібно для завершення роботи клавіатури. Це евристика, що забезпечує базову функціональність для всіх можливих сценаріїв. Це означає, що його не можна оптимізувати для будь-якого конкретного сценарію. Як вже згадувалося раніше, PO також стає дедалі меншим занепокоєнням із збільшенням часу обчислення на таскель (негативна кореляція).
Коли ви згадуєте цілі паралелізації з розділу 2, одним пунктом було:
- висока ефективність використання всіх процесорних ядер
Вищезазначене що - то , БАСЕЙНИ chunksize-алгоритм може спробувати поліпшити є мінімізацією холостого ходу робочих-процеси , відповідно використання CPU-ядер .
Повторне запитання щодо SO multiprocessing.Pool
задають люди, які задаються питанням про невикористані ядра / робочі процеси на холостому ходу в ситуаціях, коли ви очікуєте, що всі робочі процеси зайняті. Хоча це може мати багато причин, простої робочих процесів до кінця обчислення - це спостереження, яке ми часто можемо зробити, навіть із щільними сценаріями (рівний час обчислення на клавішу) у випадках, коли кількість робітників не є дільником числа із шматків ( n_chunks % n_workers > 0
).
Питання зараз:
Як ми можемо практично перекласти наше розуміння шматки в щось, що дозволяє пояснити спостереження за використанням працівників або навіть порівняти ефективність різних алгоритмів у цьому відношенні?
6.1 Моделі
Для отримання глибшого розуміння тут нам потрібна форма абстракції паралельних обчислень, яка спрощує надто складну реальність до керованої міри складності, зберігаючи при цьому значення у визначених межах. Така абстракція називається моделлю . Реалізація такої " Моделі паралелізації" (РМ) генерує відображені на робочому місці метадані (позначки часу), як справжні обчислення, якби дані збиралися. Створені моделлю метадані дозволяють прогнозувати метрики паралельних обчислень за певних обмежень.
Однією з двох підмоделей у визначеному тут ПМ є Модель розподілу (СР) . DM пояснює , як атомні одиниці роботи (taskels) розподілені по паралельним робочим і часу , коли немає інших факторів , крім відповідного chunksize-алгоритму, число робочих, введення-ітерація (кількість taskels) і їх тривалості обчислень не зважають . Це означає, що будь-яка форма накладних витрат не включена.
Для отримання повного PM , DM розширюється накладними моделями (OM) , що представляють різні форми паралелізації накладних витрат (PO) . Таку модель потрібно калібрувати для кожного вузла окремо (апаратні, ОС-залежності). Скільки форм накладних витрат представлено в ОМ , залишається відкритим, і тому може існувати безліч ОМ з різним ступенем складності. Який рівень точності потребує реалізований ОМ , визначається загальною вагою ПО для конкретного обчислення. Коротші кисті призводять до більшої ваги PO , що, в свою чергу, вимагає більш точного OM , якби ми намагалися передбачити ефективність паралелізації (PE) .
6.2 Паралельний графік (PS)
Паралельний графік являє собою двовимірне подання паралельних обчислень, де вісь х представляє час і вісь у представляє собою пул паралельних робочих. Кількість робітників і загальний час обчислень позначають протяжність прямокутника, в якому намальовано менші прямокутники. Ці менші прямокутники представляють атомні одиниці роботи (кисті).
Нижче ви знайдете візуалізацію PS, намальовану даними з DM алгоритму chunksize Пула для щільного сценарію .
- Вісь х розділена на рівні одиниці часу, де кожна одиниця відповідає часу обчислення, необхідного таскелю.
- Вісь y ділиться на кількість робочих процесів, які використовує пул.
- Таскель тут відображається як найменший прямокутник блакитного кольору, розміщений у часовій шкалі (графіку) анонімізованого робочого процесу.
- Завдання - це один або кілька завдань на робочій шкалі часу, які постійно підсвічуються однаковим відтінком.
- Одиниці часу простою представлені через плитки червоного кольору.
- Паралельний графік розділений на розділи. Останній розділ - це хвіст.
Назви складених частин видно на малюнку нижче.
У повному ПМ, включаючи ОМ , Частка холостого ходу не обмежується лише хвостом, але також містить простір між завданнями і навіть між кистями.
6.3 Ефективність
Моделі, представлені вище, дозволяють кількісно визначити рівень використання робочих. Ми можемо розрізнити:
- Ефективність розподілу (DE) - обчислюється за допомогою DM (або спрощеного методу для щільного сценарію ).
- Ефективність паралелізації (PE) - або обчислюється за допомогою каліброваного PM (прогнозування), або розраховується за метаданими реальних обчислень.
Важливо зазначити, що обчислені коефіцієнти корисної дії не корелюють автоматично з більш швидкими загальними обчисленнями для даної задачі розпаралелювання. Використання працівника в цьому контексті лише розрізняє працівника, який має розпочату, але ще не закінчену таскель, та працівника, який не має такої "відкритої" таскели. Це означає, що можливий холостий хід протягом часового проміжку кисті не реєструється.
Всі вищезазначені коефіцієнти корисної дії в основному отримуються шляхом обчислення коефіцієнта розподілу зайнятості / паралельного розкладу . Різниця між DE та PE полягає в тому, що Busy Share займає меншу частину загального паралельного розкладу для PM, що розширений накладними витратами .
Ця відповідь далі обговорюватиме лише простий метод розрахунку DE для щільного сценарію. Це достатньо адекватно для порівняння різних алгоритмів chunksize, оскільки ...
- ... DM - це частина PM , яка змінюється з використанням різних алгоритмів chunksize.
- ... Щільний сценарій з однаковою тривалістю обчислення на таскель зображує "стабільний стан", для якого ці часові проміжки випадають із рівняння. Будь-який інший сценарій може просто призвести до випадкових результатів, оскільки порядок виконання завдань має значення.
6.3.1 Абсолютна ефективність розподілу (ADE)
Цю базову ефективність можна розрахувати загалом, розділивши зайняту частку на весь потенціал паралельного розкладу :
Абсолютна ефективність розподілу (ADE) = Графік зайнятості / паралельного розподілу
Для щільного сценарію спрощений код обчислення виглядає так:
def calc_ade(n_workers, len_iterable, n_chunks, chunksize, last_chunk):
"""Calculate Absolute Distribution Efficiency (ADE).
`len_iterable` is not used, but contained to keep a consistent signature
with `calc_rde`.
"""
if n_workers == 1:
return 1
potential = (
((n_chunks // n_workers + (n_chunks % n_workers > 1)) * chunksize)
+ (n_chunks % n_workers == 1) * last_chunk
) * n_workers
n_full_chunks = n_chunks - (chunksize > last_chunk)
taskels_in_regular_chunks = n_full_chunks * chunksize
real = taskels_in_regular_chunks + (chunksize > last_chunk) * last_chunk
ade = real / potential
return ade
Якщо немає Холостий Share , Busy частка буде дорівнює до Parallel Schedule , отже , ми отримуємо ADE 100%. У нашій спрощеній моделі це сценарій, коли всі доступні процеси будуть зайняті протягом усього часу, необхідного для обробки всіх завдань. Іншими словами, вся робота фактично паралелізується до 100 відсотків.
Але чому я тримаю в вигляді PE в якості абсолютного PE тут?
Щоб зрозуміти це, ми повинні розглянути можливий випадок для chunksize (cs), який забезпечує максимальну гнучкість планування (також, кількість гірців може бути. Випадковість?):
__________________________________ ~ ОДИН ~ __________________________________
Якщо у нас, наприклад, є чотири робочі процеси та 37 завдань, то будуть і робочі, що працюють в режимі холостого ходу, навіть chunksize=1
тому, що n_workers=4
це не дільник 37. Залишок від ділення 37/4 дорівнює 1. Цей єдиний залишився таскель повинен бути обробляється єдиним працівником, тоді як решта троє працюють на холостому ходу.
Так само, як і раніше, буде один працівник, що працює в режимі холостого ходу з 39 завданнями, як ви можете бачити на малюнку нижче.
Якщо порівняти верхню Parallel розклад для chunksize=1
з нижче версії для chunksize=3
, ви помітите , що верхня Parallel Розклад менше, терміни на вісь х коротше. Зараз має стати очевидним, як несподівано більші шматки також можуть призвести до збільшення загального часу обчислень, навіть для щільних сценаріїв .
Але чому б просто не використати довжину осі х для розрахунків ефективності?
Оскільки накладні витрати в цій моделі не містяться. Це буде різним для обох послідовностей, отже вісь х насправді не є безпосередньо порівнянною. Накладні витрати все одно можуть призвести до більш тривалого загального часу обчислення, як показано у випадку 2 з малюнка нижче.
6.3.2 Відносна ефективність розподілу (RDE)
Значення ADE не містить інформації, якщо можливий кращий розподіл завдань, якщо chunksize встановлено на 1. Краще тут все-таки означає меншу частку холостого ходу .
Щоб отримати значення DE, скориговане для максимально можливого DE , ми повинні розділити розглянутий ADE на ADE, який ми отримуємо chunksize=1
.
Відносна ефективність розподілу ( RDE ) = ADE_cs_x / ADE_cs_1
Ось як це виглядає в коді:
def calc_rde(n_workers, len_iterable, n_chunks, chunksize, last_chunk):
"""Calculate Relative Distribution Efficiency (RDE)."""
ade_cs1 = calc_ade(
n_workers, len_iterable, n_chunks=len_iterable,
chunksize=1, last_chunk=1
)
ade = calc_ade(n_workers, len_iterable, n_chunks, chunksize, last_chunk)
rde = ade / ade_cs1
return rde
RDE , як тут визначено, по суті є казкою про хвіст паралельного розкладу . На RDE впливає максимально ефективний шунксиз, що міститься в хвості. (Цей хвіст може мати довжину по осі Х chunksize
або last_chunk
.) Це призводить до того, що ПСИ природно сходиться до 100% (навіть) для всіх видів «хвостових виглядає» як показано на малюнку , наведеному нижче.
Низький рівень RDE ...
- є вагомою підказкою щодо потенціалу оптимізації.
- природно стає менш імовірним для довших ітерацій, оскільки відносна частина хвоста загального паралельного розкладу зменшується.
Ви можете знайти частина II цієї відповіді тут .
4
Це довільно, і весь розрахунок chunksize є евристичним. Відповідним фактором є те, наскільки може змінюватися ваш фактичний час обробки. Трохи більше про це тут, поки я не встигну відповісти, якщо тоді все ще знадобиться.