zoukankan      html  css  js  c++  java
  • Effective C# 学习笔记(三十五) 了解PLINQ如何实现并行算法

    AsParallel() 是PLINQ的一个方法,它使得并行编程变得简单,但是其并不能解决并行编程中的所有问题。

    首先举例说明如何对于一个顺序执行的逻辑启用并行运行特性,看如下代码:

    var numsParallel = from n in data.AsParallel() //这里用了AsParallel()方法进行并行调用

    where n < 150

    select Factorial(n);

     

    并行的第一步是对要执行的数据进行划分,以保证由多核创建创建出的多线程能分别执行,以提高运行效率。PLINQ使用4中划分算法:

     

    1. Range partitioning(平均分配法)

    即将要分配的数据平均分配到多个任务执行。数据/并行任务个数=每个任务执行的数据量

    这种算法多用于支持索引,并且可获取数据数量的集合类型。主要是实现了IList<T>接口的集合类型,如List<T>array

     

    1. Chunk partitioning(区块分配法)

    这种算法将要执行的数据按固定大小(目前据我所知不可控),分配给每个线程,当某个线程完成部分区块的处理后,按该算法就会把新的区块分配给该闲置线程,直到所有的线程处理完毕。区块的大小也会随处理逻辑花费时间,和要处理数据的多少(由where子句过滤的数据)而变化。

     

    1. Stripe partitioning(条纹分配法)

    该算法是Range partitioning 的一种特例,其根据固定步长来跳格处理数据。如有四个任务来处理数据,第一个任务处理04812,第二个任务处理15913,其他类似。其避免了为了内部线程同步去实现TakeWhile()SkipWhile()方法。

     

    1. Hash Partitioninig(哈希分配法)

    这种算法主要是为了解决带JoinGroupjoinGroupByDistinctExceptUnionIntersector操作的查询。其保证了生成相同hash的数据项被同一个任务处理。其最小化了线程间内部通信。

     

    线程间调度算法

    1. Pipelining

    由一个线程来完成遍历工作,每遍历一个数据交给一个线程处理,若该线程处理完毕,其就可以处理新的数据了。而线程创建的个数一般和你CPU的核数相当。也就是说为了完成一个整体任务,一般会创建(CPU核数+1)个线程。

     

    1. Stop & Go

    该算法会调用所有线程来处理数据,多出现在Query查询执行ToList()ToArray(),或PLINQ需要处理所有数据时。使用该算法在可用内存较多的情况下可以提供运算性能,如:

    var stopAndGoArray = (from n in data.AsParallel()

    where n < 150

    select Factorial(n)).ToArray();

    var stopAndGoList = (from n in data.AsParallel()

    where n < 150

    select Factorial(n)).ToList();

    注意:上面的代码都是先构建Query查询,再执行stop & go算法的,这样可以提高性能。若是先执行Stop & go 算法,再去合并结果集就会造成线程过多,反而影响了性能。

     

    1. Inverted Enumeration.

    这种算法对于枚举来说性能一般来说时最高的。该算法比Stop & Go 算法使用的内存空间小,其性能取决于对于查询的结果要执行的操作所花费的时间。是哦那个该算法你应该对数据调用AsParallel()方法,并在调用结果时调用ForAll()方法。如下代码所示:

    var nums2 = from n in data.AsParallel() //调用并行运算方法

    where n < 150

    select Factorial(n);

    nums2.ForAll(item => Console.WriteLine(item));//调用ForAll() 方法

     

    LINQ是懒加载数据的,也就是说只有你在访问被LINQ描述的查询结果集时它才执行查询的运算。而对于每个数据的运算都是单个运行的。而对PLINQ,来说其更像 Linq to SQL Entity framework,当你获取第一个对象时,它返回的是整个数据集。所以要小心使用,以免造成不必要的内存占用和性能损失。下面的代码说明了LINQ to Objects PLINQ 对于数据的不同处理方式。

    static class ParallelTest

        {

         //测试数据长度

            private static int testLen = 30;

         //扩展方法where子句过滤执行方法

            public static bool SomeTest(this int inputValue)

            {

                Console.WriteLine("testing element: {0}", inputValue);

                return inputValue % 10 == 0;

            }

         //扩展方法Select子句投影执行方法

            public static string SomeProjection(this int input)

            {

                Console.WriteLine("projecting an element: {0}", input);

                return string.Format("Delivered {0} at {1}",

                input.ToString(),

                DateTime.Now.ToLongTimeString());

            }

         //测试Linq To Objects 顺序执行

            public static void TestSequence()

            {

                var answers = from n in Enumerable.Range(0, testLen)

                              where n.SomeTest()

                              select n.SomeProjection();

     

                var iter = answers.GetEnumerator();

                Console.WriteLine("About to start iterating");

                while (iter.MoveNext())

                {

                    Console.WriteLine("called MoveNext");

                    Console.WriteLine(iter.Current);

                }

            }

         //测试PLINQ并行执行

            public static void TestParallel()

            {

                var answers = from n in ParallelEnumerable.Range(0, testLen)

                              where n.SomeTest()

                              orderby n.ToString().Length

                              select n.SomeProjection().Skip(20).Take(20);

     

                var iter = answers.GetEnumerator();

                Console.WriteLine("About to start iterating");

                while (iter.MoveNext())

                {

                    Console.WriteLine("called MoveNext");

                    Console.WriteLine(iter.Current);

                }

                Console.Read();

            }

     

    --------------------LINQ to Objects 运行结果--------------------

    About to start iterating

    testing element: 0

    projecting an element: 0

    called MoveNext

    Delivered 0 at 9:35:39

    testing element: 1

    testing element: 2

    testing element: 3

    testing element: 4

    testing element: 5

    testing element: 6

    testing element: 7

    testing element: 8

    testing element: 9

    testing element: 10

    projecting an element: 10

    called MoveNext

    Delivered 10 at 9:35:40

    testing element: 11

    testing element: 12

    testing element: 13

    testing element: 14

    testing element: 15

    testing element: 16

    testing element: 17

    testing element: 18

    testing element: 19

    testing element: 20

    projecting an element: 20

    called MoveNext

    Delivered 20 at 9:35:40

    testing element: 21

    testing element: 22

    testing element: 23

    testing element: 24

    testing element: 25

    testing element: 26

    testing element: 27

    testing element: 28

    testing element: 29

     

    从上面的运行结果来看,每一步都是顺序执行的,先过滤数据,然后选择出合适条件的数据

    -------------PLINQ运行结果-------------

     

    About to start iterating

    testing element: 0

    projecting an element: 0

    testing element: 15

    testing element: 16

    testing element: 17

    testing element: 18

    testing element: 19

    testing element: 20

    testing element: 1

    testing element: 2

    testing element: 3

    testing element: 4

    testing element: 5

    testing element: 6

    projecting an element: 20

    testing element: 7

    testing element: 8

    testing element: 9

    testing element: 21

    testing element: 22

    testing element: 10

    projecting an element: 10

    testing element: 11

    testing element: 12

    testing element: 23

    testing element: 24

    testing element: 25

    testing element: 26

    testing element: 27

    testing element: 28

    testing element: 13

    testing element: 14

    testing element: 29

    called MoveNext

    Delivered 0 at 9:35:40

    called MoveNext

    Delivered 20 at 9:35:40

    called MoveNext

    Delivered 10 at 9:35:40

     

    从上面运行结果看,PLINQ的运行逻辑不是顺序的,其先运算并缓存大部分数据然后再在缓存中获取数据,过滤数据和选择输出不是并行的,你不能预测某个数据在何时进行处理。

     

    以上实验证明,LINQ to Objects PLINQ对于数据查询的不同处理方式,所以在编写自己的应用逻辑的时候一定要选择合适的方法。

  • 相关阅读:
    shell编程:字符串处理方式
    shell编程:变量替换
    export的用法
    docker stack利用secrets启动wordpress
    docker swarm创建swarm集群
    docker x509: certificate has expired or is not yet valid
    docker-compose的scale的用法
    字符串函数-unquote()函数
    Sass-@each
    Sass-@while
  • 原文地址:https://www.cnblogs.com/haokaibo/p/2113304.html
Copyright © 2011-2022 走看看