Краткий ответ
Chunksize-алгоритм пула является эвристическим. Он обеспечивает простое решение для всех возможных сценариев проблем, которые вы пытаетесь внедрить в методы Pool. Как следствие, его нельзя оптимизировать для какого-либо конкретного сценария.
Алгоритм произвольно делит итерацию примерно на четыре части больше, чем при наивном подходе. Больше фрагментов означает больше накладных расходов, но увеличивает гибкость планирования. Как будет видно из этого ответа, в среднем это приводит к более высокому использованию рабочих, но без гарантии более короткого общего времени вычислений для каждого случая.
«Приятно знать, - можете подумать вы, - но как эта информация поможет мне в решении конкретных проблем, связанных с многопроцессорностью?» Что ж, это не так. Более честный короткий ответ: «короткого ответа нет», «многопроцессорность - это сложно» и «это зависит от обстоятельств». Наблюдаемый симптом может иметь разные корни даже при одинаковых сценариях.
Этот ответ пытается предоставить вам основные концепции, которые помогут вам получить более четкое представление о черном ящике планирования Pool. Он также пытается дать вам несколько основных инструментов для распознавания и предотвращения потенциальных обрывов, поскольку они связаны с размером фрагментов.
Содержание
Часть I
- Определения
- Цели распараллеливания
- Сценарии распараллеливания
- Риски Chunksize> 1
- Алгоритм размера фрагментов Пула
Количественная оценка эффективности алгоритма
6.1 Модели
6.2 Параллельное расписание
6.3 Эффективность
6.3.1 Абсолютная эффективность распределения (ADE)
6.3.2 Относительная эффективность распределения (RDE)
Часть II
- Наивный алгоритм против алгоритма пула
- Проверка в реальных условиях
- Заключение
Сначала необходимо уточнить некоторые важные термины.
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__
), в этом ответе отдельные единицы работы в задаче будут обозначаться как таскель .
Taskel (от задачи + эль ление) является наименьшей единицей работы в рамках задачи . Это однократное выполнение функции, указанной func
параметром Pool
-метода, вызываемое с аргументами, полученными из одного элемента переданного фрагмента . Задача состоит из chunksize
taskels .
Накладные расходы на распараллеливание (PO)
PO состоит из внутренних служебных данных Python и служебных данных для межпроцессного взаимодействия (IPC). Накладные расходы на задачу в Python включают код, необходимый для упаковки и распаковки задач и их результатов. IPC-overhead включает необходимую синхронизацию потоков и копирование данных между разными адресными пространствами (требуется два шага копирования: родительский -> очередь -> дочерний). Объем накладных расходов IPC зависит от ОС, оборудования и размера данных, что затрудняет обобщение результатов.
2. Цели распараллеливания
При использовании многопроцессорной обработки наша общая цель (очевидно) - минимизировать общее время обработки всех задач. Для достижения этой общей цели наша техническая цель должна заключаться в оптимизации использования аппаратных ресурсов .
Некоторые важные подцели для достижения технической цели:
- минимизировать накладные расходы на распараллеливание (наиболее известный, но не единственный: IPC )
- высокая загрузка всех ядер процессора
- ограничение использования памяти для предотвращения чрезмерного разбиения на страницы ( мусора ) ОС
Во - первых, задачи должны быть вычислительно тяжелый (интенсивный) достаточно, чтобы получить обратно РО мы должны платить за распараллеливания. Актуальность PO снижается с увеличением абсолютного времени вычислений на задачу. Или, говоря наоборот, чем больше абсолютное время вычислений на одну задачу для вашей проблемы, тем менее актуальной становится потребность в сокращении PO. Если ваши вычисления будут занимать часы на одну задачу, накладные расходы IPC будут незначительными по сравнению с этим. Основная задача здесь - предотвратить простаивание рабочих процессов после распределения всех задач. Держать все ядра загруженными означает, что мы максимально распараллеливаем.
3. Сценарии распараллеливания
Какие факторы определяют оптимальный аргумент размера фрагмента для таких методов, как multiprocessing.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) -chunk, блокирует передачу второго тяжелого Taskel в своей задаче одному из простаивающих рабочих, уже завершивших свои (60, 60) -chunks. Очевидно, мы бы не рискнули таким неприятным исходом, если бы поставили chunksize=1
.
Это риск больших кусков. Чем больше размер фрагментов, тем меньше накладных расходов мы жертвуем гибкостью планирования, а в случаях, подобных описанным выше, это плохая сделка.
Как мы увидим в главе 6. Количественная оценка эффективности алгоритма , большие куски также могут привести к неоптимальным результатам для плотных сценариев .
5. Алгоритм размера фрагментов Пула
Ниже вы найдете немного измененную версию алгоритма внутри исходного кода. Как видите, я отрезал нижнюю часть и превратил ее в функцию для 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
, приводит к начальному размеру фрагмента, по крайней мере, в четыре раза меньшему (для 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-алгоритма Pool ( cs_pool1
), а также размер фрагмента для полного алгоритма Pool ( cs_pool2
). Далее он вычисляет реальные коэффициенты rf_pool1 = cs_naive / cs_pool1
и rf_pool2 = cs_naive / cs_pool2
, которые говорят нам, во сколько раз наивно рассчитанные размеры фрагментов больше, чем внутренняя версия (и) Pool.
Ниже вы видите две фигуры, созданные на основе результатов этой функции. На левом рисунке просто показаны размеры фрагментов n_workers=4
вплоть до итеративной длины 500
. На правом рисунке показаны значения для rf_pool1
. Для итерируемой длины 16
реальный коэффициент становится >=4
(для len_iterable >= n_workers * 4
), а его максимальное значение - 7
для повторяющейся длины 28-31
. Это огромное отклонение от исходного коэффициента 4
, к которому алгоритм сходится для более длительных итераций. «Дольше» здесь относительно и зависит от количества указанных рабочих.
Помните chunksize cs_pool1
все еще не хватает extra
-adjustment с остатком от divmod
содержащихся в cs_pool2
от полного алгоритма.
Алгоритм продолжается:
if extra:
chunksize += 1
Теперь в случаях, когда есть остаток ( 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
). Для достаточно длинных итераций завершенный алгоритм размера фрагментов Pool ( 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
От 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-алгоритма выглядит по-другому по сравнению с выводом наивного алгоритма ...
- Как узнать, действительно ли подход Пула что- то улучшает ?
- И что именно это могло что - то быть?
Как показано в предыдущей главе, для более длинных итераций (большего числа задач) алгоритм размера фрагментов Pool приблизительно разделяет итерацию на в четыре раза больше фрагментов, чем наивный метод. Меньшие фрагменты означают больше задач, а большее количество задач означает больше накладных расходов на распараллеливание (PO) , стоимость, которую необходимо сопоставить с преимуществом повышенной гибкости планирования (вспомните «Риски размера фрагмента> 1» ).
По довольно очевидным причинам базовый алгоритм размера фрагментов Pool не может сопоставить гибкость планирования с PO для нас. Накладные расходы IPC зависят от ОС, оборудования и размера данных. Алгоритм не может знать, на каком оборудовании мы запускаем наш код, и при этом он не знает, сколько времени потребуется для завершения таскела. Это эвристика, обеспечивающая базовую функциональность для всех возможных сценариев. Это означает, что его нельзя оптимизировать для какого-либо конкретного сценария. Как упоминалось ранее, PO также становится все менее проблемным с увеличением времени вычислений на задачу (отрицательная корреляция).
Когда вы вспоминаете цели распараллеливания из главы 2, вы видите один пункт:
- высокая загрузка всех ядер процессора
Ранее упомянутое кое-что , что алгоритм размера фрагмента Pool может попытаться улучшить, - это минимизация простаивающих рабочих процессов , соответственно, использование ядер процессора .
Повторяющийся вопрос о SO multiprocessing.Pool
задают люди, интересующиеся неиспользуемыми ядрами / простаивающими рабочими процессами в ситуациях, когда вы ожидаете, что все рабочие процессы будут заняты. Хотя у этого может быть много причин, простаивающие рабочие процессы ближе к концу вычисления - это наблюдение, которое мы часто можем сделать, даже с плотными сценариями (равное время вычислений на таскел) в тех случаях, когда количество рабочих не является делителем числа. кусков ( n_chunks % n_workers > 0
).
Теперь вопрос:
Как мы можем практически преобразовать наше понимание размеров фрагментов в нечто, что позволяет нам объяснить наблюдаемое использование рабочих или даже сравнить эффективность различных алгоритмов в этом отношении?
6.1 Модели
Для получения более глубокого понимания нам нужна форма абстракции параллельных вычислений, которая упрощает чрезмерно сложную реальность до управляемой степени сложности, сохраняя при этом значимость в определенных границах. Такая абстракция называется моделью . Реализация такой « модели распараллеливания» (PM) генерирует отображаемые работником метаданные (временные метки), как и при реальных вычислениях, если бы данные собирались. Сгенерированные моделью метаданные позволяют прогнозировать показатели параллельных вычислений при определенных ограничениях.
Одной из двух подмоделей в рамках определенного здесь PM является Модель распределения (DM) . DM объясняет , как атомные единицы работы (taskels) распределены по параллельным рабочим и времени , когда нет других факторов , кроме соответствующего chunksize-алгоритма, число рабочих, ввод-итерация (количество taskels) и их продолжительности вычислений не считаются . Это означает, что никакие накладные расходы не включены.
Для получения полной ПМ , то ДЙ расширяются с ВЛИ Model (OM) , представляющий различные формы Распараллеливания Накладного (PO) . Такая модель должна быть откалибрована для каждого узла индивидуально (зависимости от оборудования и ОС). Сколько форм накладных расходов представлено в OM , остается открытым, поэтому могут существовать несколько OM с разной степенью сложности. Какой уровень точности требуется внедренному OM, определяется общим весом PO для конкретных вычислений. Более короткие задачи приводят к большему весу PO , что, в свою очередь, требует более точного, OM.если бы мы пытались предсказать эффективность распараллеливания (PE) .
6.2 Параллельное расписание (PS)
Параллельный график представляет собой двумерное представление параллельных вычислений, где ось х представляет время и ось у представляет собой пул параллельных рабочих. Количество рабочих и общее время вычислений обозначают протяженность прямоугольника, в котором нарисованы меньшие прямоугольники. Эти меньшие прямоугольники представляют собой элементарные единицы работы (таскелы).
Ниже вы найдете визуализацию PS, нарисованную с использованием данных из алгоритма chunksize DM of Pool для плотного сценария .
- Ось x разделена на равные единицы времени, где каждая единица обозначает время вычислений, необходимое таскелю.
- Ось Y делится на количество рабочих процессов, используемых пулом.
- Таскель здесь отображается в виде самого маленького прямоугольника голубого цвета, помещенного на временную шкалу (расписание) анонимизированного рабочего процесса.
- Задача - это один или несколько задач на шкале рабочего времени, которые постоянно выделяются одним и тем же оттенком.
- Единицы времени простоя представлены красными плитками.
- Параллельное расписание разбито на разделы. Последняя часть - это хвостовая часть.
Названия составных частей можно увидеть на картинке ниже.
В полном PM, включающем OM , доля холостого хода не ограничивается хвостовой частью, но также включает пространство между задачами и даже между элементами задач.
6.3 Эффективность
Представленные выше модели позволяют количественно оценить коэффициент использования рабочих. Мы можем выделить:
- Эффективность распределения (DE) - рассчитывается с помощью DM (или упрощенного метода для плотного сценария ).
- Эффективность распараллеливания (PE) - вычисляется с помощью откалиброванного PM (прогноз) или рассчитывается на основе метаданных реальных вычислений.
Важно отметить, что вычисленная эффективность не коррелирует автоматически с более быстрыми общими вычислениями для данной проблемы распараллеливания. Использование рабочих в этом контексте различает только работника, имеющего начатую, но еще не завершенную задачу, и рабочего, у которого нет такой «открытой» панели задач. Это означает, что возможен холостой ход течение периода времени таскела не регистрируется.
Все вышеупомянутые значения эффективности в основном получены путем вычисления частного деления доли занятости / параллельного расписания . Разница между DE и PE заключается в том, что доля занятости занимает меньшую часть общего параллельного расписания для PM с расширенными накладными расходами .
В этом ответе будет обсуждаться только простой метод вычисления DE. для плотного сценария. Этого достаточно для сравнения различных алгоритмов размера фрагментов, поскольку ...
- ... 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), который обеспечивает максимальную гибкость планирования (также, количество горцев может быть. Совпадение?):
__________________________________ ~ ONE ~ __________________________________
Если у нас, например, четыре рабочих процесса и 37 задач, будут простаивающие рабочие даже с chunksize=1
, просто потому, чтоn_workers=4
это не делитель 37. Остаток от деления 37/4 равен 1. Этот единственный оставшийся таскел должен быть обрабатывается единственным работником, а остальные три простаивают.
Точно так же останется один неработающий рабочий с 39 таскелями, как вы можете видеть на картинке ниже.
Когда вы сравните верхнее параллельное расписание для chunksize=1
с версией ниже для chunksize=3
, вы заметите, что верхнее параллельное расписание меньше, а временная шкала на оси x короче. Теперь должно стать очевидным, насколько неожиданно большие куски могут привести к увеличению общего времени вычислений, даже для плотных сценариев .
Но почему бы просто не использовать длину оси x для расчета эффективности?
Потому что накладные расходы в этой модели не содержатся. Он будет отличаться для обоих размеров фрагментов, поэтому ось x на самом деле напрямую не сопоставима. Накладные расходы могут привести к увеличению общего времени вычислений, как показано в случае 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 зависит от максимального эффективного размера фрагмента, содержащегося в хвосте. (Этот хвост может иметь длину по оси X chunksize
или last_chunk
.) Следствием этого является то, что RDE естественным образом сходится к 100% (даже) для всех видов «хвостовых образов», как показано на рисунке ниже.
Низкий RDE ...
- это сильный намек на потенциал оптимизации.
- естественно, становится менее вероятным для более длинных итераций, потому что относительная хвостовая часть общего параллельного расписания сокращается.
Вы можете найти часть II этого ответа здесь .
4
Произвольно, и весь расчет chunksize является эвристическим. Важным фактором является то, насколько может варьироваться ваше фактическое время обработки. Чуть подробнее об этом здесь , пока я не было времени для ответа , если еще нужен тогда.