PLINQ (
PLINQ предназначен для параллельного выполнения
Рассмотрим простой пример использования
private static bool IsDivisibleBy5 ( int p )
{
// Моделирование длительной обработки
for ( int i = 0; i < 10000000; i++ ) {
i++; i--;
}
return p % 5 == 0;
}
Следующий фрагмент программы с использование
IEnumerable<int> arr = Enumerable.Range ( 1, 1000 );
var q =
from n in arr
where ISDivisibleBy5 ( n )
select n;
List<int> list = q.ToList();
Console.WriteLine ( list.Count.ToString() );
Чтобы распараллелить этот запрос средствами PLINQ, достаточно применить к источнику данных (в данном случае, это перечислимый массив arr) extension-метод AsParallel():
IEnumerable<int> arr = Enumerable.Range ( 1, 1000 );
var q =
from n in arr.AsParallel()
where ISDivisibleBy5 ( n )
select n;
List<int> list = q.ToList();
Console.WriteLine ( list.Count.ToString() );
В действительности, System.. Другими словами, запрос из Примера 2 может быть переписан в следующей эквивалентной форме:
IEnumerable<int> q =
Enumerable.Select<int,int> (
Enumerable.Where<int> (
arr, delegate (int n) { return
IsDivisibleBy5 (n); }
),
delegate (int n) { return n; }
);
Наряду с , библиотека System. содержит эквивалентный класс ParallelEnumerable, который содержит тот же набор методов, что и класс , но предназначенных для параллельного исполнения.
Тогда параллельная реализация Примера 4 запишется следующим образом:
IParallelEnumerable<int> q =
ParallelEnumerable.Select<int,int> (
ParallelEnumerable.Where<int> (
ParallelQuery.AsParallel<int> (arr),
delegate (int n) { return
IsDivisibleBy5 (n); }
),
delegate (int n) { return n; }
);
Рассмотрим еще один (схематичный) запрос, записанный в синтаксисе
IEnumerable<T> data = …;
var q =
from x in data
where p ( x )
orderby k ( x )
select f ( x );
foreach ( var e in q )
a ( e );
AsParallel():
IEnumerable<T> data = …;
var q =
from x in data.AsParallel()
where p ( x )
orderby k ( x )
select f ( x );
foreach ( var e in q )
a ( e );
И снова запрос из Примера 6 может быть эквивалентно переписан с использованием :
IEnumerable<T> data = …;
var q = Enumerable.Select(
Enumerable.OrderBy(
Enumerable.Where(data, x => p(x)),
x => k(x)),
x => f(x));
foreach (var e in q)
a(e);
Распараллелить запрос из Примера 8 легко:
IEnumerable<T> data = …;
var q = ParallelEnumerable.Select(
ParallelEnumerable.OrderBy(
ParallelEnumerable.Where(data.AsParallel(), x => p(x)),
x => k(x)),
x => f(x));
foreach (var e in q)
a(e);
Запрос из Примера 8 можно переписать еще одним эквивалентным способом, который отличается тем, что в нем опущены явные , что сокращает запрос в записи, но вызывает процедуру (неявного) вывода типов на этапе компиляции программы:
IEnumerable<T> data = …; var q = data.Where(x => p(x)).Orderby(x => k(x)).Select(x => f(x)); foreach (var e in q) a(e);
AsParallel() к источнику данных:
IEnumerable<T> data = …;
var q = data.AsParallel().
Where(x => p(x)).Orderby(x => k(x)).Select(x => f(x));
foreach (var e in q) a(e);
Прежде чем перейти к конструкциям параллельных шаблонов
Операция
Обычно, эта операция выполняется по некоторому стандартному шаблону - перебираются по порядку элементы исходной последовательности, а текущий результат сохраняется в специальной переменной, называемой аккумулятором. На каждом шаге перебора, к очередному элементу последовательности применяется основная вычислительная функция (например, функция, вычисляющая минимальное значение двух чисел), вторым аргументом которой является текущее значение переменной-аккумулятора. Результат применения этой функции снова записывается в переменную-аккумулятор, которая, в конечном итоге, будет хранить окончательный результат. Иногда основную вычислительную функцию называют функцией
В языке
public static TResult Aggregate<TSource, TAccumulate, TResult> (
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func,
Func<TAccumulate, TResult> resultSelector
)
{
TAccumulate accumulator = seed;
foreach (TSource elem in source)
{
accumulator = func(accumulator, elem);
}
return resultSelector(accumulator);
}
Чтобы воспользоваться этой функцией, программист должен указать
source ),seed ),func ) - функцию resultSelector ).В частности, с использованием данной функции сумму квадратов последовательности чисел можно вычислить следующим образом:
public static int SumSquares(IEnumerable<int> source)
{
return source.Aggregate(0,(sum, x) => sum + x * x, (sum) => sum);
}
Предположим, что вызов функции
SumSquares ( Enumerable.Range ( 1,4 ) )
происходит на машине с двумя ядрами (процессорами). Если предполагать, что при этом будут запущены два потока, то вполне возможно, что один поток будет вычислять сумму квадратов для $${1,4}$$, а второй поток - для $${3,2}$$. Таким образом, при последовательном и параллельном исполнении,
Последовательное исполнение: $$((((0 + 1^2) + 2^2) + 3^2) + 4^2) = 30$$
Параллельное исполнение: $$(((0 + 1^2) + 4^2) + ((0 + 3^2) + 2^2)) = 30$$
Из примеров последовательного и параллельного вычисления суммы квадратов чисел видно, что при параллеьном вычислении операции SumSquares функция
а функция объединения промежуточных результатов есть просто функция сложения
$$( x, y ) => x + y$$Кроме того, тип ( TSource ) элементов обрабатываемой последовательности может, в общем случае, отличаться от типа ( TAccumulate ) промежуточных значений, что еще больше подчеркивает различие двух названных выше функций.
По этой причине, функция в PLINQ имеет в качестве дополнительного параметра, по сравнению с аналогичной функцией в finalReduceFunc объединения (
public static TResult Aggregate<TSource, TAccumulate, TResult> (
this IParallelEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate,TSource,TAccumulate> intermediateReduceFunc,
Func<TAccumulate, TAccumulate, TAccumulate> finalReduceFunc,
Func<TAccumulate, TResult> resultSelector
)
В общем случае, не любая операция ), предлагаемых в PLINQ.
Для того, чтобы определить возможно ли корректное и к элементам подпоследовательности применяется функция finalReduceFunc. Если такой алгоритм всегда дает корректный результат, то это значит, что его можно вычислять с помощью шаблонов
Далее мы рассмотрим некоторые свойства операций
Во-первых, поскольку вычисление является параллельным, то
Во-вторых, как было отмечено выше, при параллельном вычислении операции
Оператор $$F(x,y) $$ является ассоциативным, если $$F(F(x,y),z) = F(x,F(y,z)) $$ и
Ниже приведена таблица с примерами различных по категориям операторов:
| Ассоциативный | |||
|---|---|---|---|
| Нет | Да | ||
| Коммутативный | Нет | $$(a, b) => a / b$$ $$(a, b) => a - b$$ $$(a, b) => 2 * a + b$$ | $$(string\ a, string\ b) => a.Concat(b)$$ $$(a, b) => a$$ $$(a, b) => b$$ |
| Да | $$(float\ a, float\ b) => a + b$$ $$(float\ a, float\ b) => a * b$$ $$(boo\l a, bool\ b) => !(a b)$$ $$(int\ a, int\ b) => 2 + a * b$$ $$(int\ a, int\ b) => (a + b) / 2$$ | $$(int a, int b) => a + b$$ $$(int\ a, int\ b) => a * b$$ $$(a, b) => Min(a, b)$$ $$(a, b) => Max(a, b)$$ | |
Таким образом, необходимым условием корректности параллельного вычисления операции
При использовании ) переменной-аккумулятора любое значение. Например, вычислить "сумму квадратов плюс 5" можно с помощью следующей операции
public static int SumSquaresPlus5(IEnumerable<int> source)
{
return source.Aggregate(5, (sum, x) => sum + x * x, (sum) => sum);
}
Однако, при выполнении этой операции параллельно, переменная-аккумулятор каждого потока будет принициализирована числом 5, что даст, в итоге, неверный результат.
Также неверными, в общем случае, начальными значениями переменных-аккумуляторов будут значение, возвращаемое оператором default для некоторого типа T, или первый элемент исходной последовательности.
Правильным начальным значением переменной-аккумулятора при параллельных вычислениях будет "единичный" (с алгебраической точки зрения) элемент, т.е., такой элемент, многократное применение к которому функции
Пусть $$F()$$ - функция
G(a,b) = G(b,a) для любых a, bG(G(a, b), c) = G(a, G(b, c)) для любых a, b, cF(a, x) = G(a, F(s, x)) , для любых a, xЗадача 1.
Реализуйте с помощью параллельной операции
Задача 2.
Реализуйте с помощью параллельной операции
Задача 3.
В "Решето Эратосфена для нахождения простых чисел", параллельная реализация построена на основе конструкции . При этом, логика алгоритма допускает использование операций из PLINQ.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.