Эксперт .NET
 Аватар для insite2012
5548 / 4311 / 1218
Регистрация: 12.10.2013
Сообщений: 12,371
Записей в блоге: 2

PLINQ и работа с PFX

03.01.2015, 10:36. Показов 61763. Ответов 4
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Итак, приступим.
Небольшое отступление. На создание этой темы меня подтолкнуло чтение статьи с сайта RSDN (написанной, в свою очередь, на материале из книги Албахари, как я понимаю). В данной статье раскрываются тонкости работы PLINQ и библиотеки PFX (ParallelFramework), предназначенных для повышения производительности создаваемых приложений с учетом возрастания количества ядер процессоров на современных компьютерах. Почитав данные материалы, и поискав по форуму, я заметил, что данное направление пока еще мало раскрыто (именно на форуме), и поэтому, думаю, данный материал (особенно, если его поддержат те, у кого есть намного бОльший опыт в данной области) будет полезен. Сразу скажу, что я сам пока не профи в данной области, и буду благодарен за поддержку моего начинания.

Первым делом рассмотрим самое простое - это запросы обычного LINQ. Штука очень удобная, вне всяких сомнений. Но как улучшить производительность для многоядерных компьютеров?
Для этой цели в платформу .NET (начиная с версии 4.0) добавлено новое API - PLINQ. Предположим, мы имеем большой набор данных, который нам необходимо перебрать через запросы LINQ. Для повышения производительности мы можем использовать параллельные запросы. Вот простой пример кода. Верхняя часть - обычный запрос, нижняя - параллельный.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
 
namespace ParallelTest {
    class Program {
        static void Main(string[] args) {
            IEnumerable<int> numbers = Enumerable.Range(0, 100000000);
            Stopwatch sw = new Stopwatch();
            sw.Start();
            int[] result_NotParallel = numbers.Where(i => i % 2 == 0).ToArray();
            sw.Stop();
            Console.WriteLine("Result WITHOUT using Parallel execution: {0}", sw.ElapsedMilliseconds);
 
            sw.Reset();
            sw.Start();
            int[] result_Parallel = numbers.AsParallel().WithExecutionMode(ParallelExecutionMode.ForceParallelism)
                .WithDegreeOfParallelism(Environment.ProcessorCount)
                .Where(i => i % 2 == 0).ToArray();
            sw.Stop();
            Console.WriteLine("Result WITH using Parallel execution: {0}", sw.ElapsedMilliseconds);
            Console.ReadLine();
        }
    }
}
Как видно из кода, для того, чтобы выполнить запрос параллельно, достаточно применить статический метод PLINQ AsParallel(). Необходимо отметить, что не все запросы могут быть выполнены параллельно, но это будет рассмотрено далее.
Несколько важных комментариев по приведенному коду:
1. Использование метода AsParallel() не гарантирует, что запрос ОБЯЗАТЕЛЬНО будет выполнен параллельно. Если PLINQ сделает вывод о том, что для улучшения производительности необходимо использовать обычный запрос, он будет выполнен именно так.
2. Для того, чтобы использовать именно параллельный запрос (даже если это затребует много ресурсов), после метода AsParallel() мы можем применить метод WithExecutionMode(), в котором указать параметр - один из членов перечисления ParallelExecutionMode, который и укажет коду, как следует выполнить запрос. В данном коде использован элемент ForceParallelism, который указывает, что запрос ОБЯЗАТЕЛЬНО должен быть выполнен параллельно, даже если потребуется больше ресурсов, чем для использования обычного запроса.
3. Так же есть возможность указать степень параллельности выполнения запроса - через статический метод WithDegreeOfParallelism(), с параметром типа int, который указывает, какое количество задач будет выполняться одновременно. В моем примере в качестве этого параметра используется число доступных ядер процессора на компьютере.

Кроме данных методов, в PLINQ существуют и другие, и их использование будет постепенно рассмотрено.
И напоследок, два скриншота с тестом для демонстрации преимуществ в скорости. Первый сделан на слабом нетбуке, на котором, собственно, и писался данный код. Как видно, использование параллельного запроса привело к увеличению времени выполнения, поскольку затребовало использования больше ресурсов.
Второй скриншот сделан на достаточно мощном четырехядерном компьютере, и там сразу видны все преимущества параллельности - загрузка всех ядер процессора распределена равномерно, время выполнения параллельного запроса существенно снижено.

