C#提高LINQ运行效率的PLINQ

首先来了解一下什么是Plinq,我们来看看官方的描述:并行 LINQ (PLINQ) 是语言集成查询 (LINQ) 模式的并行实现。PLINQ 将整套 LINQ 标准查询运算符实现为 System.Linq 命名空间的扩展方法,并提供适用于并行操作的其他运算符。PLINQ 将 LINQ 语法的简洁和可靠性与并行编程的强大功能结合在一起。.

    这里有两个关键词,一个是“并行”,另一个是“扩展”,所谓并行是相对于程序顺序来说说的,LINQ是顺序执行的,而PLINQ的并行执行弥补了LINQ同步执行的效率,当然根据使用情况的不同来选择,比如数据量很小就体现不出PLINQ的优势了。扩展讲述了PLINQ是LINQ的扩展方法,也就是LINQ能特性PLINQ都具有。区别在于,PLINQ 会尝试充分利用系统上的所有处理器。方法是将数据源分区成片段,然后在多个处理器上针对单独工作线程上的每个片段执行并行查询。在许多情况下,并行执行意味着查询运行速度显著提高。

如何使用

    使用很简单,就是在数据源后面添加调用 ParallelEnumerable.AsParallel 扩展方法, 这里我们用例子来看PLINQ的使用,需求是查询10万内的随机数,如下:

        static void Main(string[] args)        {            // 生成10万个随机数            var source = Enumerable.Range(1, 100000);            // 用plinq找出这个数组的所有偶数            var result = source.AsParallel().Where(x => x % 2 == 0).Select(s=>s);            // 计算出偶数的个数            var count = result.Count();            // 打印出来结果            Console.WriteLine(count);//结果为50000        }

 效果对比

是不是很简单,接下来我们用PLINQ与顺序执行的LINQ对比一下使用效率,需求很简单,就是定义个dog对象,然后用Parallel.For并行写入字典,分别用linq和plinq查询出来,上代码:

   public class dog//dog对象    {        public int Id { get; set; }        public string Name { get; set; }        public int Year { get; set; }        public DateTime CreateTime { get; set; }    }    public partial class linqtest    {        private static void Test()        {            var dogs = new ConcurrentDictionary<int, dog>();//字典 线程安全            // 并行生成1000万条数据条数据            Parallel.For(0, 10_000_000, (i) =>            {                var single = new dog                {                    Id = i,                    Name = "name" + i,                    Year = new Random().Next(1, 20),//狗狗活的年数                    CreateTime = DateTime.Now.AddSeconds(i)                };                dogs.TryAdd(i, single);//写入到dogs字典里            });            Console.WriteLine("数据已生成");
            // PLINQ查询            var watch = new Stopwatch();            watch.Start();            var query1 = (from n in dogs.Values.AsParallel()//这里标志用了plinq                          where n.Year > 5 && n.Year < 12                          select n).ToList();            watch.Stop();            Console.WriteLine("PLINQ耗时:{0}", watch.ElapsedMilliseconds);            // LINQ查询            watch.Restart();            var query2 = (from n in dogs.Values                          where n.Year > 5 && n.Year < 12                          select n).ToList();
            watch.Stop();            Console.WriteLine("LINQ耗时:{0}", watch.ElapsedMilliseconds);        }    }  

最好用release运行,笔者用debug模式未测出差距,耗时如下图,明显看出PLINQ的效率是LINQ的一倍多。

C#提高LINQ运行效率的PLINQ

    在运行时,PLINQ 基础结构将分析查询的总体结构。如果通过并行可能会提高查询速度,PLINQ 则将源序列分区为可以同时运行的任务。如果并行化查询不安全,PLINQ 则只会按顺序运行查询。如果 PLINQ 可以在可能会较昂贵的并行算法或成本较低的顺序算法之间进行选择,它会默认选择顺序算法。

PLINQ使用的其它特性

 1、使用 AsSequential,如果你不想在过程使用并行查询,可以用此特性还原成顺序查询。 

var source = Enumerable.Range(1, 100000);var result =from num in source.AsParallel().AsSequential() where num % 2 ==0 select num;

2、使用AsOrdered ,由于PLINQ是并行运行,所以结果可能不是按照顺序来,这是可以添加AsOrdered方法来查询。

var source = Enumerable.Range(1, 100000);var result =from num in source.AsParallel().AsOrdered() where num % 2 ==0 select num;

3、使用WithDegreeOfParallelism,此属性可以设置电脑并行的CUP数。​​​​​​​

var result =from num in source.AsParallel().WithDegreeOfParallelism(5) where num % 2 ==0 select num;

这是常用的特性,其他特性请查看官方文档。

使用注意要点

    在很多情况下,可以并行化查询,但是设置并行查询的开销可能会超出获得的性能收益。如果查询不执行大量的计算,或者如果数据源较小,则 PLINQ 查询的速度可能比顺序 LINQ to Objects 查询的速度慢。PLINQ不支持LINQ to sql 只针对内存对象使用。确保并行执行的循环线程是安全的,尽量避免在lock中执行任务等待。使用的时候注意选择。