C#4.0入门 第二章 任务并行库—第三页 性能的差异(转)

 
这里有一张单色(2值)的PNG的图片(1024*1024)。因为是单色,所以只有黑跟白两个值(实际上使用了只有黑跟白两色的8位PNG格式的图片文件)。到底,哪些像素是黑色的,我们编写一个程序来查点一下。同时用它来进行单任务和2~4任务分别进行查点时性能的比较。


比较单任务与多任务




 using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
class Program
{
public static IEnumerable<Color> Pixels(int from, int to)
{
var bm = new Bitmap(@"s:\sample001.png");
for (int y = from; y < to; y++)
{
for (int x = 0; x < bm.Width; x++)
{
yield return bm.GetPixel(x, y);
}
}
}
private static bool isBlack(Color c)
{
return c.B == 0 && c.G == 0 && c.R == 0;
}
private static void count(int fromInclusive, int toExclusive, ref int count, object lockTarget)
{
var q = from n in Pixels(fromInclusive, toExclusive)
where isBlack(n)
select n;
int r = q.Count();
lock (lockTarget) { count += r; }
}
static void Main(string[] args)
{
Object thisLock = new Object();
DateTime start1 = DateTime.Now;
int count1 = 0;
count(0, 1024, ref count1, thisLock);
Console.WriteLine(DateTime.Now - start1);
Console.WriteLine("{0} Black Pixels by single task", count1);
DateTime start2 = DateTime.Now;
int count2 = 0;
Parallel.Invoke(() => count(0, 512, ref count2, thisLock),
() =>count(512, 1024, ref count2, thisLock));
Console.WriteLine(DateTime.Now - start2);
Console.WriteLine("{0} Black Pixels by 2 task", count2);
DateTime start3 = DateTime.Now;
int count3 = 0;
Parallel.Invoke(() =>count(0, 342, ref count3, thisLock),
() => count(342, 682, ref count3, thisLock),
() => count(682, 1024, ref count3, thisLock));
Console.WriteLine(DateTime.Now - start3);
Console.WriteLine("{0} Black Pixels by 3 task", count3);
DateTime start4 = DateTime.Now;
int count4 = 0;
Parallel.Invoke(() =>count(0, 256, ref count4, thisLock),
() => count(256, 512, ref count4, thisLock),
() => count(512, 768, ref count4, thisLock),
() => count(768, 1024, ref count4, thisLock));
Console.WriteLine(DateTime.Now - start4);
Console.WriteLine("{0} Black Pixels by 4 task", count4);
}
}


这段代码乍看上去有点冗长,其实是必要的,正式开发中存在着大量的这种代码。首先,在并行处理的每一个任务中分别把画像文件一一读入。4任务的情况下要读4次相同的文件。虽然乍看上去是多余的,但是因为Bitmap类是非线程安全的,不能对别的线程进行访问。因此,尽管多余,还是要在一个处理线程内读入相同文件。


请注意使用了lock语句。在这种程序里,要防止互相竞争访问而导致死锁的发生。


另外,下面的代码中,变量r是可以去掉的。


未去掉变量r的代码




 int r = q.Count();
lock (lockTarget) { count += r; }


可以象下例那样去掉变量r。


去掉变量r的代码




 lock (lockTarget) { count += q.Count(); }


但是,查点像素的处理,实际上是在Count方法内进行的。因此,如果这样进行改写的话,这个处理本身就变成被排他性的锁住了,全体处理就被延迟了,从而失去了有效利用多核进行并行处理的意义。


还有一点要注意的是,如果任务数超过了实际存在的内核数,反而会影响性能。以下为在双核CPU上执行的例子。






运行结果1(在双核CPU上运行的例子。所花的时间,2任务<3任务=4任务<单任务)




 00:00:07.9630000
16470 Black Pixels by single task
00:00:03.0170000
16470 Black Pixels by 2 task
00:00:03.0870000
16470 Black Pixels by 3 task
00:00:03.0850000
16470 Black Pixels by 4 task

也就是说,如果在同一个内核上分配太多任务的话,反而会对性能产生影响。因此,在原本是双内核,但是由于使用了HT技术,所以看起来好像是4内核的Core
i5上,运行结果又不一样了。


运行结果2(在Core i5上运行的例子。所花的时间,4任务<3任务<2任务<单任务)



 00:00:02.8451627
16470 Black Pixels by single task
00:00:01.4070805
16470 Black Pixels by 2 task
00:00:01.1200640
16470 Black Pixels by 3 task
00:00:01.0490600
16470 Black Pixels by 4 task



这种情况下,把分配的任务增加到4任务的时候,任务越多所花时间越短。但是,由于实际上是双内核,所以从单任务变为2任务的时候,提速效果特别明显,但是超过了2任务,就没有这么明显的效果了。

■ 总结

把本章内容进行总结。

● 多内核时代已经开始。
● 软件开发的多内核时代是从Visual Studio 2010/C#4.0/.NET
Framework4.0开始的。


● 利用Parallel.Invoke类,可以使并行变得很容易实现。


● 利用Parallel.ForEach方法实现foreach语句。

利用Parallel.For方法实现for语句。


● 但是,不能利用并行替换所有循环操作。

利用并行可以大幅度提高性能。

posted @ 2011-06-11 16:01  董雨  阅读(217)  评论(0编辑  收藏  举报