Разница Линк и Плинк - PullRequest
8 голосов
/ 17 декабря 2011

В чем разница между этими двумя?

Каков наилучший способ сравнения?

Всегда лучше plinq?

Когда мы используем plinq?

Ответы [ 5 ]

10 голосов
/ 18 декабря 2011

Linq - это набор технологий, которые работают вместе для решения аналогичного семейства проблем - во всех из них у вас есть источник данных (XML-файл или файлы, содержимое базы данных, коллекция объектов в памяти), и вы хотите извлечь некоторые или все эти данные и действуют на них каким-либо образом. Linq работает над общим набором проблем, таких как:

var brithdays = from user in users where
  user.dob.Date == DateTime.Today && user.ReceiveMails
  select new{user.Firstname, user.Lastname, user.Email};
foreach(bdUser in birthdays)
  SendBirthdayMail(bdUser.Firstname, bdUser.Lastname, bdUser.Email);

И эквивалент (явное использование связанных с Linq классов и методов с традиционным синтаксисом C #):

var birthdays = users
  .Where(user => user.dob.Date == DateTime.Today)
  .Select(user => new{user.Firstname, user.Lastname, user.Email});
foreach(bdUser in birthdays)
  SendBirthdayMail(bdUser.Firstname, bdUser.Lastname, bdUser.Email);

Оба примера кода, который может работать независимо от того, будет ли он преобразован в вызовы базы данных, синтаксический анализ документов XML или поиск по массиву объектов.

Единственная разница в том, что это за объект users. Если бы это был список, массив или другая перечисляемая коллекция, это было бы linq-to-objects, если бы это было System.Data.Linq.Table, это было бы linq to sql. Первое приведет к операциям в памяти, последнее к SQL-запросу, который затем будет десериализован для объектов в памяти как можно позже.

Если это был ParallelQuery - созданный путем вызова .AsParallel для перечисляемой коллекции в памяти - тогда запрос будет выполнен in-memroy, распараллелен (в большинстве случаев) так, чтобы он выполнялся несколькими потоками - в идеале, каждый сердечник занят работой, продвигая работу вперед.

Очевидно, что идея в том, чтобы быть быстрее. Когда это работает хорошо, это делает.

Хотя есть и некоторые недостатки.

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

Во-вторых, преимущества параллельной обработки зависят от доступных ядер. С запросом, который не заканчивает блокировку ресурсов на 4-ядерном компьютере, теоретически вы получаете ускорение в 4 раза (4-многопотоковое может дать вам больше или даже меньше, но, вероятно, не в 8 раз, поскольку удвоение потоков в некоторых частях процессора не дает явного двукратного увеличения). С одним и тем же запросом для одноядерного процессора или с привязкой к процессору, означающим, что доступно только одно ядро ​​(например, веб-сервер в режиме «веб-сада»), ускорение не происходит. При блокировке ресурсов все еще может быть выигрыш, но выгода зависит от машины.

В-третьих, если какой-либо общий ресурс (возможно, на который выводятся результаты коллекции) используется не потокобезопасным способом, он может пойти не так, как надо с неверными результатами, сбоями и т. Д.

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

В-пятых, если у вас четырехъядерный компьютер, работающий на более или менее одинаковом алгоритме в четырех разных потоках (возможно, в ситуации клиент-сервер из-за четырех клиентов или в ситуации с настольным компьютером из набора похожих задач) выше), то они уже лучше всего используют эти ядра. Разделение работы алгоритма таким образом, чтобы его можно было обрабатывать по всем четырем ядрам, означает, что вы перешли с четырех потоков, используя по одному ядру, каждый на 16 потоков, сражающихся на четырех ядрах. В лучшем случае это будет то же самое, и вероятные накладные расходы сделают это немного хуже.

Это может все еще быть главной победой во многих случаях, но приведенное выше должно прояснить, что это не всегда будет.

3 голосов
/ 19 июня 2013

Учитывая, что AsParallel прозрачно распараллеливает LINQ-запросы, возникает вопрос: «Почему Microsoft просто не распараллелила стандартные операторы запросов и не сделала PLINQ значением по умолчанию?»

Существует ряд причин для выборав подходе.Во-первых, для того, чтобы PLINQ был полезен, требуется достаточное количество вычислительных ресурсов для его передачи в рабочие потоки.Большинство запросов LINQ to Objects выполняются очень быстро, и не только распараллеливание не требуется, но издержки разделения, сортировки и координации дополнительных потоков могут фактически замедлить процесс.

Дополнительно:

Вывод запроса PLINQ (по умолчанию) может отличаться от запроса LINQ в отношении упорядочения элементов.

