.NET并行库测试实例

.NET并行库测试实例

并行库存应用场景:
 并行计算首要目的是提高CPU的计算能力,简单说程序应该是以CPU密集型运算为主的,如果你的程序
是IO(磁盘和网络)密集型运算,并行计算并不能对你的程序有多大的提高。有时反而会有影响。我们
还是以事实来说话:


namespace ParallelTest
{
    static class Program
    {
        static void Compute(int i)
        {
            Thread.Sleep(20);
        }
        [STAThread]
        static void Main()
        {
            DateTime start = DateTime.Now;


            for(int i=0;i<100;i++){
                Compute(i);
            }

            TimeSpan ts = DateTime.Now - start;
            System.Console.WriteLine("TotalMilliseconds is : {0}",ts.TotalMilliseconds);
        }
    }
}

我们用Thread.Sleep(20);来模拟一个CPU密集型运算,它只和CPU调度有关,不存在IO运算。这个传统的程序
在我的Pentium D 300的机器上跑的时间是3125ms,理论上它应该是2000,但那上方法内运行的代码所占用的时间,
方法帧的生成,CPU的调度都是额外的时间开销,所以这个程序跑了3125ms.这是我运行20次得到的相同的时间
(当然也就是平均时间了)。

现在我们用并行代码来运行这个程序:
           
namespace ParallelTest
{
    static class Program
    {
        static void Compute(int i)
        {
            Thread.Sleep(20);
        }
        [STAThread]
        static void Main()
        {
            DateTime start = DateTime.Now;
    
    
     Parallel.For(0, 100, delegate(int i) {  Compute(i); });

           
     TimeSpan ts = DateTime.Now - start;
            System.Console.WriteLine("TotalMilliseconds is : {0}",ts.TotalMilliseconds);
        }
    }
}

这些我运行20次,它们的时间大多数时候是相同的,有十多次是1093.75,同时还有1109.375,1140.625,
1081.175之类的时间,总之它们的平均时间比3125少了几乎是两倍,也就是两个CPU运行方法中代码的时间应该
为1000ms,而方法帧生成,CPU调度只占用实际代执行的1/10左右。

更强的是当我把Sleep(20)改成Sleep(50)时,我们知道两个CPU执行100次应该至少2500ms,但实际上这个平均值是
2063.75ms,说明并代码并不只是两个CPU的两个线程在运行,一定还利用超线程/纤程等技术。而非并行代码运行
的时间是6250ms,没有任何让人惊喜的地方。


下面我们再来来看一下本地IO的例子。

我把我的一个目录从E分区复制到D分区,目录是一个图片库存,两级子目录,其中都是平时用到的图片和一些不可
告人(别揭发我啊)的图片按类型分类的。

我们先用传统的编程方式来实现:
 
namespace ParallelTest
{

    static class Program
    {

        static void CopyDir(DirectoryInfo s, DirectoryInfo d)
        {
            if (!d.Exists)
                d.Create();
            foreach (DirectoryInfo sd in s.GetDirectories()) {
                CopyDir(sd, new DirectoryInfo(Path.Combine(d.FullName,sd.Name)));
            }
            foreach (FileInfo f in s.GetFiles()) {
                f.CopyTo(new FileInfo(Path.Combine(d.FullName,f.Name)).FullName);
            }
           
        }

        [STAThread]
        static void Main()
        {
            DateTime start = DateTime.Now;

            CopyDir(new DirectoryInfo("E://BigTools//lspic"),new DirectoryInfo("d://"));

            TimeSpan ts = DateTime.Now - start;
            System.Console.WriteLine("times is : {0}",ts.TotalMilliseconds);
           
        }
    }
}
这段代码打印的时间是 75187.5ms.
改用并行代码:

namespace ParallelTest
{

    static class Program
    {

        private static void CopyDir(DirectoryInfo s, DirectoryInfo d)
        {
            if (!d.Exists)
                d.Create();
            Parallel.Invoke(
                () =>
                {
                    Parallel.ForEach(s.GetFiles(), f =>
                    {
                        var t = new FileInfo(Path.Combine(d.FullName, f.Name));
                        f.CopyTo(t.FullName);

                    });
                },
                () =>
                {
                    Parallel.ForEach(s.GetDirectories(), subs =>
                    {
                        var subd = new DirectoryInfo(Path.Combine(d.FullName, subs.Name));
                        CopyDir(subs, subd);
                    });
                });
        }

        [STAThread]
        static void Main()
        {
            DateTime start = DateTime.Now;

            CopyDir(new DirectoryInfo("E://BigTools//lspic"),new DirectoryInfo("d://"));

            TimeSpan ts = DateTime.Now - start;
            System.Console.WriteLine("times is : {0}",ts.TotalMilliseconds);
           
        }
    }
}


结果时间只用了33187.5ms,我们看这段程序的两个分支都在并行执行,首先把当前目录中文件和子目录的处理
作为两个匿名方法分配给Parallel.Invoke()方法来并行处理,然后在其中的循环又分别使用并行代码来执行。
时间节省了一倍多。


但是,这并不是真正的IO密集型运算。因为图片文件都在几十k左右,生成C#的目录对象和文件对象本身花的时间
和真正的IO读写的时间比例没有拉开。也就是IO操作还没到饱和。所以并行代码不仅充分利用了CPU,也大大利用
了IO性能。

但当我在另一个目录中放入6个600M左右(Myeclipse7.2的安装文件改名后复制)的文件时,结果就明显了:
非并行代码:231453.125ms,并行代码:573453.125ms.
令人吃惊的是并行代码不但不能提高程序的性能,反而极大地影响性能。无论人多少CPU在工作,磁盘IO的吞吐量是
有限的。而过多的并行操作反而增加了切换的频度,使IO操作本身增加了大量的OverHead.

当并行代码的程序正在运行的时候,我看了一下目标盘同时生成了四个文件,证明了我上面使用超线程或纤程的
猜想,但我的CPU在系统属性中无法看出支持超线程。

如果你亲手试一下这个例子,6个文件正好说明问题。当运行并行代码时,同时有四个线程(或纤程)在运行,因为
刚开运行时目录盘下立即产生四个文件,然后磁盘不停地嘎嘎嘎嘎响,但时间比四个文件单线程执行要多好久。大约在
450000ms才执行完成,然后余下的两个文件同样在并行代码下COPY,时间不很短。说明并行代码在多并发情况下,对密
集型IO操作不但不能提高性能,还大量浪费环境切换的开销。而实际有多少个并发,目前的并行库还不能控制。
即使只有两个文件,非并行代码和并行代码执行的时间分别为73890.625ms,168343.75ms。并行代码多花了一倍多的时间。


真正的本地IO操作C#代码下运行不可能很快,因为它没有DMA通道的支持,从托管代码到系统调用,每一次COPY一定数量的
字节都必须经过5次内核模式/用户模式的上下文切换和3次读缓冲/应用程序内存/写缓冲的复制。解决密集型IO的方案应该
是IO的并行吞吐能力和ZeorCopy之类的DMA通道才是首选,如java的FileChannel可以直接transferTo到一个输出流,比如
网络IO这样在文件和网络IO之间直接建立DMA通道而不需要反复切换和COPY。

所以,任何技术都有它的合适应用场景,比如在一个CPU的机器上单线程无IO操作运算肯定要比多线程还要快,因为无论如何
同时只有一个线程运行,如果没有IO阻塞,多线程反而增加线程调度的开销。并行编程也同样,主要看我们具体的执行逻辑,
根据具体的情况选择适当的技术。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值