Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Метод Parallel.For






Простейшая и основная форма метода Parallel.For имеет следующий синтаксис:

public static ParallelLoopResult For(

int fromInclusive,

int toExclusive,

Action< int> body

)

Первые два параметра задают нижнюю и верхнюю границы области изменения индекса. Нижняя граница включается в область, верхняя - исключается. Это соответствует общепринятой записи оператора for:

for(int i = 0; i < n; i++) {body; }

Параметр body - делегат класса Action - представляет метод, выполняющий тело цикла. Методу передается индекс той итерации, которую должен выполнить метод.

Метод Parallel.For представляет функцию, возвращающую результат. Анализ этого результата необходим при управлении итерациями, позволяя выяснить, как закончилась итерация.

При распараллеливании циклов на языке С# ответственность за корректное применение метода Parallel.For полностью лежит на программисте. Если применить этот метод к циклу, где итерации не являются независимыми, то цикл будет выполняться, итерации будут выполняться параллельно, время работы сократится, но из-за гонки данных, скорее всего, результаты будут неправильными.

Метод Parallel.For является синтаксически наиболее простым и интуитивно понятным методом распараллеливания циклов. Это средство высокого уровня, не требующее обращения к низкоуровневым понятиям - задачи (task) или потока (thread). Понятно, что на С# программисте лежит ответственность за корректное использование метода только для тех циклов, где итерации независимы. Создание собственного массива потоков для распараллеливания цикла представляется в большинстве случаев неразумным решением. Оно особо чревато неприятными последствиями, когда конкурировать начинают несколько задач, каждая из которых создает свои потоки. В этих ситуациях накладные расходы могут быть неоправданно велики.

классический прием разделения длинного цикла на два цикла. Область изменения индекса [0, n-1] разбивается на группы (сегменты) и вначале идет внешний цикл по числу групп, а внутренний цикл идет по элементам группы. По сути это означает применение сегментного алгоритма, описанного в предыдущих главах. Рассмотрим цикл:

for(int i = 0; i < n; i++) { body(i) }

Область изменения индекса цикла разобьём на p сегментов. Заменим наш цикл двумя циклами:

int p = 10;

int m = n / p;

int start = 0, finish = 0;

//Внешний цикл распараллеливается

Parallel.For(0, p, (j) =>

{

start = j * m;

finish = (start + m < n)? start + m: n;

//Внутрений цикл удлиняет итерацию внешнего цикла

for (int i = start; i < finish; i++)

{

body(i);

}

});

Итерации не должны быть слишком короткими, поскольку в этом случае происходит частое переключение потоков. Сокращение длины распараллеливаемого цикла за счет увеличения времени выполнения одной итерации дает отрицательный эффект, увеличивая на порядок время работы. Если итерации цикла не являются слишком короткими, то не следует увеличивать длину итерации, разбивая внешний цикл на два цикла и распараллеливая только короткий внешний цикл. Лучшее решение дается для длинного цикла и длинных итераций. Оптимальное решение определяется конкретной задачей.

Например, для задачи умножения матриц Parallel.For прекрасно справляется с длинными циклами и длинными итерациями, обеспечивая реально возможное ускорение. Накладные расходы на распараллеливание не ощутимы.

Parallel.ForEach, internally, uses a Partitioner< T> to distribute your collection into work items. It will not do one task per item, but rather batch this to lower the overhead involved.

The second option will schedule a single Task per item in your collection. While the results will be (nearly) the same, this will introduce far more overhead than necessary, especially for large collections, and cause the overall runtimes to be slower.

FYI - The Partitioner used can be controlled by using the appropriate overloads to Parallel.ForEach, if so desired. For details, see Custom Partitioners on MSDN.

The main difference, at runtime, is the second will act asynchronous. This can be duplicated using Parallel.ForEach by doing:

Task.Factory.StartNew(() => Parallel.ForEach< Item> (items, item => DoSomething(item)));

By doing this, you still take advantage of the partitioners, but don't block until the operation is complete.






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.