Следующие операторы запроса предотвращают распараллеливание запроса, если исходные элементы не находятся в исходной позиции индексации:

Take, TakeWhile, Skip и SkipWhile Индексированные версии Select, SelectMany и ElementAt Большинство операторов запросов изменяют позицию индексации элементов (включая те, которые удаляют элементы, например, Где).Это означает, что если вы хотите использовать предыдущие операторы, они обычно должны быть в начале запроса.

Следующие операторы запроса являются распараллеливаемыми, но используют дорогостоящую стратегию секционирования, которая иногда может быть медленнеечем последовательная обработка:

Join, GroupBy, GroupJoin, Distinct, Union, Intersect и Except Сеансовые перегрузки совокупного оператора в их стандартных воплощениях не распараллеливаются - PLINQ предоставляет специальные перегрузки, чтобы справиться с этим.

Когда использовать PLINQ Соблазнительно искать в существующих приложениях запросы LINQ и экспериментировать с их распараллеливанием.Это обычно непродуктивно, потому что большинство проблем, для которых LINQ, очевидно, является лучшим решением, имеют тенденцию выполняться очень быстро и поэтому не получают выгоды от распараллеливания.Лучший подход - найти узкое место, интенсивно использующее процессор, и затем подумать: «Можно ли это выразить как запрос LINQ?» (Отрадным побочным эффектом такой реструктуризации является то, что LINQ обычно делает код меньше и более читабельным.)

PLINQ хорошо подходит для смущающих параллельных задач.Это также хорошо работает для задач структурированной блокировки, таких как вызов нескольких веб-сервисов одновременно (см. Блокировка вызовов или I / O-Intensive Functions).

PLINQ может быть плохим выбором для обработки изображений, так как объединяет миллионы пикселейв выходной последовательности создает узкое место.Вместо этого лучше записывать пиксели непосредственно в массив или неуправляемый блок памяти и использовать параллелизм класса или задачи Parallel для управления многопоточностью.(Однако можно победить сопоставление результатов, используя ForAll. Это имеет смысл, если алгоритм обработки изображений естественным образом поддается LINQ.)

2 голосов
/ 17 декабря 2011

PLinq - это параллельная версия Linq. Некоторые запросы могут выполняться в нескольких потоках, а затем PLinq повышает производительность.

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

MSDN содержит много документации.

1 голос
/ 23 июля 2018

Я также хотел знать, когда использовать PLINQ вместо LINQ, поэтому я провел несколько тестов.

Краткое описание : При решении вопроса о том, использовать ли LINQ или PLINQ для выполнения запроса, нужно ответить на два вопроса.

  1. Сколько итераций задействовано в выполнении запроса (сколько объектов в коллекции)?

  2. Сколько работы задействовано в итерации?

Используйте LINQ, если PLINQ не является более производительным. PLINQ может быть более производительным, чем LINQ, если запрос коллекции включает слишком много итераций И ​​/ ИЛИ каждая итерация требует слишком много работы.

Но тогда возникают два трудных вопроса:

  1. Сколько итераций - это слишком много итераций?
  2. Сколько работы - это слишком много работы?

Мой совет: проверьте ваш запрос . Протестируйте один раз с использованием LINQ и один раз с использованием PLINQ, а затем сравните два результата.

Тест 1: Увеличение количества итераций в запросе за счет увеличения количества объектов в коллекции.

Затраты на инициализацию PLINQ составляют около 20 мс. Если сильные стороны PLINQ не используются, это напрасная трата времени, поскольку у LINQ накладные расходы 0 мс.

Работа, выполняемая в каждой итерации, всегда одинакова для каждого теста. Работа сведена к минимуму.

Определение работы : Умножение int (объекта в коллекции) на 10.

При выполнении итерации 1 миллиона объектов, где каждая итерация требует минимальной работы, PLINQ работает быстрее, чем LINQ. Хотя в профессиональной среде я никогда не запрашивал и даже не инициализировал коллекцию из 10 миллионов объектов в памяти, так что это может быть маловероятным сценарием, когда PLINQ превосходит LINQ.

╔═══════════╦═══════════╦════════════╗
║ # Objects ║ LINQ (ms) ║ PLINQ (ms) ║
╠═══════════╬═══════════╬════════════╣
║ 1         ║         1 ║         20 ║
║ 10        ║         0 ║         18 ║
║ 100       ║         0 ║         20 ║
║ 1k        ║         0 ║         23 ║
║ 10k       ║         1 ║         17 ║
║ 100k      ║         4 ║         37 ║
║ 1m        ║        36 ║         76 ║
║ 10m       ║       392 ║        285 ║
║ 100m      ║      3834 ║       2596 ║
╚═══════════╩═══════════╩════════════╝

