Plinq: Распределение Данных Между Рабочими Потоками

Пусть существует определенная последовательность элементов, которую мы хотим обработать с помощью PLINQ-запроса.

В то же время существует определенное количество физических ядер ЦП, готовых выполнять рабочие потоки.

Как распределить элементы входной коллекции между потоками? Представим, что входная коллекция остается монолитной, а рабочие потоки начинают выбирать элементы один за другим.

Что.

отбор проб будет сводиться к выполнению следующих действий:

  • Установка блокировки
  • Выбор элемента
  • Снятие блокировки
Очевидно, что это повлечет за собой большие накладные расходы на установку/разблокировку.

Особенно они будут заметны, если элемент быстро обрабатывается рабочим потоком.

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

Как разделить входную последовательность элементов так, чтобы максимально распараллелить обработку, максимально эффективно используя возможности оборудования? Как правило, точно известно только количество рабочих потоков, которые будут обрабатывать запрос LINQ. Мы не можем заранее предсказать время обработки каждого товара.

Кроме того, длина коллекции неизвестна (конечно, количество элементов можно заранее посчитать, но это действие тоже потенциально трудоемко и ресурсозатратно).

То есть «честно» распределить элементы между потоками выполнения не получится.

Оптимальным решением было бы не делить сразу всю исходную последовательность, а выводить данные в виде порций разного размера.

Parallel Extensions делает именно это и работает следующим образом:

  1. каждый рабочий поток имеет свою собственную очередь элементов для обработки;
  2. начальный размер любой очереди равен единице, т.е.

    из исходной коллекции выбирается один элемент на поток;

  3. размер очереди растет: при повторном обращении к коллекции он увеличится вдвое.

    Длина очереди для каждого потока рассчитывается отдельно и увеличивается до определенного значения, после чего рост прекращается;

Вот как это выглядит. В момент времени 0 каждый рабочий поток получает 1 элемент из коллекции:

PLINQ: Распределение данных между рабочими потоками

В следующий момент рабочие потоки 1 и 2 завершат обработку своих элементов и выберут по два элемента из входной коллекции.

В этом случае поток 3 все еще обрабатывает первый выбранный элемент:

PLINQ: Распределение данных между рабочими потоками

Проходит еще некоторое время, потоки 1 и 2 завершают обработку элементов, полученных на предыдущем шаге, и теперь получают по 4 элемента каждый.

Поток 3 также завершает обработку и запрашивает 2 новых:

PLINQ: Распределение данных между рабочими потоками

И так далее.

Здесь существует баланс нагрузки: если один рабочий поток обрабатывает элемент слишком долго, другие потоки будут брать из коллекции все больше и больше, тем самым повышая общую эффективность.

Очередь потока растет, пока ее размер не достигнет 512 байт. То есть максимальное количество элементов, например типа Int32, равно 128. Конечно, рассмотренный метод не является универсальным и в ряде случаев будет не лучшим.

Можно предложить ряд дополнений к используемому решению.

Например, измерить среднее время обработки каждого элемента и изменить размер детали с учетом этого параметра.

Но разработчики Parallel Extensions решили придерживаться описанного выше подхода.

Он хорошо работал в большинстве сценариев, поэтому и был реализован.

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

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

Теги: #.

net 4.0 #PLINQ #параллелизм #разделение #.

NET

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.