Effective C# 学习笔记(三十五) 了解PLINQ如何实现并行算法
2011-07-21 22:11 小郝(Kaibo Hao) 阅读(500) 评论(0) 编辑 收藏 举报AsParallel() 是PLINQ的一个方法,它使得并行编程变得简单,但是其并不能解决并行编程中的所有问题。
首先举例说明如何对于一个顺序执行的逻辑启用并行运行特性,看如下代码:
var numsParallel = from n in data.AsParallel() //这里用了AsParallel()方法进行并行调用
where n < 150
select Factorial(n);
并行的第一步是对要执行的数据进行划分,以保证由多核创建创建出的多线程能分别执行,以提高运行效率。PLINQ使用4中划分算法:
- Range partitioning(平均分配法)
即将要分配的数据平均分配到多个任务执行。数据/并行任务个数=每个任务执行的数据量
这种算法多用于支持索引,并且可获取数据数量的集合类型。主要是实现了IList<T>接口的集合类型,如List<T>,array。
- Chunk partitioning(区块分配法)
这种算法将要执行的数据按固定大小(目前据我所知不可控),分配给每个线程,当某个线程完成部分区块的处理后,按该算法就会把新的区块分配给该闲置线程,直到所有的线程处理完毕。区块的大小也会随处理逻辑花费时间,和要处理数据的多少(由where子句过滤的数据)而变化。
- Stripe partitioning(条纹分配法)
该算法是Range partitioning 的一种特例,其根据固定步长来跳格处理数据。如有四个任务来处理数据,第一个任务处理0,4,8,12,第二个任务处理1,5,9,13,其他类似。其避免了为了内部线程同步去实现TakeWhile()和SkipWhile()方法。
- Hash Partitioninig(哈希分配法)
这种算法主要是为了解决带Join,Groupjoin,GroupBy,Distinct,Except,Union及Intersector操作的查询。其保证了生成相同hash的数据项被同一个任务处理。其最小化了线程间内部通信。
线程间调度算法
- Pipelining
由一个线程来完成遍历工作,每遍历一个数据交给一个线程处理,若该线程处理完毕,其就可以处理新的数据了。而线程创建的个数一般和你CPU的核数相当。也就是说为了完成一个整体任务,一般会创建(CPU核数+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 算法,再去合并结果集就会造成线程过多,反而影响了性能。
- 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对于数据查询的不同处理方式,所以在编写自己的应用逻辑的时候一定要选择合适的方法。
出处:http://www.cnblogs.com/haokaibo/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。