Linq和Plinq的区别

9

这两者有什么区别?

最好的比较方法是什么?

总是用plinq更好吗?

我们什么时候使用plinq?


这里有一些不错的信息:http://www.scip.be/index.php?Page=ArticlesNET08&Lang=EN - user1231231412
1
我认为可以相当安全地假设,如果总是使用PLINQ更好,那么LINQ就不会存在。结果是:并不总是使用PLINQ更好。 - jason
5个回答

13

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);

以下是使用传统的 C# 语法和 Linq 相关类和方法的等效代码:

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,那么它就是针对 SQL 的 linq to sql。前者会产生内存操作,而后者则会产生 SQL 查询,然后尽可能晚地反序列化为内存对象。

如果它是通过在内存中可枚举的集合上调用 .AsParallel 生成的 ParallelQuery ,那么查询将在内存中并行执行 (大多数情况下),以便由多个线程执行 - 理想情况下保持每个核心忙碌,推动工作的进展。

显然,这里的想法是要更快。当它表现良好时,它确实如此。

但是也有一些缺点。

首先,始终存在一些开销来启动并行处理,即使在最终不可能并行处理的情况下也是如此。如果数据上没有足够的工作量,这种开销将超过任何潜在的收益。

其次,并行处理的好处取决于可用的核心。对于在 4 核机器上不会阻塞资源的查询,理论上可以获得 4 倍的速度提升(4 个超线程可能会给你更多,甚至可能会给你更少,但可能不会是 8 倍,因为超线程对 CPU 的某些部分的加倍并没有明显的两倍增加)。对于单核心或只有一个核心可用的处理器亲和性 (例如"Web Garden"模式下的 Web 服务器),相同的查询则不会有速度提升。如果存在资源阻塞,仍然可能会有收益,但收益取决于机器的情况。

第三,如果任何共享资源(可能是输出结果的集合)以非线程安全的方式使用,则可能会出现错误的结果、崩溃等问题。

第四点,如果有一个共享资源被线程安全地使用,并且这种线程安全性来自于锁定,那么可能会出现足够的争用,从而成为瓶颈,抵消了并行化带来的所有好处。

第五点,如果您有一台四核机器在四个不同线程上运行更或多少相同的算法(可能是由于四个客户端的客户-服务器情况,或者在进程中较高的一组类似任务的桌面情况),那么它们已经最大限度地利用了这些核心。将算法中的工作分割开来,以便跨所有四个核心处理,意味着您已经从每个使用一个核心的四个线程转变为16个线程争夺四个核心。最好的情况下,它将是相同的,并且很可能会因开销而稍微变差。

在许多情况下,它仍然可能是一个重大的优势,但以上内容应该说明,并不总是如此。


5

我还想知道在何时使用PLINQ而不是LINQ,因此我进行了一些测试。

总结:在决定是否使用LINQ或PLINQ运行查询时,有两个问题需要回答。

  1. 运行查询涉及多少次迭代(集合中有多少对象)?

  2. 每个迭代涉及多少工作?

除非PLINQ更加高效,否则请使用LINQ。如果查询集合涉及太多次迭代和/或每个迭代涉及太多工作,则PLINQ可能比LINQ更加高效。

但是,接下来会出现两个困难的问题:

  1. 有太多次迭代是指多少次迭代?
  2. 太多工作是指多少工作?

我的建议是测试您的查询。先使用LINQ进行一次测试,然后再使用PLINQ进行一次测试,最后比较两个结果。

测试1:通过增加集合中的对象数量来增加查询中的迭代次数。

初始化PLINQ的开销约为20毫秒。如果没有利用PLINQ的优势,这就是浪费时间,因为LINQ的开销为0毫秒。

每个测试中,每次迭代涉及的工作量始终相同。工作量被保持最小。

工作定义:将int(集合中的对象)乘以10。

当迭代1百万个对象且每次迭代涉及最小工作时,PLINQ比LINQ更快。尽管在专业环境中,我从未查询过或甚至初始化过一个包含1000万个对象的集合,因此这可能是一个不太可能的情况,即PLINQ恰好优于LINQ。

╔═══════════╦═══════════╦════════════╗
║ # Objects ║ LINQ (ms) ║ PLINQ (ms) ║
╠═══════════╬═══════════╬════════════╣
║ 1120 ║
║ 10018 ║
║ 100020 ║
║ 1k        ║         023 ║
║ 10k       ║         117 ║
║ 100k      ║         437 ║
║ 1m        ║        3676 ║
║ 10m       ║       392285 ║
║ 100m      ║      38342596 ║
╚═══════════╩═══════════╩════════════╝

测试2:增加迭代中的工作量

我设置集合中对象的数量始终为10,因此查询涉及的迭代次数较少。对于每个测试,我增加了处理每个迭代所涉及的工作量。

工作定义:将int(集合中的对象)乘以10。

增加工作定义:增加将int乘以10的迭代次数。

当在工作迭代内部的迭代次数增加到1000万时,PLINQ在查询集合时比LINQ更快,我得出结论,当单个迭代涉及这么多工作量时,PLINQ优于LINQ。

表格中的“# 迭代次数”表示工作迭代内部的迭代次数。请参见下面的测试2代码。

╔══════════════╦═══════════╦════════════╗
║ # Iterations ║ LINQ (ms) ║ PLINQ (ms) ║
╠══════════════╬═══════════╬════════════╣
║ 1122 ║
║ 10132 ║
║ 100025 ║
║ 1k           ║         118 ║
║ 10k          ║         021 ║
║ 100k         ║         330 ║
║ 1m           ║        2752 ║
║ 10m          ║       263107 ║
║ 100m         ║      2624728 ║
║ 1b           ║     263006774 ║
╚══════════════╩═══════════╩════════════╝

测试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;
    }
}

2

PLinq是Linq的并行版本。一些查询可以在多个线程上执行,然后PLinq会提高性能。

然而,其他查询不能并行执行,如果这样做会产生错误的结果。因此,何时使用PLinq是您应该为每个查询决定的事情,并确保性能实际上有所提高。

MSDN对此有很多文档。


0

在使用 PLINQ时,考虑避免使用 匿名类型, 因为根据 《C#中的多线程》Joe Albahari著述:

匿名类型(作为类是引用类型)会产生堆分配成本和随后的垃圾回收成本。

(...)

基于堆的分配可以高度并行化(由于每个线程都有自己的堆栈),而所有线程必须竞争相同的堆 - 由单个内存管理器和垃圾收集器管理。


0

PLINQ 可以通过更有效地利用主机计算机上的所有可用核心显著增加 LINQ to Objects 查询的速度。这种提高的性能将高性能计算能力带到了桌面上。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接