Тест 2: увеличение объема работы, связанной с итерацией

Я установил число объектов в коллекции всегда равным 10, поэтому в запросе используется небольшое количество итераций. Для каждого теста я увеличивал объем работы по обработке каждой итерации.

Определение работы : Умножение int (объекта в коллекции) на 10.

Определение увеличения работы : Увеличение количества итераций для умножения целого числа на 10.

PLINQ быстрее выполнял запросы к коллекции, поскольку работа была значительно увеличена, когда число итераций внутри рабочей итерации было увеличено до 10 миллионов, и я пришел к выводу, что PLINQ превосходит LINQ, когда одна итерация включает в себя этот объем работы.

«# Итераций» в этой таблице означает количество итераций внутри рабочей итерации. См. Код теста 2 ниже.

╔══════════════╦═══════════╦════════════╗
║ # Iterations ║ LINQ (ms) ║ PLINQ (ms) ║
╠══════════════╬═══════════╬════════════╣
║ 1            ║         1 ║         22 ║
║ 10           ║         1 ║         32 ║
║ 100          ║         0 ║         25 ║
║ 1k           ║         1 ║         18 ║
║ 10k          ║         0 ║         21 ║
║ 100k         ║         3 ║         30 ║
║ 1m           ║        27 ║         52 ║
║ 10m          ║       263 ║        107 ║
║ 100m         ║      2624 ║        728 ║
║ 1b           ║     26300 ║       6774 ║
╚══════════════╩═══════════╩════════════╝

Тест 1, код:

class Program
{
    private static IEnumerable<int> _numbers;

    static void Main(string[] args)
    {
        const int numberOfObjectsInCollection = 1000000000;

        _numbers = Enumerable.Range(0, numberOfObjectsInCollection);

        var watch = new Stopwatch();

        watch.Start();

        var parallelTask = Task.Run(() => ParallelTask());

        parallelTask.Wait();

        watch.Stop();

        Console.WriteLine($"Parallel: {watch.ElapsedMilliseconds}ms");

        watch.Reset();

        watch.Start();

        var sequentialTask = Task.Run(() => SequentialTask());

        sequentialTask.Wait();

        watch.Stop();

        Console.WriteLine($"Sequential: {watch.ElapsedMilliseconds}ms");

        Console.ReadKey();
    }

    private static void ParallelTask()
    {
        _numbers
            .AsParallel()
            .Select(x => DoWork(x))
            .ToArray();
    }

    private static void SequentialTask()
    {
        _numbers
            .Select(x => DoWork(x))
            .ToArray();
    }

    private static int DoWork(int @int)
    {
        return @int * 10;
    }
}

Тест 2 код:

class Program
{
    private static IEnumerable<int> _numbers;

    static void Main(string[] args)
    {
        _numbers = Enumerable.Range(0, 10);

        var watch = new Stopwatch();

        watch.Start();

        var parallelTask = Task.Run(() => ParallelTask());

        parallelTask.Wait();

        watch.Stop();

        Console.WriteLine($"Parallel: {watch.ElapsedMilliseconds}ms");

        watch.Reset();

        watch.Start();

        var sequentialTask = Task.Run(() => SequentialTask());

        sequentialTask.Wait();

        watch.Stop();

        Console.WriteLine($"Sequential: {watch.ElapsedMilliseconds}ms");

        Console.ReadKey();
    }

    private static void ParallelTask()
    {
        _numbers
            .AsParallel()
            .Select(x => DoWork(x))
            .ToArray();
    }

    private static void SequentialTask()
    {
        _numbers
            .Select(x => DoWork(x))
            .ToArray();
    }

    private static int DoWork(int @int)
    {
        const int numberOfIterations = 1000000000;

        for (int i = 0; i < numberOfIterations; i++)
        {
            @int = @int * 10;
        }

        return @int;
    }
}
0 голосов
/ 19 марта 2013

Попробуйте избежать анонимных типов при работе с PLINQ , потому что согласно Threading в C # Джо Альбахари :

анонимнотипы (являющиеся классами и, следовательно, ссылочными типами) несут затраты на размещение на основе кучи и последующую сборку мусора.

(...)

распределение на основе стека обеспечивает высокую степень распараллеливания (поскольку каждый потокимеет собственный стек), тогда как все потоки должны конкурировать за одну и ту же кучу, управляемую одним менеджером памяти и сборщиком мусора.

...