小试 .NET 4.0 之 并行计算

我们都知道CPU的性能至关重要,但主频已经越来越难以提升,纵向发展受限的情况下,横向发展成为必然——核心数开始越来越多。然而多核心的利用、并行计算一直是编程中的难题,大的不说,就说代码的编写,程序员大多都有过痛苦的经历:多线程的程序代码量大,编写复杂,容易出错,并且实际运行效率是否理想也较难保证。

 

为改善这种状况,.NET 4.0中引入了 TPL(任务并行库),关于TPL,MSDN的简介是:

任务并行库 (TPL) 的设计是为了能更简单地编写可自动使用多处理器的托管代码。使用该库,您可以非常方便地用现有序列代码表达潜在并行性,这样序列代码中公开的并行任务将会在所有可用的处理器上同时运行。通常这会大大提高速度。

 

简而言之,TPL提供了一系列的类库,可以使编写并行运算的代码更简单和方便。

说起来很简单,我们来看点例子:

 

 1  void  ThreadpoolMatrixMult( int  size,  double [,] m1,  double [,] m2, 
 2       double [,] result)
 3  {
 4     int  N  =  size;                           
 5     int  P  =   2   *  Environment.ProcessorCount;  //  assume twice the procs for 
 6                                             //  good work distribution
 7     int  Chunk  =  N  /  P;                       //  size of a work chunk
 8    AutoResetEvent signal  =   new  AutoResetEvent( false ); 
 9     int  counter  =  P;                         //  use a counter to reduce 
10                                             //  kernel transitions    
11     for  ( int  c  =   0 ; c  <  P; c ++ ) {            //  for each chunk
12      ThreadPool.QueueUserWorkItem( delegate (Object o)
13      {
14         int  lc  =  ( int )o;
15         for  ( int  i  =  lc  *  Chunk;            //  iterate through a work chunk
16             i  <  (lc  +   1   ==  P  ?  N : (lc  +   1 *  Chunk);  //  respect upper 
17                                                        //  bound
18             i ++ ) {
19           //  original inner loop body
20           for  ( int  j  =   0 ; j  <  size; j ++ ) {
21            result[i, j]  =   0 ;
22             for  ( int  k  =   0 ; k  <  size; k ++ ) {
23              result[i, j]  +=  m1[i, k]  *  m2[k, j];
24            }
25          }
26        }
27         if  (Interlocked.Decrement( ref  counter)  ==   0 ) {  //  use efficient 
28                                                        //  interlocked 
29                                                        //  instructions      
30          signal.Set();   //  and kernel transition only when done
31        }
32      }, c); 
33    }
34    signal.WaitOne();
35  }

 

 

很眼熟但同时看着也很心烦的代码吧。在换用TPL后,上面的代码就可以简化为:

 

 

 1  void  ParMatrixMult( int  size,  double [,] m1,  double [,] m2,  double [,] result)
 2  {
 3    Parallel.For(  0 , size,  delegate ( int  i) {
 4       for  ( int  j  =   0 ; j  <  size; j ++ ) {
 5        result[i, j]  =   0 ;
 6         for  ( int  k  =   0 ; k  <  size; k ++ ) {
 7          result[i, j]  +=  m1[i, k]  *  m2[k, j];
 8        }
 9      }
10    });
11  }

 

 

舒服多了吧?具体的内容请见MSDN的文章 优化多核计算机的托管代码

 

装好正式版的VS2010以后,写了段代码来测试下,TPL究竟好不好用。

代码很简单,拿一条字符串和一堆字符串里的每一条分别用LevenshteinDistance算法做字符串相似程度比对。先用传统的顺序执行的代码跑一遍,记录下时间;再换用TPL的并行代码跑一遍,记录下时间。然后比对两次运行的时间差异。

 

  1  using  System;
  2  using  System.Collections.Generic;
  3  using  System.Linq;
  4  using  System.Text;
  5  using  System.Threading.Tasks;
  6  using  System.Diagnostics;
  7 
  8  namespace  ParallelLevenshteinDistance
  9  {
 10       class  Program
 11      {
 12           static   void  Main( string [] args)
 13          {
 14              Stopwatch sw;
 15 
 16               int  length;
 17               int  count;
 18               string [] strlist;
 19               int [] steps;
 20               string  comparestring;
 21 
 22              Console.WriteLine( " Input string lenth: " );
 23              length  =   int .Parse(Console.ReadLine());
 24 
 25              Console.WriteLine( " Input string list count: " );
 26              count  =   int .Parse(Console.ReadLine());
 27 
 28              comparestring  =  GenerateRandomString(length);
 29              strlist  =   new   string [count];
 30              steps  =   new   int [count];
 31 
 32               //  prepare string[] for comparison
 33              Parallel.For( 0 , count,  delegate ( int  i)
 34              {
 35                  strlist[i]  =  GenerateRandomString(length);
 36              });
 37 
 38              Console.WriteLine( " {0}Computing...{0} " , Environment.NewLine);
 39 
 40               //  sequential comparison
 41              sw  =  Stopwatch.StartNew();
 42               for  ( int  i  =   0 ; i  <  count; i ++ )
 43              {
 44                  steps[i]  =  LevenshteinDistance(comparestring, strlist[i]);
 45              }
 46              sw.Stop();
 47              Console.WriteLine( " [Sequential] Elapsed: " );
 48              Console.WriteLine(sw.Elapsed.ToString());
 49 
 50               //  parallel comparison
 51              sw  =  Stopwatch.StartNew();
 52              Parallel.For( 0 , count,  delegate ( int  i)
 53              {
 54                  steps[i]  =  LevenshteinDistance(comparestring, strlist[i]);
 55              });
 56              sw.Stop();
 57              Console.WriteLine( " [Parallel] Elapsed: " );
 58              Console.WriteLine(sw.Elapsed.ToString());
 59                          
 60              Console.ReadLine();
 61          }
 62 
 63           private   static   string  GenerateRandomString( int  length)
 64          {
 65              Random r  =   new  Random(( int )DateTime.Now.Ticks);
 66              StringBuilder sb  =   new  StringBuilder(length);
 67               for  ( int  i  =   0 ; i  <  length; i ++ )
 68              {
 69                   int  c  =  r.Next( 97 123 );
 70                  sb.Append(Char.ConvertFromUtf32(c));
 71              }
 72               return  sb.ToString();
 73          }
 74 
 75           private   static   int  LevenshteinDistance( string  str1,  string  str2)
 76          {
 77               int [,] scratchDistanceMatrix  =   new   int [str1.Length  +   1 , str2.Length  +   1 ];
 78               //  distance matrix contains one extra row and column for the seed values            
 79               for  ( int  i  =   0 ; i  <=  str1.Length; i ++ ) scratchDistanceMatrix[i,  0 =  i;
 80               for  ( int  j  =   0 ; j  <=  str2.Length; j ++ ) scratchDistanceMatrix[ 0 , j]  =  j;
 81 
 82               for  ( int  i  =   1 ; i  <=  str1.Length; i ++ )
 83              {
 84                   int  str1Index  =  i  -   1 ;
 85                   for  ( int  j  =   1 ; j  <=  str2.Length; j ++ )
 86                  {
 87                       int  str2Index  =  j  -   1 ;
 88                      var cost  =  (str1[str1Index]  ==  str2[str2Index])  ?   0  :  1 ;
 89 
 90                       int  deletion  =  (i  ==   0 ?   1  : scratchDistanceMatrix[i  -   1 , j]  +   1 ;
 91                       int  insertion  =  (j  ==   0 ?   1  : scratchDistanceMatrix[i, j  -   1 +   1 ;
 92                       int  substitution  =  (i  ==   0   ||  j  ==   0 ?  cost : scratchDistanceMatrix[i  -   1 , j  -   1 +  cost;
 93 
 94                      scratchDistanceMatrix[i, j]  =  Math.Min(Math.Min(deletion, insertion), substitution);
 95 
 96                       //  Check for Transposition
 97                       if  (i  >   1   &&  j  >   1   &&  (str1[str1Index]  ==  str2[str2Index  -   1 ])  &&  (str1[str1Index  -   1 ==  str2[str2Index]))
 98                      {
 99                          scratchDistanceMatrix[i, j]  =  Math.Min(scratchDistanceMatrix[i, j], scratchDistanceMatrix[i  -   2 , j  -   2 +  cost);
100                      }
101                  }
102              }
103 
104               //  Levenshtein distance is the bottom right element
105               return  scratchDistanceMatrix[str1.Length, str2.Length];
106          }
107 
108      }
109  }
110 

 

 

这里只用了最简单的 Parallel.For 方法,代码很简单和随意,但是看看效果还是可以的。

测试机找了不少,喜欢硬件的朋友兴许也能找到你感兴趣的:P

 

Intel Core i7 920 (4物理核心8逻辑核心,2.66G) + DDR3 1600 @ 7-7-7-24

AMD Athlon II X4 630 (4物理核心,2.8G) + DDR3 1600 @ 8-8-8-24

AMD Athlon II X2 240 (2物理核心,2.8G) + DDR2 667

Intel Core E5300 (2物理核心,2.33G) + DDR2 667

Intel Atom N270 (1物理核心2逻辑核心,1.6G) + DDR2 667

还在VM workstation里跑过,分别VM了XP和WIN7,都跑在上述i7的机器里,各自分配了2个核心。

 

程序设置每个字符串长1000个字符,共1000条字符串。

每个机器上程序都跑了3遍,取平均成绩,得到下表:

 

CPUCoreTime_Sequential(s)Time_Parallel(s)S/P(%)
Intel Core i7 9204 Cores, 8 Threads, 2.6G55.132633614.64568653376.44%
AMD AthlonII X4 6304 Cores, 4 Threads, 2.8G58.1059200317.15249417338.76%
AMD AthlonII X2 2402 Cores, 2 Threads, 2.8G66.159735332.2939723204.87%
Intel E53002 Cores, 2 Threads, 2.3G70.8271571338.5065399183.94%
Intel Atom N2701 Cores, 2 Threads, 1.6G208.4785161157.2786908132.55%
VMWin7(2 logic core)2 Cores, 2 Threads56.9650677733.06908413172.26%
VMXP(2 logic core)2 Cores, 2 Threads59.799398735.35805007169.13%

 

可见,在多核心处理器上,并行计算的执行速度都得到了大幅提升,即便是在单核心超线程出2个逻辑核的Atom N270上亦缩短了32.55%的运行时间。在A240上并行计算的效率竟然是顺序计算的204.87% ?!而同样是4核心,i7 920在超线程的帮助下,并行执行效率提升明显高过A630。最后VM里的测试,是否也可以在某种程度上佐证在多核心的调度上,Win7要强过XP呢(纯猜测)?顺带可以看到,同样是i7的硬件环境,单线程宿主OS(Win7)里执行花费55.133秒,VM(Win7)里56.965秒,速度上有约3%的差异。

另外,针对性能较强的i7处理器,加大程序中的2个变量后再做测试,并行执行的效率比得到了进一步的提升。应该是因为创建/管理/销毁多线程的开销被进一步的摊平的缘故。例如在每字符串2000个字符,共2000条字符串的情况下,顺序执行和并行执行的时间分别是07:20.9679066和01:47.7059225,消耗时间比达到了409.42%。

 

 来几张截图:

 

 

从截图中可以发现,这段测试程序在顺序执行的部分,内存占用相对平稳,CPU则大部分核心处在比较空闲的状态。到了并行执行部分,所有核心都如预期般被调动起来,同时内存占用开始出现明显波动。附图是在每字符串2000个字符,共2000条字符串的情况下得到的。

 

 这里只是非常局部和简单的一个测试,目的是希望能带来一个直观的体验。微软已经提供了一组不错的例子可供参考 Samples for Parallel Programming with the .NET Framework 4  。

 

 

转载于:https://www.cnblogs.com/Elvin/archive/2010/04/20/1716258.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值