Спасибо за внимание. Постепенно, по мере изучения мной возможностей PLINQ и PFX, данная тема будет дополняться новыми постами.
Миниатюры
PLINQ и работа с PFX   PLINQ и работа с PFX  
17
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
03.01.2015, 10:36
Ответы с готовыми решениями:

PLINQ или Parallel.ForEach?
Пытаюсь сообразить как лучше сделать задачку. Есть большой список EXCEL файлов и мне надо их считать проделать некоторые операции (все...

Можно ли использовать PLINQ на .NET Framework 2.0
Добрый день! Возможно ли на c# проделать следующую штуку: если у пользователя стоит только .NET Framework 2.0, то запускать один...

Как из pfx файла достать private/public ключ?
Есть pfx файл и из него надо достать приватный ключ и публичный ключ, создаю файл с помощью библиотеки bouncy castle. Если знаете более...

4
Эксперт .NET
 Аватар для insite2012
5548 / 4311 / 1218
Регистрация: 12.10.2013
Сообщений: 12,371
Записей в блоге: 2
03.01.2015, 11:44  [ТС]
Продолжим. В данном посте рассмотрим одну из особенностей PLINQ - порядок следования элементов в выходной последовательности.
Если используется обычный LINQ то порядок элементов в выходной последовательности соответствует их порядку во входной (не будем рассматривать фильтрацию последовательности, поскольку в данном случае, очевидно, не все члены входной последовательности присутствуют в выходной, хотя и в данном случае порядок следования сохранится).
Но при использовании PLINQ данное правило меняется. То есть, не факт, что порядок следования элементов в выходной последовательности будет в точности соответствовать входной. В большинстве случаев это и не важно, однако если порядок следования элементов выходной последовательности должен точно следовать порядку входной, можно явно указать это, используя статический метод AsOrdered(). Это укажет PLINQ отслеживать позицию каждого элемента последовательности, что приведет к замедлению скорости работы.
Если порядок следования последовательности после какого-либо запроса PLINQ уже не имеет значения, можно дать команду PLINQ прекратить отслеживание позиций элементов через статический метод AsUnordered().
Вот код, который это демонстрирует, и скриншот выполненной программы.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace ParallelTest2 {
    class Program {
        static void Main(string[] args) {
            string data = "abcdefgh";
            //Последовательный запрос
            string not_Parallel = new string(data
                .Select(s => char.ToUpper(s))
                .ToArray());
            Console.WriteLine(not_Parallel);
            //Параллельный запрос без сохранения порядка следования элементов
            string with_Parallel_NotOrdered = new string(data
                .AsParallel()
                .Select(s => char.ToUpper(s))
                .ToArray());
            Console.WriteLine(with_Parallel_NotOrdered);
            //Параллельный запрос с сохранением порядка следования элементов
            string with_Parallel_WithOredered = new string(data
                .AsParallel()
                .AsOrdered()
                .Select(s => char.ToUpper(s))
                .ToArray());
            Console.WriteLine(with_Parallel_WithOredered);
            //Параллельный запрос с включением-отключением отслеживания порядка следования элементов
            string final_Result = new string(data.AsParallel()
            .AsOrdered()
            .Select(s => char.ToUpper(s))
            .AsUnordered()
            .Select(s => char.ToLower(s))
            .ToArray());
            Console.WriteLine(final_Result);
 
            Console.ReadLine();
        }
    }
}
Миниатюры
PLINQ и работа с PFX  
6
Эксперт .NET
 Аватар для insite2012
5548 / 4311 / 1218
Регистрация: 12.10.2013
Сообщений: 12,371
Записей в блоге: 2
04.01.2015, 15:59  [ТС]
Продолжаем тему.
Все мы в процессе использования LINQ часто используем анонимные типы, поскольку это удобно. Однако PLINQ вносит свои коррективы в практику. При использовании параллельных запросов наилучшим выбором будет использование не анонимных типов, а именованных структур. Происходит это из-за того, что структура - тип значения, и хранится в стеке. При параллельных запросах каждому потоку выделяется свой стек. При использовании же анонимных типов (которые, как известно, являются классами) всем потокам приходится обращаться к управляемой куче, что снижает производительность.
Я провел несколько экспериментов, и выяснил такую закономерность (на своем железе): при количестве элементов последовательности менее 1000000 анонимные типы работают быстрее. При большем количестве элементов последовательности зависимость изменяется: работа с использованием именованной структуры резко ускоряется, и чем больше элементов, тем четче видна эта разница.
Ниже пример кода и скриншот, который показывает разницу в скорости (количество элементов последовательности - 1000000), при различных вариантах (структура-анонимный тип).
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using System.Diagnostics;
 
namespace ConsoleApplication6 {
    class Program {
        static void Main(string[] args) {
            Stopwatch sw = new Stopwatch();
            int[] numbers = Enumerable.Range(0, 10000000).ToArray();
            sw.Start();
            var structResult = numbers
                .AsParallel()
                .Select((i, j) => new ItemStruct { Item = i, Index = j })
                .Where(data=>data.Item%2==0)
                .OrderBy(item => item.Index)
                .ToArray();
            Console.WriteLine("Result using struct: {0} milliseconds", sw.ElapsedMilliseconds);
 
            sw.Restart();
            var classResult = numbers
                .AsParallel()
                .Select((i, j) => new { Item = i, Index = j })
                .Where(data => data.Item % 2 == 0)
                .OrderBy(item => item.Index)
                .ToArray();
            Console.WriteLine("Result using class: {0} milliseconds", sw.ElapsedMilliseconds);
 
            Console.ReadLine();
        }
    }
    struct ItemStruct {
        public int Item;
        public int Index;
    }
}
Миниатюры
PLINQ и работа с PFX  
10
Эксперт .NET
 Аватар для insite2012
5548 / 4311 / 1218
Регистрация: 12.10.2013
Сообщений: 12,371
Записей в блоге: 2
10.01.2015, 14:05  [ТС]
Продолжу тему. Рассмотрим два вопроса: отмена параллельных операция и оптимизация параллельной работы.
Для отмены параллельной операции необходимо использовать расширяющий метод WithCancellation(), в который передать объект - маркер отмены - свойство Token объекта CancellationTokenSource, созданного перед этим.
После того, как объект отмены создан, вызов его метода Cancel() приведет к передаче в параллельные вычисления объекта CancellationToken, после чего все потоки, выполняющие параллельные вычисления, будут завершены.
Необходимо отметить, что в данном случае необходима обязательная обработка ошибок в параллельном коде, поскольку вызов этого метода привод к генерации OperationCanceledException().
Ниже приведен небольшой пример с комментариями к коду.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using System.Threading;
using System.Threading.Tasks;
 
namespace PLINQ_Cancellation {
    class Program {
        //Маркер отмены
        static CancellationTokenSource cancelToken = new CancellationTokenSource();
        static void Main(string[] args) {
            //Входная последовательность
            IEnumerable<int> numbers = Enumerable.Range(0, 100);
            //Параллельная фильтрация последовательности
            var result = numbers
                .AsParallel()
                .WithCancellation(cancelToken.Token)
                .WithMergeOptions(ParallelMergeOptions.NotBuffered)
                .Where(n => {
                    Thread.Sleep(100);
                    return n % 2 == 0;
                });
            //Поток для отмены операции (через 2 сек.)
            new Thread(() => {
                //Задержка 2 сек.
                Thread.Sleep(2000);
                //Вызов метода отмены параллельной операции
                cancelToken.Cancel();
 
            }).Start();
            //Получение выходной последовательности
            //(с обязательной обработкой OperationCanceledException)
            try {
                foreach (var r in result) {
                    Console.WriteLine("Item: {0}", r);
                }
            }
            catch (OperationCanceledException ex) {
                Console.WriteLine(ex.Message);
            }
            Console.ReadLine();
        }
    }
}
Оптимизация параллельных вычислений (со стороны выходной последовательности) может быть осуществлена следующим образом. Допустим, нам необходимо для каждого элемента выполнить какой-либо метод, при чем последовательность выполнения нам не важна. Тогда все, что следует сделать - вызвать метод ForAll() после вызова AsParallel(), передав в метод ForAll() делегат (прямо или через лямбда выражение). После этого для каждого элемента последовательности будет выполнен метод-цель переданного делегата, с параметром - элементом последовательности.
Ниже приведен простой пример использования данного метода.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using System.Threading;
using System.Threading.Tasks;
 
namespace PLINQ_Optimisation1 {
    class Program {
        static void Main(string[] args) {
            IEnumerable<int> numbers = Enumerable.Range(0, 10);
 
            numbers.AsParallel()
                   .ForAll(i => Console.WriteLine("Changed item: {0}",i*i));
            Console.ReadLine();
        }
    }
}
7
24.08.2015, 11:05
 Комментарий модератора 
Если вы нашли неточность или опечатку, хотите что-то добавить к написанному в статье - обсуждение ведётся в отдельной теме: https://www.cyberforum.ru/faq/thread1519086.html
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
24.08.2015, 11:05
Помогаю со студенческими работами здесь

PLINQ: Читать файл, каждую строку преобразовывать и записывать преобразованную строку в другой файл
есть простая задача: читать файл, каждую строку преобразовывать и записывать преобразованную строку в другой файл. строки достаточно...

Многопоточность с TPL и PLINQ
Доброго времени суток! В поисках ответов на свои вопросы, я удачно зашёл на данный форум и посетил тему, в которой увидел отличный код,...

NullReferenceException при использовании PLINQ
Добрый день, проблема заключается в том,что при парсинге сайта с помощью HtmlAgilityPack. Метод SelectNode может вернуть null( парсю 1000...

Сумма и подсчет количества парных, непарных элементов в большом массиве (PLINQ)
Здавствуйте, нужна помощь в решении задачи. Дан большой массив радномных целых чисел (10000), нужно получить сумму всех елементов...

Найти среднее квадратическое отклонение от всех чисел последовательности с помощью PLINQ
Здравствуйте, нужна помощь в технологии plinq (именно с parallel), что бы распаралелитьлокальные данные. Суть задачи лежит в следующим:...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
5
Закрытая тема Создать тему
Опции темы

Новые блоги и статьи
http://iceja.net/ сервер решения полиномов
iceja 18.01.2026
Выкатила http:/ / iceja. net/ сервер решения полиномов (находит действительные корни полиномов методом Штурма). На сайте документация по API, но скажу прямо VPS слабенький и 200 000 полиномов. . .
Первый деплой
lagorue 16.01.2026
Не спеша развернул своё 1ое приложение в kubernetes. А дальше мне интересно создать 1фронтэнд приложения и 2 бэкэнд приложения развернуть 2 деплоя в кубере получится 2 сервиса и что-бы они. . .
Расчёт переходных процессов в цепи постоянного тока
igorrr37 16.01.2026
/ * Дана цепь постоянного тока с R, L, C, k(ключ), U, E, J. Программа составляет систему уравнений по 1 и 2 законам Кирхгофа, решает её и находит: токи, напряжения и их 1 и 2 производные при t = 0;. . .
Восстановить юзерскрипты Greasemonkey из бэкапа браузера
damix 15.01.2026
Если восстановить из бэкапа профиль Firefox после переустановки винды, то список юзерскриптов в Greasemonkey будет пустым. Но восстановить их можно так. Для этого понадобится консольная утилита. . .
Изучаю kubernetes
lagorue 13.01.2026
А пригодятся-ли мне знания kubernetes в России?
Сукцессия микоризы: основная теория в виде двух уравнений.
anaschu 11.01.2026
https:/ / rutube. ru/ video/ 7a537f578d808e67a3c6fd818a44a5c4/
WordPad для Windows 11
Jel 10.01.2026
WordPad для Windows 11 — это приложение, которое восстанавливает классический текстовый редактор WordPad в операционной системе Windows 11. После того как Microsoft исключила WordPad из. . .
Classic Notepad for Windows 11
Jel 10.01.2026
Old Classic Notepad for Windows 11 Приложение для Windows 11, позволяющее пользователям вернуть классическую версию текстового редактора «Блокнот» из Windows 10. Программа предоставляет более. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru